##// END OF EJS Templates
Merge pull request #3450 from ipython/flatten...
Fernando Perez -
r11042:0c0c1a16 merge
parent child Browse files
Show More
@@ -0,0 +1,79 b''
1 """
2 Shim to maintain backwards compatibility with old frontend imports.
3
4 We have moved all contents of the old `frontend` subpackage into top-level
5 subpackages (`html`, `qt` and `terminal`), and flattened the notebook into
6 just `IPython.html`, formerly `IPython.frontend.html.notebook`.
7
8 This will let code that was making `from IPython.frontend...` calls continue
9 working, though a warning will be printed.
10 """
11
12 #-----------------------------------------------------------------------------
13 # Copyright (c) 2013, IPython Development Team.
14 #
15 # Distributed under the terms of the Modified BSD License.
16 #
17 # The full license is in the file COPYING.txt, distributed with this software.
18 #-----------------------------------------------------------------------------
19
20 #-----------------------------------------------------------------------------
21 # Imports
22 #-----------------------------------------------------------------------------
23 from __future__ import print_function
24
25 # Stdlib
26 import sys
27 import types
28 from warnings import warn
29
30 warn("The top-level `frontend` package has been deprecated. "
31 "All its subpackages have been moved to the top `IPython` level.")
32
33 #-----------------------------------------------------------------------------
34 # Class declarations
35 #-----------------------------------------------------------------------------
36
37 class ShimModule(types.ModuleType):
38
39 def __init__(self, *args, **kwargs):
40 self._mirror = kwargs.pop("mirror")
41 super(ShimModule, self).__init__(*args, **kwargs)
42
43 def __getattr__(self, key):
44 # Use the equivalent of import_item(name), see below
45 name = "%s.%s" % (self._mirror, key)
46
47 # NOTE: the code below is copied *verbatim* from
48 # importstring.import_item. For some very strange reason that makes no
49 # sense to me, if we call it *as a function*, it doesn't work. This
50 # has something to do with the deep bowels of the import machinery and
51 # I couldn't find a way to make the code work as a standard function
52 # call. But at least since it's an unmodified copy of import_item,
53 # which is used extensively and has a test suite, we can be reasonably
54 # confident this is OK. If anyone finds how to call the function, all
55 # the below could be replaced simply with:
56 #
57 # from IPython.utils.importstring import import_item
58 # return import_item('MIRROR.' + key)
59
60 parts = name.rsplit('.', 1)
61 if len(parts) == 2:
62 # called with 'foo.bar....'
63 package, obj = parts
64 module = __import__(package, fromlist=[obj])
65 try:
66 pak = module.__dict__[obj]
67 except KeyError:
68 raise ImportError('No module named %s' % obj)
69 return pak
70 else:
71 # called with un-dotted string
72 return __import__(parts[0])
73
74
75 # Unconditionally insert the shim into sys.modules so that further import calls
76 # trigger the custom attribute access above
77
78 sys.modules['IPython.frontend.html.notebook'] = ShimModule('notebook', mirror='IPython.html')
79 sys.modules['IPython.frontend'] = ShimModule('frontend', mirror='IPython')
@@ -0,0 +1,1 b''
1 Subproject commit 2a98f498092682f11affe9b0b86bd7e642cf7b13
@@ -0,0 +1,39 b''
1 """Tests for IPython.utils.importstring."""
2
3 #-----------------------------------------------------------------------------
4 # Copyright (C) 2013 The IPython Development Team
5 #
6 # Distributed under the terms of the BSD License. The full license is in
7 # the file COPYING, distributed as part of this software.
8 #-----------------------------------------------------------------------------
9
10 #-----------------------------------------------------------------------------
11 # Imports
12 #-----------------------------------------------------------------------------
13
14 import nose.tools as nt
15
16 from IPython.utils.importstring import import_item
17
18 #-----------------------------------------------------------------------------
19 # Tests
20 #-----------------------------------------------------------------------------
21
22 def test_import_plain():
23 "Test simple imports"
24 import os
25 os2 = import_item('os')
26 nt.assert_true(os is os2)
27
28
29 def test_import_nested():
30 "Test nested imports from the stdlib"
31 from os import path
32 path2 = import_item('os.path')
33 nt.assert_true(path is path2)
34
35
36 def test_import_raises():
37 "Test that failing imports raise the right exception"
38 nt.assert_raises(ImportError, import_item, 'IPython.foobar')
39
@@ -1,16 +1,16 b''
1 1 MANIFEST
2 2 build
3 3 dist
4 4 _build
5 5 docs/man/*.gz
6 6 docs/source/api/generated
7 7 docs/gh-pages
8 IPython/frontend/html/notebook/static/mathjax
8 IPython/html/notebook/static/mathjax
9 9 *.py[co]
10 10 __pycache__
11 11 build
12 12 *.egg-info
13 13 *~
14 14 *.bak
15 15 .ipynb_checkpoints
16 16 .tox
@@ -1,3 +1,3 b''
1 1 [submodule "components"]
2 path = IPython/frontend/html/notebook/static/components
2 path = IPython/html/static/components
3 3 url = https://github.com/ipython/ipython-components.git
@@ -1,86 +1,86 b''
1 1 # encoding: utf-8
2 2 """
3 3 IPython: tools for interactive and parallel computing in Python.
4 4
5 5 http://ipython.org
6 6 """
7 7 #-----------------------------------------------------------------------------
8 8 # Copyright (c) 2008-2011, IPython Development Team.
9 9 # Copyright (c) 2001-2007, Fernando Perez <fernando.perez@colorado.edu>
10 10 # Copyright (c) 2001, Janko Hauser <jhauser@zscout.de>
11 11 # Copyright (c) 2001, Nathaniel Gray <n8gray@caltech.edu>
12 12 #
13 13 # Distributed under the terms of the Modified BSD License.
14 14 #
15 15 # The full license is in the file COPYING.txt, distributed with this software.
16 16 #-----------------------------------------------------------------------------
17 17
18 18 #-----------------------------------------------------------------------------
19 19 # Imports
20 20 #-----------------------------------------------------------------------------
21 21 from __future__ import absolute_import
22 22
23 23 import os
24 24 import sys
25 25
26 26 #-----------------------------------------------------------------------------
27 27 # Setup everything
28 28 #-----------------------------------------------------------------------------
29 29
30 30 # Don't forget to also update setup.py when this changes!
31 31 if sys.version[0:3] < '2.6':
32 32 raise ImportError('Python Version 2.6 or above is required for IPython.')
33 33
34 34 # Make it easy to import extensions - they are always directly on pythonpath.
35 35 # Therefore, non-IPython modules can be added to extensions directory.
36 36 # This should probably be in ipapp.py.
37 37 sys.path.append(os.path.join(os.path.dirname(__file__), "extensions"))
38 38
39 39 #-----------------------------------------------------------------------------
40 40 # Setup the top level names
41 41 #-----------------------------------------------------------------------------
42 42
43 43 from .config.loader import Config
44 44 from .core.getipython import get_ipython
45 45 from .core import release
46 46 from .core.application import Application
47 from .frontend.terminal.embed import embed
47 from .terminal.embed import embed
48 48
49 49 from .core.error import TryNext
50 50 from .core.interactiveshell import InteractiveShell
51 51 from .testing import test
52 52 from .utils.sysinfo import sys_info
53 53 from .utils.frame import extract_module_locals
54 54
55 55 # Release data
56 56 __author__ = '%s <%s>' % (release.author, release.author_email)
57 57 __license__ = release.license
58 58 __version__ = release.version
59 59 version_info = release.version_info
60 60
61 61 def embed_kernel(module=None, local_ns=None, **kwargs):
62 62 """Embed and start an IPython kernel in a given scope.
63 63
64 64 Parameters
65 65 ----------
66 66 module : ModuleType, optional
67 67 The module to load into IPython globals (default: caller)
68 68 local_ns : dict, optional
69 69 The namespace to load into IPython user namespace (default: caller)
70 70
71 71 kwargs : various, optional
72 72 Further keyword args are relayed to the IPKernelApp constructor,
73 73 allowing configuration of the Kernel. Will only have an effect
74 74 on the first embed_kernel call for a given process.
75 75
76 76 """
77 77
78 78 (caller_module, caller_locals) = extract_module_locals(1)
79 79 if module is None:
80 80 module = caller_module
81 81 if local_ns is None:
82 82 local_ns = caller_locals
83 83
84 84 # Only import .zmq when we really need it
85 85 from IPython.kernel.zmq.embed import embed_kernel as real_embed_kernel
86 86 real_embed_kernel(module=module, local_ns=local_ns, **kwargs)
@@ -1,14 +1,14 b''
1 1 # encoding: utf-8
2 2 """Terminal-based IPython entry point.
3 3 """
4 4 #-----------------------------------------------------------------------------
5 5 # Copyright (c) 2012, IPython Development Team.
6 6 #
7 7 # Distributed under the terms of the Modified BSD License.
8 8 #
9 9 # The full license is in the file COPYING.txt, distributed with this software.
10 10 #-----------------------------------------------------------------------------
11 11
12 from IPython.frontend.terminal.ipapp import launch_new_instance
12 from IPython.terminal.ipapp import launch_new_instance
13 13
14 14 launch_new_instance()
@@ -1,391 +1,391 b''
1 1 """ A minimal application base mixin for all ZMQ based IPython frontends.
2 2
3 3 This is not a complete console app, as subprocess will not be able to receive
4 4 input, there is no real readline support, among other limitations. This is a
5 refactoring of what used to be the IPython/frontend/qt/console/qtconsoleapp.py
5 refactoring of what used to be the IPython/qt/console/qtconsoleapp.py
6 6
7 7 Authors:
8 8
9 9 * Evan Patterson
10 10 * Min RK
11 11 * Erik Tollerud
12 12 * Fernando Perez
13 13 * Bussonnier Matthias
14 14 * Thomas Kluyver
15 15 * Paul Ivanov
16 16
17 17 """
18 18
19 19 #-----------------------------------------------------------------------------
20 20 # Imports
21 21 #-----------------------------------------------------------------------------
22 22
23 23 # stdlib imports
24 24 import atexit
25 25 import json
26 26 import os
27 27 import shutil
28 28 import signal
29 29 import sys
30 30 import uuid
31 31
32 32
33 33 # Local imports
34 34 from IPython.config.application import boolean_flag
35 35 from IPython.config.configurable import Configurable
36 36 from IPython.core.profiledir import ProfileDir
37 37 from IPython.kernel.blocking import BlockingKernelClient
38 38 from IPython.kernel import KernelManager
39 39 from IPython.kernel import tunnel_to_kernel, find_connection_file, swallow_argv
40 40 from IPython.utils.path import filefind
41 41 from IPython.utils.py3compat import str_to_bytes
42 42 from IPython.utils.traitlets import (
43 43 Dict, List, Unicode, CUnicode, Int, CBool, Any, CaselessStrEnum
44 44 )
45 45 from IPython.kernel.zmq.kernelapp import (
46 46 kernel_flags,
47 47 kernel_aliases,
48 48 IPKernelApp
49 49 )
50 50 from IPython.kernel.zmq.session import Session, default_secure
51 51 from IPython.kernel.zmq.zmqshell import ZMQInteractiveShell
52 52
53 53 #-----------------------------------------------------------------------------
54 54 # Network Constants
55 55 #-----------------------------------------------------------------------------
56 56
57 57 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
58 58
59 59 #-----------------------------------------------------------------------------
60 60 # Globals
61 61 #-----------------------------------------------------------------------------
62 62
63 63
64 64 #-----------------------------------------------------------------------------
65 65 # Aliases and Flags
66 66 #-----------------------------------------------------------------------------
67 67
68 68 flags = dict(kernel_flags)
69 69
70 70 # the flags that are specific to the frontend
71 71 # these must be scrubbed before being passed to the kernel,
72 72 # or it will raise an error on unrecognized flags
73 73 app_flags = {
74 74 'existing' : ({'IPythonConsoleApp' : {'existing' : 'kernel*.json'}},
75 75 "Connect to an existing kernel. If no argument specified, guess most recent"),
76 76 }
77 77 app_flags.update(boolean_flag(
78 78 'confirm-exit', 'IPythonConsoleApp.confirm_exit',
79 79 """Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
80 80 to force a direct exit without any confirmation.
81 81 """,
82 82 """Don't prompt the user when exiting. This will terminate the kernel
83 83 if it is owned by the frontend, and leave it alive if it is external.
84 84 """
85 85 ))
86 86 flags.update(app_flags)
87 87
88 88 aliases = dict(kernel_aliases)
89 89
90 90 # also scrub aliases from the frontend
91 91 app_aliases = dict(
92 92 ip = 'KernelManager.ip',
93 93 transport = 'KernelManager.transport',
94 94 hb = 'IPythonConsoleApp.hb_port',
95 95 shell = 'IPythonConsoleApp.shell_port',
96 96 iopub = 'IPythonConsoleApp.iopub_port',
97 97 stdin = 'IPythonConsoleApp.stdin_port',
98 98 existing = 'IPythonConsoleApp.existing',
99 99 f = 'IPythonConsoleApp.connection_file',
100 100
101 101
102 102 ssh = 'IPythonConsoleApp.sshserver',
103 103 )
104 104 aliases.update(app_aliases)
105 105
106 106 #-----------------------------------------------------------------------------
107 107 # Classes
108 108 #-----------------------------------------------------------------------------
109 109
110 110 #-----------------------------------------------------------------------------
111 111 # IPythonConsole
112 112 #-----------------------------------------------------------------------------
113 113
114 114 classes = [IPKernelApp, ZMQInteractiveShell, KernelManager, ProfileDir, Session]
115 115
116 116 try:
117 117 from IPython.kernel.zmq.pylab.backend_inline import InlineBackend
118 118 except ImportError:
119 119 pass
120 120 else:
121 121 classes.append(InlineBackend)
122 122
123 123 class IPythonConsoleApp(Configurable):
124 124 name = 'ipython-console-mixin'
125 125 default_config_file_name='ipython_config.py'
126 126
127 127 description = """
128 128 The IPython Mixin Console.
129 129
130 130 This class contains the common portions of console client (QtConsole,
131 131 ZMQ-based terminal console, etc). It is not a full console, in that
132 132 launched terminal subprocesses will not be able to accept input.
133 133
134 134 The Console using this mixing supports various extra features beyond
135 135 the single-process Terminal IPython shell, such as connecting to
136 136 existing kernel, via:
137 137
138 138 ipython <appname> --existing
139 139
140 140 as well as tunnel via SSH
141 141
142 142 """
143 143
144 144 classes = classes
145 145 flags = Dict(flags)
146 146 aliases = Dict(aliases)
147 147 kernel_manager_class = KernelManager
148 148 kernel_client_class = BlockingKernelClient
149 149
150 150 kernel_argv = List(Unicode)
151 151 # frontend flags&aliases to be stripped when building kernel_argv
152 152 frontend_flags = Any(app_flags)
153 153 frontend_aliases = Any(app_aliases)
154 154
155 155 # create requested profiles by default, if they don't exist:
156 156 auto_create = CBool(True)
157 157 # connection info:
158 158
159 159 sshserver = Unicode('', config=True,
160 160 help="""The SSH server to use to connect to the kernel.""")
161 161 sshkey = Unicode('', config=True,
162 162 help="""Path to the ssh key to use for logging in to the ssh server.""")
163 163
164 164 hb_port = Int(0, config=True,
165 165 help="set the heartbeat port [default: random]")
166 166 shell_port = Int(0, config=True,
167 167 help="set the shell (ROUTER) port [default: random]")
168 168 iopub_port = Int(0, config=True,
169 169 help="set the iopub (PUB) port [default: random]")
170 170 stdin_port = Int(0, config=True,
171 171 help="set the stdin (DEALER) port [default: random]")
172 172 connection_file = Unicode('', config=True,
173 173 help="""JSON file in which to store connection info [default: kernel-<pid>.json]
174 174
175 175 This file will contain the IP, ports, and authentication key needed to connect
176 176 clients to this kernel. By default, this file will be created in the security-dir
177 177 of the current profile, but can be specified by absolute path.
178 178 """)
179 179 def _connection_file_default(self):
180 180 return 'kernel-%i.json' % os.getpid()
181 181
182 182 existing = CUnicode('', config=True,
183 183 help="""Connect to an already running kernel""")
184 184
185 185 confirm_exit = CBool(True, config=True,
186 186 help="""
187 187 Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
188 188 to force a direct exit without any confirmation.""",
189 189 )
190 190
191 191
192 192 def build_kernel_argv(self, argv=None):
193 193 """build argv to be passed to kernel subprocess"""
194 194 if argv is None:
195 195 argv = sys.argv[1:]
196 196 self.kernel_argv = swallow_argv(argv, self.frontend_aliases, self.frontend_flags)
197 197 # kernel should inherit default config file from frontend
198 198 self.kernel_argv.append("--IPKernelApp.parent_appname='%s'" % self.name)
199 199
200 200 def init_connection_file(self):
201 201 """find the connection file, and load the info if found.
202 202
203 203 The current working directory and the current profile's security
204 204 directory will be searched for the file if it is not given by
205 205 absolute path.
206 206
207 207 When attempting to connect to an existing kernel and the `--existing`
208 208 argument does not match an existing file, it will be interpreted as a
209 209 fileglob, and the matching file in the current profile's security dir
210 210 with the latest access time will be used.
211 211
212 212 After this method is called, self.connection_file contains the *full path*
213 213 to the connection file, never just its name.
214 214 """
215 215 if self.existing:
216 216 try:
217 217 cf = find_connection_file(self.existing)
218 218 except Exception:
219 219 self.log.critical("Could not find existing kernel connection file %s", self.existing)
220 220 self.exit(1)
221 221 self.log.info("Connecting to existing kernel: %s" % cf)
222 222 self.connection_file = cf
223 223 else:
224 224 # not existing, check if we are going to write the file
225 225 # and ensure that self.connection_file is a full path, not just the shortname
226 226 try:
227 227 cf = find_connection_file(self.connection_file)
228 228 except Exception:
229 229 # file might not exist
230 230 if self.connection_file == os.path.basename(self.connection_file):
231 231 # just shortname, put it in security dir
232 232 cf = os.path.join(self.profile_dir.security_dir, self.connection_file)
233 233 else:
234 234 cf = self.connection_file
235 235 self.connection_file = cf
236 236
237 237 # should load_connection_file only be used for existing?
238 238 # as it is now, this allows reusing ports if an existing
239 239 # file is requested
240 240 try:
241 241 self.load_connection_file()
242 242 except Exception:
243 243 self.log.error("Failed to load connection file: %r", self.connection_file, exc_info=True)
244 244 self.exit(1)
245 245
246 246 def load_connection_file(self):
247 247 """load ip/port/hmac config from JSON connection file"""
248 248 # this is identical to IPKernelApp.load_connection_file
249 249 # perhaps it can be centralized somewhere?
250 250 try:
251 251 fname = filefind(self.connection_file, ['.', self.profile_dir.security_dir])
252 252 except IOError:
253 253 self.log.debug("Connection File not found: %s", self.connection_file)
254 254 return
255 255 self.log.debug(u"Loading connection file %s", fname)
256 256 with open(fname) as f:
257 257 cfg = json.load(f)
258 258
259 259 self.config.KernelManager.transport = cfg.get('transport', 'tcp')
260 260 self.config.KernelManager.ip = cfg.get('ip', LOCALHOST)
261 261
262 262 for channel in ('hb', 'shell', 'iopub', 'stdin'):
263 263 name = channel + '_port'
264 264 if getattr(self, name) == 0 and name in cfg:
265 265 # not overridden by config or cl_args
266 266 setattr(self, name, cfg[name])
267 267 if 'key' in cfg:
268 268 self.config.Session.key = str_to_bytes(cfg['key'])
269 269
270 270 def init_ssh(self):
271 271 """set up ssh tunnels, if needed."""
272 272 if not self.existing or (not self.sshserver and not self.sshkey):
273 273 return
274 274
275 275 self.load_connection_file()
276 276
277 277 transport = self.config.KernelManager.transport
278 278 ip = self.config.KernelManager.ip
279 279
280 280 if transport != 'tcp':
281 281 self.log.error("Can only use ssh tunnels with TCP sockets, not %s", transport)
282 282 sys.exit(-1)
283 283
284 284 if self.sshkey and not self.sshserver:
285 285 # specifying just the key implies that we are connecting directly
286 286 self.sshserver = ip
287 287 ip = LOCALHOST
288 288
289 289 # build connection dict for tunnels:
290 290 info = dict(ip=ip,
291 291 shell_port=self.shell_port,
292 292 iopub_port=self.iopub_port,
293 293 stdin_port=self.stdin_port,
294 294 hb_port=self.hb_port
295 295 )
296 296
297 297 self.log.info("Forwarding connections to %s via %s"%(ip, self.sshserver))
298 298
299 299 # tunnels return a new set of ports, which will be on localhost:
300 300 self.config.KernelManager.ip = LOCALHOST
301 301 try:
302 302 newports = tunnel_to_kernel(info, self.sshserver, self.sshkey)
303 303 except:
304 304 # even catch KeyboardInterrupt
305 305 self.log.error("Could not setup tunnels", exc_info=True)
306 306 self.exit(1)
307 307
308 308 self.shell_port, self.iopub_port, self.stdin_port, self.hb_port = newports
309 309
310 310 cf = self.connection_file
311 311 base,ext = os.path.splitext(cf)
312 312 base = os.path.basename(base)
313 313 self.connection_file = os.path.basename(base)+'-ssh'+ext
314 314 self.log.critical("To connect another client via this tunnel, use:")
315 315 self.log.critical("--existing %s" % self.connection_file)
316 316
317 317 def _new_connection_file(self):
318 318 cf = ''
319 319 while not cf:
320 320 # we don't need a 128b id to distinguish kernels, use more readable
321 321 # 48b node segment (12 hex chars). Users running more than 32k simultaneous
322 322 # kernels can subclass.
323 323 ident = str(uuid.uuid4()).split('-')[-1]
324 324 cf = os.path.join(self.profile_dir.security_dir, 'kernel-%s.json' % ident)
325 325 # only keep if it's actually new. Protect against unlikely collision
326 326 # in 48b random search space
327 327 cf = cf if not os.path.exists(cf) else ''
328 328 return cf
329 329
330 330 def init_kernel_manager(self):
331 331 # Don't let Qt or ZMQ swallow KeyboardInterupts.
332 332 if self.existing:
333 333 self.kernel_manager = None
334 334 return
335 335 signal.signal(signal.SIGINT, signal.SIG_DFL)
336 336
337 337 # Create a KernelManager and start a kernel.
338 338 self.kernel_manager = self.kernel_manager_class(
339 339 shell_port=self.shell_port,
340 340 iopub_port=self.iopub_port,
341 341 stdin_port=self.stdin_port,
342 342 hb_port=self.hb_port,
343 343 connection_file=self.connection_file,
344 344 config=self.config,
345 345 )
346 346 self.kernel_manager.client_factory = self.kernel_client_class
347 347 self.kernel_manager.start_kernel(extra_arguments=self.kernel_argv)
348 348 atexit.register(self.kernel_manager.cleanup_ipc_files)
349 349
350 350 if self.sshserver:
351 351 # ssh, write new connection file
352 352 self.kernel_manager.write_connection_file()
353 353
354 354 # in case KM defaults / ssh writing changes things:
355 355 km = self.kernel_manager
356 356 self.shell_port=km.shell_port
357 357 self.iopub_port=km.iopub_port
358 358 self.stdin_port=km.stdin_port
359 359 self.hb_port=km.hb_port
360 360 self.connection_file = km.connection_file
361 361
362 362 atexit.register(self.kernel_manager.cleanup_connection_file)
363 363
364 364 def init_kernel_client(self):
365 365 if self.kernel_manager is not None:
366 366 self.kernel_client = self.kernel_manager.client()
367 367 else:
368 368 self.kernel_client = self.kernel_client_class(
369 369 shell_port=self.shell_port,
370 370 iopub_port=self.iopub_port,
371 371 stdin_port=self.stdin_port,
372 372 hb_port=self.hb_port,
373 373 connection_file=self.connection_file,
374 374 config=self.config,
375 375 )
376 376
377 377 self.kernel_client.start_channels()
378 378
379 379
380 380
381 381 def initialize(self, argv=None):
382 382 """
383 383 Classes which mix this class in should call:
384 384 IPythonConsoleApp.initialize(self,argv)
385 385 """
386 386 self.init_connection_file()
387 387 default_secure(self.config)
388 388 self.init_ssh()
389 389 self.init_kernel_manager()
390 390 self.init_kernel_client()
391 391
@@ -1,306 +1,306 b''
1 1 # encoding: utf-8
2 2 """
3 3 An application for managing IPython profiles.
4 4
5 5 To be invoked as the `ipython profile` subcommand.
6 6
7 7 Authors:
8 8
9 9 * Min RK
10 10
11 11 """
12 12
13 13 #-----------------------------------------------------------------------------
14 14 # Copyright (C) 2008-2011 The IPython Development Team
15 15 #
16 16 # Distributed under the terms of the BSD License. The full license is in
17 17 # the file COPYING, distributed as part of this software.
18 18 #-----------------------------------------------------------------------------
19 19
20 20 #-----------------------------------------------------------------------------
21 21 # Imports
22 22 #-----------------------------------------------------------------------------
23 23
24 24 import os
25 25
26 26 from IPython.config.application import Application
27 27 from IPython.core.application import (
28 28 BaseIPythonApplication, base_flags
29 29 )
30 30 from IPython.core.profiledir import ProfileDir
31 31 from IPython.utils.path import get_ipython_dir, get_ipython_package_dir
32 32 from IPython.utils.traitlets import Unicode, Bool, Dict
33 33
34 34 #-----------------------------------------------------------------------------
35 35 # Constants
36 36 #-----------------------------------------------------------------------------
37 37
38 38 create_help = """Create an IPython profile by name
39 39
40 40 Create an ipython profile directory by its name or
41 41 profile directory path. Profile directories contain
42 42 configuration, log and security related files and are named
43 43 using the convention 'profile_<name>'. By default they are
44 44 located in your ipython directory. Once created, you will
45 45 can edit the configuration files in the profile
46 46 directory to configure IPython. Most users will create a
47 47 profile directory by name,
48 48 `ipython profile create myprofile`, which will put the directory
49 49 in `<ipython_dir>/profile_myprofile`.
50 50 """
51 51 list_help = """List available IPython profiles
52 52
53 53 List all available profiles, by profile location, that can
54 54 be found in the current working directly or in the ipython
55 55 directory. Profile directories are named using the convention
56 56 'profile_<profile>'.
57 57 """
58 58 profile_help = """Manage IPython profiles
59 59
60 60 Profile directories contain
61 61 configuration, log and security related files and are named
62 62 using the convention 'profile_<name>'. By default they are
63 63 located in your ipython directory. You can create profiles
64 64 with `ipython profile create <name>`, or see the profiles you
65 65 already have with `ipython profile list`
66 66
67 67 To get started configuring IPython, simply do:
68 68
69 69 $> ipython profile create
70 70
71 71 and IPython will create the default profile in <ipython_dir>/profile_default,
72 72 where you can edit ipython_config.py to start configuring IPython.
73 73
74 74 """
75 75
76 76 _list_examples = "ipython profile list # list all profiles"
77 77
78 78 _create_examples = """
79 79 ipython profile create foo # create profile foo w/ default config files
80 80 ipython profile create foo --reset # restage default config files over current
81 81 ipython profile create foo --parallel # also stage parallel config files
82 82 """
83 83
84 84 _main_examples = """
85 85 ipython profile create -h # show the help string for the create subcommand
86 86 ipython profile list -h # show the help string for the list subcommand
87 87
88 88 ipython locate profile foo # print the path to the directory for profile 'foo'
89 89 """
90 90
91 91 #-----------------------------------------------------------------------------
92 92 # Profile Application Class (for `ipython profile` subcommand)
93 93 #-----------------------------------------------------------------------------
94 94
95 95
96 96 def list_profiles_in(path):
97 97 """list profiles in a given root directory"""
98 98 files = os.listdir(path)
99 99 profiles = []
100 100 for f in files:
101 101 full_path = os.path.join(path, f)
102 102 if os.path.isdir(full_path) and f.startswith('profile_'):
103 103 profiles.append(f.split('_',1)[-1])
104 104 return profiles
105 105
106 106
107 107 def list_bundled_profiles():
108 108 """list profiles that are bundled with IPython."""
109 109 path = os.path.join(get_ipython_package_dir(), u'config', u'profile')
110 110 files = os.listdir(path)
111 111 profiles = []
112 112 for profile in files:
113 113 full_path = os.path.join(path, profile)
114 114 if os.path.isdir(full_path) and profile != "__pycache__":
115 115 profiles.append(profile)
116 116 return profiles
117 117
118 118
119 119 class ProfileLocate(BaseIPythonApplication):
120 120 description = """print the path an IPython profile dir"""
121 121
122 122 def parse_command_line(self, argv=None):
123 123 super(ProfileLocate, self).parse_command_line(argv)
124 124 if self.extra_args:
125 125 self.profile = self.extra_args[0]
126 126
127 127 def start(self):
128 128 print self.profile_dir.location
129 129
130 130
131 131 class ProfileList(Application):
132 132 name = u'ipython-profile'
133 133 description = list_help
134 134 examples = _list_examples
135 135
136 136 aliases = Dict({
137 137 'ipython-dir' : 'ProfileList.ipython_dir',
138 138 'log-level' : 'Application.log_level',
139 139 })
140 140 flags = Dict(dict(
141 141 debug = ({'Application' : {'log_level' : 0}},
142 142 "Set Application.log_level to 0, maximizing log output."
143 143 )
144 144 ))
145 145
146 146 ipython_dir = Unicode(get_ipython_dir(), config=True,
147 147 help="""
148 148 The name of the IPython directory. This directory is used for logging
149 149 configuration (through profiles), history storage, etc. The default
150 150 is usually $HOME/.ipython. This options can also be specified through
151 151 the environment variable IPYTHONDIR.
152 152 """
153 153 )
154 154
155 155
156 156 def _print_profiles(self, profiles):
157 157 """print list of profiles, indented."""
158 158 for profile in profiles:
159 159 print ' %s' % profile
160 160
161 161 def list_profile_dirs(self):
162 162 profiles = list_bundled_profiles()
163 163 if profiles:
164 164 print
165 165 print "Available profiles in IPython:"
166 166 self._print_profiles(profiles)
167 167 print
168 168 print " The first request for a bundled profile will copy it"
169 169 print " into your IPython directory (%s)," % self.ipython_dir
170 170 print " where you can customize it."
171 171
172 172 profiles = list_profiles_in(self.ipython_dir)
173 173 if profiles:
174 174 print
175 175 print "Available profiles in %s:" % self.ipython_dir
176 176 self._print_profiles(profiles)
177 177
178 178 profiles = list_profiles_in(os.getcwdu())
179 179 if profiles:
180 180 print
181 181 print "Available profiles in current directory (%s):" % os.getcwdu()
182 182 self._print_profiles(profiles)
183 183
184 184 print
185 185 print "To use any of the above profiles, start IPython with:"
186 186 print " ipython --profile=<name>"
187 187 print
188 188
189 189 def start(self):
190 190 self.list_profile_dirs()
191 191
192 192
193 193 create_flags = {}
194 194 create_flags.update(base_flags)
195 195 # don't include '--init' flag, which implies running profile create in other apps
196 196 create_flags.pop('init')
197 197 create_flags['reset'] = ({'ProfileCreate': {'overwrite' : True}},
198 198 "reset config files in this profile to the defaults.")
199 199 create_flags['parallel'] = ({'ProfileCreate': {'parallel' : True}},
200 200 "Include the config files for parallel "
201 201 "computing apps (ipengine, ipcontroller, etc.)")
202 202
203 203
204 204 class ProfileCreate(BaseIPythonApplication):
205 205 name = u'ipython-profile'
206 206 description = create_help
207 207 examples = _create_examples
208 208 auto_create = Bool(True, config=False)
209 209
210 210 def _copy_config_files_default(self):
211 211 return True
212 212
213 213 parallel = Bool(False, config=True,
214 214 help="whether to include parallel computing config files")
215 215 def _parallel_changed(self, name, old, new):
216 216 parallel_files = [ 'ipcontroller_config.py',
217 217 'ipengine_config.py',
218 218 'ipcluster_config.py'
219 219 ]
220 220 if new:
221 221 for cf in parallel_files:
222 222 self.config_files.append(cf)
223 223 else:
224 224 for cf in parallel_files:
225 225 if cf in self.config_files:
226 226 self.config_files.remove(cf)
227 227
228 228 def parse_command_line(self, argv):
229 229 super(ProfileCreate, self).parse_command_line(argv)
230 230 # accept positional arg as profile name
231 231 if self.extra_args:
232 232 self.profile = self.extra_args[0]
233 233
234 234 flags = Dict(create_flags)
235 235
236 236 classes = [ProfileDir]
237 237
238 238 def init_config_files(self):
239 239 super(ProfileCreate, self).init_config_files()
240 240 # use local imports, since these classes may import from here
241 from IPython.frontend.terminal.ipapp import TerminalIPythonApp
241 from IPython.terminal.ipapp import TerminalIPythonApp
242 242 apps = [TerminalIPythonApp]
243 243 try:
244 from IPython.frontend.qt.console.qtconsoleapp import IPythonQtConsoleApp
244 from IPython.qt.console.qtconsoleapp import IPythonQtConsoleApp
245 245 except Exception:
246 246 # this should be ImportError, but under weird circumstances
247 247 # this might be an AttributeError, or possibly others
248 248 # in any case, nothing should cause the profile creation to crash.
249 249 pass
250 250 else:
251 251 apps.append(IPythonQtConsoleApp)
252 252 try:
253 from IPython.frontend.html.notebook.notebookapp import NotebookApp
253 from IPython.html.notebookapp import NotebookApp
254 254 except ImportError:
255 255 pass
256 256 except Exception:
257 257 self.log.debug('Unexpected error when importing NotebookApp',
258 258 exc_info=True
259 259 )
260 260 else:
261 261 apps.append(NotebookApp)
262 262 if self.parallel:
263 263 from IPython.parallel.apps.ipcontrollerapp import IPControllerApp
264 264 from IPython.parallel.apps.ipengineapp import IPEngineApp
265 265 from IPython.parallel.apps.ipclusterapp import IPClusterStart
266 266 from IPython.parallel.apps.iploggerapp import IPLoggerApp
267 267 apps.extend([
268 268 IPControllerApp,
269 269 IPEngineApp,
270 270 IPClusterStart,
271 271 IPLoggerApp,
272 272 ])
273 273 for App in apps:
274 274 app = App()
275 275 app.config.update(self.config)
276 276 app.log = self.log
277 277 app.overwrite = self.overwrite
278 278 app.copy_config_files=True
279 279 app.profile = self.profile
280 280 app.init_profile_dir()
281 281 app.init_config_files()
282 282
283 283 def stage_default_config_file(self):
284 284 pass
285 285
286 286
287 287 class ProfileApp(Application):
288 288 name = u'ipython-profile'
289 289 description = profile_help
290 290 examples = _main_examples
291 291
292 292 subcommands = Dict(dict(
293 293 create = (ProfileCreate, ProfileCreate.description.splitlines()[0]),
294 294 list = (ProfileList, ProfileList.description.splitlines()[0]),
295 295 ))
296 296
297 297 def start(self):
298 298 if self.subapp is None:
299 299 print "No subcommand specified. Must specify one of: %s"%(self.subcommands.keys())
300 300 print
301 301 self.print_description()
302 302 self.print_subcommands()
303 303 self.exit(1)
304 304 else:
305 305 return self.subapp.start()
306 306
@@ -1,38 +1,38 b''
1 1 #-----------------------------------------------------------------------------
2 2 # Copyright (C) 2010-2011 The IPython Development Team.
3 3 #
4 4 # Distributed under the terms of the BSD License.
5 5 #
6 6 # The full license is in the file COPYING.txt, distributed with this software.
7 7 #-----------------------------------------------------------------------------
8 8 import os
9 9
10 10 import nose.tools as nt
11 11
12 12 from IPython.core import display
13 13 from IPython.utils import path as ipath
14 14
15 15 def test_image_size():
16 16 """Simple test for display.Image(args, width=x,height=y)"""
17 17 thisurl = 'http://www.google.fr/images/srpr/logo3w.png'
18 18 img = display.Image(url=thisurl, width=200, height=200)
19 19 nt.assert_equal(u'<img src="%s" width="200" height="200"/>' % (thisurl), img._repr_html_())
20 20 img = display.Image(url=thisurl, width=200)
21 21 nt.assert_equal(u'<img src="%s" width="200"/>' % (thisurl), img._repr_html_())
22 22 img = display.Image(url=thisurl)
23 23 nt.assert_equal(u'<img src="%s"/>' % (thisurl), img._repr_html_())
24 24
25 25 def test_image_filename_defaults():
26 26 '''test format constraint, and validity of jpeg and png'''
27 27 tpath = ipath.get_ipython_package_dir()
28 28 nt.assert_raises(ValueError, display.Image, filename=os.path.join(tpath, 'testing/tests/badformat.gif'),
29 29 embed=True)
30 30 nt.assert_raises(ValueError, display.Image)
31 31 nt.assert_raises(ValueError, display.Image, data='this is not an image', format='badformat', embed=True)
32 imgfile = os.path.join(tpath, 'frontend/html/notebook/static/base/images/ipynblogo.png')
32 imgfile = os.path.join(tpath, 'html/static/base/images/ipynblogo.png')
33 33 img = display.Image(filename=imgfile)
34 34 nt.assert_equal('png', img.format)
35 35 nt.assert_is_not_none(img._repr_png_())
36 36 img = display.Image(filename=os.path.join(tpath, 'testing/tests/logo.jpg'), embed=False)
37 37 nt.assert_equal('jpeg', img.format)
38 38 nt.assert_is_none(img._repr_jpeg_())
@@ -1,73 +1,73 b''
1 1 # IPython Notebook development
2 2
3 3 ## Development dependencies
4 4
5 5 Developers of the IPython Notebook will need to install the following tools:
6 6
7 7 * fabric
8 8 * node.js
9 9 * less (`npm install -g less`)
10 10 * bower (`npm install -g bower`)
11 11
12 12 ## Components
13 13
14 14 We are moving to a model where our JavaScript dependencies are managed using
15 15 [bower](http://bower.io/). These packages are installed in `static/components`
16 16 and commited into our git repo. Our dependencies are described in the file
17 17 `static/bower.json`. To update our bower packages, run `fab components` in this
18 18 directory.
19 19
20 20 Because CodeMirror does not use proper semantic versioning for its GitHub tags,
21 21 we maintain our own fork of CodeMirror that is used with bower. This fork should
22 22 track the upstream CodeMirror exactly; the only difference is that we are adding
23 23 semantic versioned tags to our repo.
24 24
25 25 ## less
26 26
27 27 If you edit our `.less` files you will need to run the less compiler to build
28 28 our minified css files. This can be done by running `fab css` from this directory.
29 29
30 30 ## JavaScript Documentation
31 31
32 32
33 33 How to Build/ view the doc for JavaScript. JavaScript documentation should follow a
34 34 style close to JSDoc one, so you should be able to build them with your favorite
35 35 documentation builder. Still the documentation comment are mainly written to be read
36 36 with YUI doc. You can either build a static version, or start a YUIdoc server that
37 37 will live update the doc at every page request.
38 38
39 39
40 40
41 41 To do so, you will need to install YUIdoc.
42 42
43 43 ### Install NodeJS
44 44
45 45 Node is a browser less javascript interpreter. To install it please refer to
46 46 the documentation for your platform. Install also NPM (node package manager) if
47 47 it does not come bundled with it.
48 48
49 49 ### Get YUIdoc
50 50
51 51 npm does by default install package in `./node_modules` instead of doing a
52 52 system wide install. I'll leave you to yuidoc docs if you want to make a system
53 53 wide install.
54 54
55 55 First, cd into js directory :
56 56 ```bash
57 cd IPython/frontend/html/notebook/static/js/
57 cd IPython/html/static/js/
58 58 # install yuidoc
59 59 npm install yuidocjs
60 60 ```
61 61
62 62
63 63 ### Run YUIdoc server
64 64
65 From IPython/frontend/html/notebook/static/js/
65 From IPython/html/static/js/
66 66 ```bash
67 67 # run yuidoc for install dir
68 68 ./node_modules/yuidocjs/lib/cli.js --server .
69 69 ```
70 70
71 71 Follow the instruction and the documentation should be available on localhost:3000
72 72
73 Omitting `--server` will build a static version in the `out` folder by default. No newline at end of file
73 Omitting `--server` will build a static version in the `out` folder by default.
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/__init__.py to IPython/html/__init__.py
1 NO CONTENT: file renamed from IPython/deathrow/__init__.py to IPython/html/auth/__init__.py
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/auth/login.py to IPython/html/auth/login.py
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/auth/logout.py to IPython/html/auth/logout.py
1 NO CONTENT: file renamed from IPython/deathrow/gui/__init__.py to IPython/html/base/__init__.py
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/base/handlers.py to IPython/html/base/handlers.py
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/base/zmqhandlers.py to IPython/html/base/zmqhandlers.py
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/fabfile.py to IPython/html/fabfile.py
1 NO CONTENT: file renamed from IPython/deathrow/gui/wx/__init__.py to IPython/html/notebook/__init__.py
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/notebook/handlers.py to IPython/html/notebook/handlers.py
@@ -1,753 +1,753 b''
1 1 # coding: utf-8
2 2 """A tornado based IPython notebook server.
3 3
4 4 Authors:
5 5
6 6 * Brian Granger
7 7 """
8 8 #-----------------------------------------------------------------------------
9 9 # Copyright (C) 2013 The IPython Development Team
10 10 #
11 11 # Distributed under the terms of the BSD License. The full license is in
12 12 # the file COPYING, distributed as part of this software.
13 13 #-----------------------------------------------------------------------------
14 14
15 15 #-----------------------------------------------------------------------------
16 16 # Imports
17 17 #-----------------------------------------------------------------------------
18 18
19 19 # stdlib
20 20 import errno
21 21 import logging
22 22 import os
23 23 import random
24 24 import select
25 25 import signal
26 26 import socket
27 27 import sys
28 28 import threading
29 29 import time
30 30 import uuid
31 31 import webbrowser
32 32
33 33
34 34 # Third party
35 35 # check for pyzmq 2.1.11
36 36 from IPython.utils.zmqrelated import check_for_zmq
37 check_for_zmq('2.1.11', 'IPython.frontend.html.notebook')
37 check_for_zmq('2.1.11', 'IPython.html')
38 38
39 39 import zmq
40 40 from jinja2 import Environment, FileSystemLoader
41 41
42 42 # Install the pyzmq ioloop. This has to be done before anything else from
43 43 # tornado is imported.
44 44 from zmq.eventloop import ioloop
45 45 ioloop.install()
46 46
47 47 # check for tornado 2.1.0
48 48 msg = "The IPython Notebook requires tornado >= 2.1.0"
49 49 try:
50 50 import tornado
51 51 except ImportError:
52 52 raise ImportError(msg)
53 53 try:
54 54 version_info = tornado.version_info
55 55 except AttributeError:
56 56 raise ImportError(msg + ", but you have < 1.1.0")
57 57 if version_info < (2,1,0):
58 58 raise ImportError(msg + ", but you have %s" % tornado.version)
59 59
60 60 from tornado import httpserver
61 61 from tornado import web
62 62
63 63 # Our own libraries
64 from IPython.frontend.html.notebook import DEFAULT_STATIC_FILES_PATH
64 from IPython.html import DEFAULT_STATIC_FILES_PATH
65 65
66 66 from .services.kernels.kernelmanager import MappingKernelManager
67 67 from .services.notebooks.nbmanager import NotebookManager
68 68 from .services.notebooks.filenbmanager import FileNotebookManager
69 69 from .services.clusters.clustermanager import ClusterManager
70 70
71 71 from .base.handlers import AuthenticatedFileHandler, FileFindHandler
72 72
73 73 from IPython.config.application import catch_config_error, boolean_flag
74 74 from IPython.core.application import BaseIPythonApplication
75 from IPython.frontend.consoleapp import IPythonConsoleApp
75 from IPython.consoleapp import IPythonConsoleApp
76 76 from IPython.kernel import swallow_argv
77 77 from IPython.kernel.zmq.session import default_secure
78 78 from IPython.kernel.zmq.kernelapp import (
79 79 kernel_flags,
80 80 kernel_aliases,
81 81 )
82 82 from IPython.utils.importstring import import_item
83 83 from IPython.utils.localinterfaces import LOCALHOST
84 84 from IPython.utils import submodule
85 85 from IPython.utils.traitlets import (
86 86 Dict, Unicode, Integer, List, Bool, Bytes,
87 87 DottedObjectName
88 88 )
89 89 from IPython.utils import py3compat
90 90 from IPython.utils.path import filefind
91 91
92 92 from .utils import url_path_join
93 93
94 94 #-----------------------------------------------------------------------------
95 95 # Module globals
96 96 #-----------------------------------------------------------------------------
97 97
98 98 _examples = """
99 99 ipython notebook # start the notebook
100 100 ipython notebook --profile=sympy # use the sympy profile
101 101 ipython notebook --pylab=inline # pylab in inline plotting mode
102 102 ipython notebook --certfile=mycert.pem # use SSL/TLS certificate
103 103 ipython notebook --port=5555 --ip=* # Listen on port 5555, all interfaces
104 104 """
105 105
106 106 #-----------------------------------------------------------------------------
107 107 # Helper functions
108 108 #-----------------------------------------------------------------------------
109 109
110 110 def random_ports(port, n):
111 111 """Generate a list of n random ports near the given port.
112 112
113 113 The first 5 ports will be sequential, and the remaining n-5 will be
114 114 randomly selected in the range [port-2*n, port+2*n].
115 115 """
116 116 for i in range(min(5, n)):
117 117 yield port + i
118 118 for i in range(n-5):
119 119 yield port + random.randint(-2*n, 2*n)
120 120
121 121 def load_handlers(name):
122 122 """Load the (URL pattern, handler) tuples for each component."""
123 name = 'IPython.frontend.html.notebook.' + name
123 name = 'IPython.html.' + name
124 124 mod = __import__(name, fromlist=['default_handlers'])
125 125 return mod.default_handlers
126 126
127 127 #-----------------------------------------------------------------------------
128 128 # The Tornado web application
129 129 #-----------------------------------------------------------------------------
130 130
131 131 class NotebookWebApplication(web.Application):
132 132
133 133 def __init__(self, ipython_app, kernel_manager, notebook_manager,
134 134 cluster_manager, log,
135 135 base_project_url, settings_overrides):
136 136
137 137 settings = self.init_settings(
138 138 ipython_app, kernel_manager, notebook_manager, cluster_manager,
139 139 log, base_project_url, settings_overrides)
140 140 handlers = self.init_handlers(settings)
141 141
142 142 super(NotebookWebApplication, self).__init__(handlers, **settings)
143 143
144 144 def init_settings(self, ipython_app, kernel_manager, notebook_manager,
145 145 cluster_manager, log,
146 146 base_project_url, settings_overrides):
147 147 # Python < 2.6.5 doesn't accept unicode keys in f(**kwargs), and
148 148 # base_project_url will always be unicode, which will in turn
149 149 # make the patterns unicode, and ultimately result in unicode
150 150 # keys in kwargs to handler._execute(**kwargs) in tornado.
151 151 # This enforces that base_project_url be ascii in that situation.
152 152 #
153 153 # Note that the URLs these patterns check against are escaped,
154 154 # and thus guaranteed to be ASCII: 'héllo' is really 'h%C3%A9llo'.
155 155 base_project_url = py3compat.unicode_to_str(base_project_url, 'ascii')
156 156 template_path = os.path.join(os.path.dirname(__file__), "templates")
157 157 settings = dict(
158 158 # basics
159 159 base_project_url=base_project_url,
160 160 base_kernel_url=ipython_app.base_kernel_url,
161 161 template_path=template_path,
162 162 static_path=ipython_app.static_file_path,
163 163 static_handler_class = FileFindHandler,
164 164 static_url_prefix = url_path_join(base_project_url,'/static/'),
165 165
166 166 # authentication
167 167 cookie_secret=ipython_app.cookie_secret,
168 168 login_url=url_path_join(base_project_url,'/login'),
169 169 read_only=ipython_app.read_only,
170 170 password=ipython_app.password,
171 171
172 172 # managers
173 173 kernel_manager=kernel_manager,
174 174 notebook_manager=notebook_manager,
175 175 cluster_manager=cluster_manager,
176 176
177 177 # IPython stuff
178 178 mathjax_url=ipython_app.mathjax_url,
179 179 max_msg_size=ipython_app.max_msg_size,
180 180 config=ipython_app.config,
181 181 use_less=ipython_app.use_less,
182 182 jinja2_env=Environment(loader=FileSystemLoader(template_path)),
183 183 )
184 184
185 185 # allow custom overrides for the tornado web app.
186 186 settings.update(settings_overrides)
187 187 return settings
188 188
189 189 def init_handlers(self, settings):
190 190 # Load the (URL pattern, handler) tuples for each component.
191 191 handlers = []
192 192 handlers.extend(load_handlers('base.handlers'))
193 193 handlers.extend(load_handlers('tree.handlers'))
194 194 handlers.extend(load_handlers('auth.login'))
195 195 handlers.extend(load_handlers('auth.logout'))
196 196 handlers.extend(load_handlers('notebook.handlers'))
197 197 handlers.extend(load_handlers('services.kernels.handlers'))
198 198 handlers.extend(load_handlers('services.notebooks.handlers'))
199 199 handlers.extend(load_handlers('services.clusters.handlers'))
200 200 handlers.extend([
201 201 (r"/files/(.*)", AuthenticatedFileHandler, {'path' : settings['notebook_manager'].notebook_dir}),
202 202 ])
203 203 # prepend base_project_url onto the patterns that we match
204 204 new_handlers = []
205 205 for handler in handlers:
206 206 pattern = url_path_join(settings['base_project_url'], handler[0])
207 207 new_handler = tuple([pattern] + list(handler[1:]))
208 208 new_handlers.append(new_handler)
209 209 return new_handlers
210 210
211 211
212 212
213 213 #-----------------------------------------------------------------------------
214 214 # Aliases and Flags
215 215 #-----------------------------------------------------------------------------
216 216
217 217 flags = dict(kernel_flags)
218 218 flags['no-browser']=(
219 219 {'NotebookApp' : {'open_browser' : False}},
220 220 "Don't open the notebook in a browser after startup."
221 221 )
222 222 flags['no-mathjax']=(
223 223 {'NotebookApp' : {'enable_mathjax' : False}},
224 224 """Disable MathJax
225 225
226 226 MathJax is the javascript library IPython uses to render math/LaTeX. It is
227 227 very large, so you may want to disable it if you have a slow internet
228 228 connection, or for offline use of the notebook.
229 229
230 230 When disabled, equations etc. will appear as their untransformed TeX source.
231 231 """
232 232 )
233 233 flags['read-only'] = (
234 234 {'NotebookApp' : {'read_only' : True}},
235 235 """Allow read-only access to notebooks.
236 236
237 237 When using a password to protect the notebook server, this flag
238 238 allows unauthenticated clients to view the notebook list, and
239 239 individual notebooks, but not edit them, start kernels, or run
240 240 code.
241 241
242 242 If no password is set, the server will be entirely read-only.
243 243 """
244 244 )
245 245
246 246 # Add notebook manager flags
247 247 flags.update(boolean_flag('script', 'FileNotebookManager.save_script',
248 248 'Auto-save a .py script everytime the .ipynb notebook is saved',
249 249 'Do not auto-save .py scripts for every notebook'))
250 250
251 251 # the flags that are specific to the frontend
252 252 # these must be scrubbed before being passed to the kernel,
253 253 # or it will raise an error on unrecognized flags
254 254 notebook_flags = ['no-browser', 'no-mathjax', 'read-only', 'script', 'no-script']
255 255
256 256 aliases = dict(kernel_aliases)
257 257
258 258 aliases.update({
259 259 'ip': 'NotebookApp.ip',
260 260 'port': 'NotebookApp.port',
261 261 'port-retries': 'NotebookApp.port_retries',
262 262 'transport': 'KernelManager.transport',
263 263 'keyfile': 'NotebookApp.keyfile',
264 264 'certfile': 'NotebookApp.certfile',
265 265 'notebook-dir': 'NotebookManager.notebook_dir',
266 266 'browser': 'NotebookApp.browser',
267 267 })
268 268
269 269 # remove ipkernel flags that are singletons, and don't make sense in
270 270 # multi-kernel evironment:
271 271 aliases.pop('f', None)
272 272
273 273 notebook_aliases = [u'port', u'port-retries', u'ip', u'keyfile', u'certfile',
274 274 u'notebook-dir']
275 275
276 276 #-----------------------------------------------------------------------------
277 277 # NotebookApp
278 278 #-----------------------------------------------------------------------------
279 279
280 280 class NotebookApp(BaseIPythonApplication):
281 281
282 282 name = 'ipython-notebook'
283 283 default_config_file_name='ipython_notebook_config.py'
284 284
285 285 description = """
286 286 The IPython HTML Notebook.
287 287
288 288 This launches a Tornado based HTML Notebook Server that serves up an
289 289 HTML5/Javascript Notebook client.
290 290 """
291 291 examples = _examples
292 292
293 293 classes = IPythonConsoleApp.classes + [MappingKernelManager, NotebookManager,
294 294 FileNotebookManager]
295 295 flags = Dict(flags)
296 296 aliases = Dict(aliases)
297 297
298 298 kernel_argv = List(Unicode)
299 299
300 300 max_msg_size = Integer(65536, config=True, help="""
301 301 The max raw message size accepted from the browser
302 302 over a WebSocket connection.
303 303 """)
304 304
305 305 def _log_level_default(self):
306 306 return logging.INFO
307 307
308 308 def _log_format_default(self):
309 309 """override default log format to include time"""
310 310 return u"%(asctime)s.%(msecs).03d [%(name)s]%(highlevel)s %(message)s"
311 311
312 312 # create requested profiles by default, if they don't exist:
313 313 auto_create = Bool(True)
314 314
315 315 # file to be opened in the notebook server
316 316 file_to_run = Unicode('')
317 317
318 318 # Network related information.
319 319
320 320 ip = Unicode(LOCALHOST, config=True,
321 321 help="The IP address the notebook server will listen on."
322 322 )
323 323
324 324 def _ip_changed(self, name, old, new):
325 325 if new == u'*': self.ip = u''
326 326
327 327 port = Integer(8888, config=True,
328 328 help="The port the notebook server will listen on."
329 329 )
330 330 port_retries = Integer(50, config=True,
331 331 help="The number of additional ports to try if the specified port is not available."
332 332 )
333 333
334 334 certfile = Unicode(u'', config=True,
335 335 help="""The full path to an SSL/TLS certificate file."""
336 336 )
337 337
338 338 keyfile = Unicode(u'', config=True,
339 339 help="""The full path to a private key file for usage with SSL/TLS."""
340 340 )
341 341
342 342 cookie_secret = Bytes(b'', config=True,
343 343 help="""The random bytes used to secure cookies.
344 344 By default this is a new random number every time you start the Notebook.
345 345 Set it to a value in a config file to enable logins to persist across server sessions.
346 346
347 347 Note: Cookie secrets should be kept private, do not share config files with
348 348 cookie_secret stored in plaintext (you can read the value from a file).
349 349 """
350 350 )
351 351 def _cookie_secret_default(self):
352 352 return os.urandom(1024)
353 353
354 354 password = Unicode(u'', config=True,
355 355 help="""Hashed password to use for web authentication.
356 356
357 357 To generate, type in a python/IPython shell:
358 358
359 359 from IPython.lib import passwd; passwd()
360 360
361 361 The string should be of the form type:salt:hashed-password.
362 362 """
363 363 )
364 364
365 365 open_browser = Bool(True, config=True,
366 366 help="""Whether to open in a browser after starting.
367 367 The specific browser used is platform dependent and
368 368 determined by the python standard library `webbrowser`
369 369 module, unless it is overridden using the --browser
370 370 (NotebookApp.browser) configuration option.
371 371 """)
372 372
373 373 browser = Unicode(u'', config=True,
374 374 help="""Specify what command to use to invoke a web
375 375 browser when opening the notebook. If not specified, the
376 376 default browser will be determined by the `webbrowser`
377 377 standard library module, which allows setting of the
378 378 BROWSER environment variable to override it.
379 379 """)
380 380
381 381 read_only = Bool(False, config=True,
382 382 help="Whether to prevent editing/execution of notebooks."
383 383 )
384 384
385 385 use_less = Bool(False, config=True,
386 386 help="""Wether to use Browser Side less-css parsing
387 387 instead of compiled css version in templates that allows
388 388 it. This is mainly convenient when working on the less
389 389 file to avoid a build step, or if user want to overwrite
390 390 some of the less variables without having to recompile
391 391 everything.
392 392
393 393 You will need to install the less.js component in the static directory
394 394 either in the source tree or in your profile folder.
395 395 """)
396 396
397 397 webapp_settings = Dict(config=True,
398 398 help="Supply overrides for the tornado.web.Application that the "
399 399 "IPython notebook uses.")
400 400
401 401 enable_mathjax = Bool(True, config=True,
402 402 help="""Whether to enable MathJax for typesetting math/TeX
403 403
404 404 MathJax is the javascript library IPython uses to render math/LaTeX. It is
405 405 very large, so you may want to disable it if you have a slow internet
406 406 connection, or for offline use of the notebook.
407 407
408 408 When disabled, equations etc. will appear as their untransformed TeX source.
409 409 """
410 410 )
411 411 def _enable_mathjax_changed(self, name, old, new):
412 412 """set mathjax url to empty if mathjax is disabled"""
413 413 if not new:
414 414 self.mathjax_url = u''
415 415
416 416 base_project_url = Unicode('/', config=True,
417 417 help='''The base URL for the notebook server.
418 418
419 419 Leading and trailing slashes can be omitted,
420 420 and will automatically be added.
421 421 ''')
422 422 def _base_project_url_changed(self, name, old, new):
423 423 if not new.startswith('/'):
424 424 self.base_project_url = '/'+new
425 425 elif not new.endswith('/'):
426 426 self.base_project_url = new+'/'
427 427
428 428 base_kernel_url = Unicode('/', config=True,
429 429 help='''The base URL for the kernel server
430 430
431 431 Leading and trailing slashes can be omitted,
432 432 and will automatically be added.
433 433 ''')
434 434 def _base_kernel_url_changed(self, name, old, new):
435 435 if not new.startswith('/'):
436 436 self.base_kernel_url = '/'+new
437 437 elif not new.endswith('/'):
438 438 self.base_kernel_url = new+'/'
439 439
440 440 websocket_url = Unicode("", config=True,
441 441 help="""The base URL for the websocket server,
442 442 if it differs from the HTTP server (hint: it almost certainly doesn't).
443 443
444 444 Should be in the form of an HTTP origin: ws[s]://hostname[:port]
445 445 """
446 446 )
447 447
448 448 extra_static_paths = List(Unicode, config=True,
449 449 help="""Extra paths to search for serving static files.
450 450
451 451 This allows adding javascript/css to be available from the notebook server machine,
452 452 or overriding individual files in the IPython"""
453 453 )
454 454 def _extra_static_paths_default(self):
455 455 return [os.path.join(self.profile_dir.location, 'static')]
456 456
457 457 @property
458 458 def static_file_path(self):
459 459 """return extra paths + the default location"""
460 460 return self.extra_static_paths + [DEFAULT_STATIC_FILES_PATH]
461 461
462 462 mathjax_url = Unicode("", config=True,
463 463 help="""The url for MathJax.js."""
464 464 )
465 465 def _mathjax_url_default(self):
466 466 if not self.enable_mathjax:
467 467 return u''
468 468 static_url_prefix = self.webapp_settings.get("static_url_prefix",
469 469 "/static/")
470 470 try:
471 471 mathjax = filefind(os.path.join('mathjax', 'MathJax.js'), self.static_file_path)
472 472 except IOError:
473 473 if self.certfile:
474 474 # HTTPS: load from Rackspace CDN, because SSL certificate requires it
475 475 base = u"https://c328740.ssl.cf1.rackcdn.com"
476 476 else:
477 477 base = u"http://cdn.mathjax.org"
478 478
479 479 url = base + u"/mathjax/latest/MathJax.js"
480 480 self.log.info("Using MathJax from CDN: %s", url)
481 481 return url
482 482 else:
483 483 self.log.info("Using local MathJax from %s" % mathjax)
484 484 return static_url_prefix+u"mathjax/MathJax.js"
485 485
486 486 def _mathjax_url_changed(self, name, old, new):
487 487 if new and not self.enable_mathjax:
488 488 # enable_mathjax=False overrides mathjax_url
489 489 self.mathjax_url = u''
490 490 else:
491 491 self.log.info("Using MathJax: %s", new)
492 492
493 notebook_manager_class = DottedObjectName('IPython.frontend.html.notebook.services.notebooks.filenbmanager.FileNotebookManager',
493 notebook_manager_class = DottedObjectName('IPython.html.services.notebooks.filenbmanager.FileNotebookManager',
494 494 config=True,
495 495 help='The notebook manager class to use.')
496 496
497 497 trust_xheaders = Bool(False, config=True,
498 498 help=("Whether to trust or not X-Scheme/X-Forwarded-Proto and X-Real-Ip/X-Forwarded-For headers"
499 499 "sent by the upstream reverse proxy. Neccesary if the proxy handles SSL")
500 500 )
501 501
502 502 def parse_command_line(self, argv=None):
503 503 super(NotebookApp, self).parse_command_line(argv)
504 504 if argv is None:
505 505 argv = sys.argv[1:]
506 506
507 507 # Scrub frontend-specific flags
508 508 self.kernel_argv = swallow_argv(argv, notebook_aliases, notebook_flags)
509 509 # Kernel should inherit default config file from frontend
510 510 self.kernel_argv.append("--IPKernelApp.parent_appname='%s'" % self.name)
511 511
512 512 if self.extra_args:
513 513 f = os.path.abspath(self.extra_args[0])
514 514 if os.path.isdir(f):
515 515 nbdir = f
516 516 else:
517 517 self.file_to_run = f
518 518 nbdir = os.path.dirname(f)
519 519 self.config.NotebookManager.notebook_dir = nbdir
520 520
521 521 def init_configurables(self):
522 522 # force Session default to be secure
523 523 default_secure(self.config)
524 524 self.kernel_manager = MappingKernelManager(
525 525 config=self.config, log=self.log, kernel_argv=self.kernel_argv,
526 526 connection_dir = self.profile_dir.security_dir,
527 527 )
528 528 kls = import_item(self.notebook_manager_class)
529 529 self.notebook_manager = kls(config=self.config, log=self.log)
530 530 self.notebook_manager.load_notebook_names()
531 531 self.cluster_manager = ClusterManager(config=self.config, log=self.log)
532 532 self.cluster_manager.update_profiles()
533 533
534 534 def init_logging(self):
535 535 # This prevents double log messages because tornado use a root logger that
536 536 # self.log is a child of. The logging module dipatches log messages to a log
537 537 # and all of its ancenstors until propagate is set to False.
538 538 self.log.propagate = False
539 539
540 540 # hook up tornado 3's loggers to our app handlers
541 541 for name in ('access', 'application', 'general'):
542 542 logging.getLogger('tornado.%s' % name).handlers = self.log.handlers
543 543
544 544 def init_webapp(self):
545 545 """initialize tornado webapp and httpserver"""
546 546 self.web_app = NotebookWebApplication(
547 547 self, self.kernel_manager, self.notebook_manager,
548 548 self.cluster_manager, self.log,
549 549 self.base_project_url, self.webapp_settings
550 550 )
551 551 if self.certfile:
552 552 ssl_options = dict(certfile=self.certfile)
553 553 if self.keyfile:
554 554 ssl_options['keyfile'] = self.keyfile
555 555 else:
556 556 ssl_options = None
557 557 self.web_app.password = self.password
558 558 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options,
559 559 xheaders=self.trust_xheaders)
560 560 if not self.ip:
561 561 warning = "WARNING: The notebook server is listening on all IP addresses"
562 562 if ssl_options is None:
563 563 self.log.critical(warning + " and not using encryption. This "
564 564 "is not recommended.")
565 565 if not self.password and not self.read_only:
566 566 self.log.critical(warning + " and not using authentication. "
567 567 "This is highly insecure and not recommended.")
568 568 success = None
569 569 for port in random_ports(self.port, self.port_retries+1):
570 570 try:
571 571 self.http_server.listen(port, self.ip)
572 572 except socket.error as e:
573 573 # XXX: remove the e.errno == -9 block when we require
574 574 # tornado >= 3.0
575 575 if e.errno == -9 and tornado.version_info[0] < 3:
576 576 # The flags passed to socket.getaddrinfo from
577 577 # tornado.netutils.bind_sockets can cause "gaierror:
578 578 # [Errno -9] Address family for hostname not supported"
579 579 # when the interface is not associated, for example.
580 580 # Changing the flags to exclude socket.AI_ADDRCONFIG does
581 581 # not cause this error, but the only way to do this is to
582 582 # monkeypatch socket to remove the AI_ADDRCONFIG attribute
583 583 saved_AI_ADDRCONFIG = socket.AI_ADDRCONFIG
584 584 self.log.warn('Monkeypatching socket to fix tornado bug')
585 585 del(socket.AI_ADDRCONFIG)
586 586 try:
587 587 # retry the tornado call without AI_ADDRCONFIG flags
588 588 self.http_server.listen(port, self.ip)
589 589 except socket.error as e2:
590 590 e = e2
591 591 else:
592 592 self.port = port
593 593 success = True
594 594 break
595 595 # restore the monekypatch
596 596 socket.AI_ADDRCONFIG = saved_AI_ADDRCONFIG
597 597 if e.errno != errno.EADDRINUSE:
598 598 raise
599 599 self.log.info('The port %i is already in use, trying another random port.' % port)
600 600 else:
601 601 self.port = port
602 602 success = True
603 603 break
604 604 if not success:
605 605 self.log.critical('ERROR: the notebook server could not be started because '
606 606 'no available port could be found.')
607 607 self.exit(1)
608 608
609 609 def init_signal(self):
610 610 if not sys.platform.startswith('win'):
611 611 signal.signal(signal.SIGINT, self._handle_sigint)
612 612 signal.signal(signal.SIGTERM, self._signal_stop)
613 613 if hasattr(signal, 'SIGUSR1'):
614 614 # Windows doesn't support SIGUSR1
615 615 signal.signal(signal.SIGUSR1, self._signal_info)
616 616 if hasattr(signal, 'SIGINFO'):
617 617 # only on BSD-based systems
618 618 signal.signal(signal.SIGINFO, self._signal_info)
619 619
620 620 def _handle_sigint(self, sig, frame):
621 621 """SIGINT handler spawns confirmation dialog"""
622 622 # register more forceful signal handler for ^C^C case
623 623 signal.signal(signal.SIGINT, self._signal_stop)
624 624 # request confirmation dialog in bg thread, to avoid
625 625 # blocking the App
626 626 thread = threading.Thread(target=self._confirm_exit)
627 627 thread.daemon = True
628 628 thread.start()
629 629
630 630 def _restore_sigint_handler(self):
631 631 """callback for restoring original SIGINT handler"""
632 632 signal.signal(signal.SIGINT, self._handle_sigint)
633 633
634 634 def _confirm_exit(self):
635 635 """confirm shutdown on ^C
636 636
637 637 A second ^C, or answering 'y' within 5s will cause shutdown,
638 638 otherwise original SIGINT handler will be restored.
639 639
640 640 This doesn't work on Windows.
641 641 """
642 642 # FIXME: remove this delay when pyzmq dependency is >= 2.1.11
643 643 time.sleep(0.1)
644 644 info = self.log.info
645 645 info('interrupted')
646 646 print self.notebook_info()
647 647 sys.stdout.write("Shutdown this notebook server (y/[n])? ")
648 648 sys.stdout.flush()
649 649 r,w,x = select.select([sys.stdin], [], [], 5)
650 650 if r:
651 651 line = sys.stdin.readline()
652 652 if line.lower().startswith('y'):
653 653 self.log.critical("Shutdown confirmed")
654 654 ioloop.IOLoop.instance().stop()
655 655 return
656 656 else:
657 657 print "No answer for 5s:",
658 658 print "resuming operation..."
659 659 # no answer, or answer is no:
660 660 # set it back to original SIGINT handler
661 661 # use IOLoop.add_callback because signal.signal must be called
662 662 # from main thread
663 663 ioloop.IOLoop.instance().add_callback(self._restore_sigint_handler)
664 664
665 665 def _signal_stop(self, sig, frame):
666 666 self.log.critical("received signal %s, stopping", sig)
667 667 ioloop.IOLoop.instance().stop()
668 668
669 669 def _signal_info(self, sig, frame):
670 670 print self.notebook_info()
671 671
672 672 def init_components(self):
673 673 """Check the components submodule, and warn if it's unclean"""
674 674 status = submodule.check_submodule_status()
675 675 if status == 'missing':
676 676 self.log.warn("components submodule missing, running `git submodule update`")
677 677 submodule.update_submodules(submodule.ipython_parent())
678 678 elif status == 'unclean':
679 679 self.log.warn("components submodule unclean, you may see 404s on static/components")
680 680 self.log.warn("run `setup.py submodule` or `git submodule update` to update")
681 681
682 682
683 683 @catch_config_error
684 684 def initialize(self, argv=None):
685 685 self.init_logging()
686 686 super(NotebookApp, self).initialize(argv)
687 687 self.init_configurables()
688 688 self.init_components()
689 689 self.init_webapp()
690 690 self.init_signal()
691 691
692 692 def cleanup_kernels(self):
693 693 """Shutdown all kernels.
694 694
695 695 The kernels will shutdown themselves when this process no longer exists,
696 696 but explicit shutdown allows the KernelManagers to cleanup the connection files.
697 697 """
698 698 self.log.info('Shutting down kernels')
699 699 self.kernel_manager.shutdown_all()
700 700
701 701 def notebook_info(self):
702 702 "Return the current working directory and the server url information"
703 703 mgr_info = self.notebook_manager.info_string() + "\n"
704 704 return mgr_info +"The IPython Notebook is running at: %s" % self._url
705 705
706 706 def start(self):
707 707 """ Start the IPython Notebook server app, after initialization
708 708
709 709 This method takes no arguments so all configuration and initialization
710 710 must be done prior to calling this method."""
711 711 ip = self.ip if self.ip else '[all ip addresses on your system]'
712 712 proto = 'https' if self.certfile else 'http'
713 713 info = self.log.info
714 714 self._url = "%s://%s:%i%s" % (proto, ip, self.port,
715 715 self.base_project_url)
716 716 for line in self.notebook_info().split("\n"):
717 717 info(line)
718 718 info("Use Control-C to stop this server and shut down all kernels.")
719 719
720 720 if self.open_browser or self.file_to_run:
721 721 ip = self.ip or LOCALHOST
722 722 try:
723 723 browser = webbrowser.get(self.browser or None)
724 724 except webbrowser.Error as e:
725 725 self.log.warn('No web browser found: %s.' % e)
726 726 browser = None
727 727
728 728 if self.file_to_run:
729 729 name, _ = os.path.splitext(os.path.basename(self.file_to_run))
730 730 url = self.notebook_manager.rev_mapping.get(name, '')
731 731 else:
732 732 url = ''
733 733 if browser:
734 734 b = lambda : browser.open("%s://%s:%i%s%s" % (proto, ip,
735 735 self.port, self.base_project_url, url), new=2)
736 736 threading.Thread(target=b).start()
737 737 try:
738 738 ioloop.IOLoop.instance().start()
739 739 except KeyboardInterrupt:
740 740 info("Interrupted...")
741 741 finally:
742 742 self.cleanup_kernels()
743 743
744 744
745 745 #-----------------------------------------------------------------------------
746 746 # Main entry point
747 747 #-----------------------------------------------------------------------------
748 748
749 749 def launch_new_instance():
750 750 app = NotebookApp.instance()
751 751 app.initialize()
752 752 app.start()
753 753
1 NO CONTENT: file renamed from IPython/deathrow/oldfrontend/cocoa/__init__.py to IPython/html/services/__init__.py
1 NO CONTENT: file renamed from IPython/deathrow/oldfrontend/cocoa/tests/__init__.py to IPython/html/services/clusters/__init__.py
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/services/clusters/clustermanager.py to IPython/html/services/clusters/clustermanager.py
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/services/clusters/handlers.py to IPython/html/services/clusters/handlers.py
1 NO CONTENT: file renamed from IPython/deathrow/oldfrontend/tests/__init__.py to IPython/html/services/kernels/__init__.py
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/services/kernels/handlers.py to IPython/html/services/kernels/handlers.py
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/services/kernels/kernelmanager.py to IPython/html/services/kernels/kernelmanager.py
1 NO CONTENT: file renamed from IPython/deathrow/oldfrontend/wx/__init__.py to IPython/html/services/notebooks/__init__.py
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/services/notebooks/azurenbmanager.py to IPython/html/services/notebooks/azurenbmanager.py
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/services/notebooks/filenbmanager.py to IPython/html/services/notebooks/filenbmanager.py
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/services/notebooks/handlers.py to IPython/html/services/notebooks/handlers.py
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/services/notebooks/nbmanager.py to IPython/html/services/notebooks/nbmanager.py
1 NO CONTENT: file renamed from IPython/deathrow/tests/__init__.py to IPython/html/services/notebooks/tests/__init__.py
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/services/notebooks/tests/test_nbmanager.py to IPython/html/services/notebooks/tests/test_nbmanager.py
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/static/auth/css/override.css to IPython/html/static/auth/css/override.css
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/static/auth/js/loginmain.js to IPython/html/static/auth/js/loginmain.js
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/static/auth/js/loginwidget.js to IPython/html/static/auth/js/loginwidget.js
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/static/auth/js/logoutmain.js to IPython/html/static/auth/js/logoutmain.js
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/static/auth/less/login.less to IPython/html/static/auth/less/login.less
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/static/auth/less/logout.less to IPython/html/static/auth/less/logout.less
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/static/auth/less/style.less to IPython/html/static/auth/less/style.less
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/static/base/css/boilerplate.css to IPython/html/static/base/css/boilerplate.css
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/static/base/images/favicon.ico to IPython/html/static/base/images/favicon.ico
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/static/base/images/ipynblogo.png to IPython/html/static/base/images/ipynblogo.png
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/static/base/images/ipynblogo.svg to IPython/html/static/base/images/ipynblogo.svg
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/static/base/js/dialog.js to IPython/html/static/base/js/dialog.js
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/static/base/js/events.js to IPython/html/static/base/js/events.js
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/static/base/js/namespace.js to IPython/html/static/base/js/namespace.js
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/static/base/js/page.js to IPython/html/static/base/js/page.js
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/static/base/js/pagemain.js to IPython/html/static/base/js/pagemain.js
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/static/base/js/utils.js to IPython/html/static/base/js/utils.js
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/static/base/less/flexbox.less to IPython/html/static/base/less/flexbox.less
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/static/base/less/mixins.less to IPython/html/static/base/less/mixins.less
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/static/base/less/page.less to IPython/html/static/base/less/page.less
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/static/base/less/style.less to IPython/html/static/base/less/style.less
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/static/base/less/variables.less to IPython/html/static/base/less/variables.less
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/static/custom/custom.css to IPython/html/static/custom/custom.css
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/static/custom/custom.js to IPython/html/static/custom/custom.js
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/static/dateformat/date.format.js to IPython/html/static/dateformat/date.format.js
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/static/notebook/css/override.css to IPython/html/static/notebook/css/override.css
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/static/notebook/js/cell.js to IPython/html/static/notebook/js/cell.js
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/static/notebook/js/celltoolbar.js to IPython/html/static/notebook/js/celltoolbar.js
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/static/notebook/js/celltoolbarpresets/default.js to IPython/html/static/notebook/js/celltoolbarpresets/default.js
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/static/notebook/js/celltoolbarpresets/example.js to IPython/html/static/notebook/js/celltoolbarpresets/example.js
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/static/notebook/js/celltoolbarpresets/slideshow.js to IPython/html/static/notebook/js/celltoolbarpresets/slideshow.js
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/static/notebook/js/codecell.js to IPython/html/static/notebook/js/codecell.js
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/static/notebook/js/codemirror-ipython.js to IPython/html/static/notebook/js/codemirror-ipython.js
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/static/notebook/js/completer.js to IPython/html/static/notebook/js/completer.js
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/static/notebook/js/config.js to IPython/html/static/notebook/js/config.js
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/static/notebook/js/contexthint.js to IPython/html/static/notebook/js/contexthint.js
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/static/notebook/js/layoutmanager.js to IPython/html/static/notebook/js/layoutmanager.js
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/static/notebook/js/main.js to IPython/html/static/notebook/js/main.js
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/static/notebook/js/maintoolbar.js to IPython/html/static/notebook/js/maintoolbar.js
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/static/notebook/js/mathjaxutils.js to IPython/html/static/notebook/js/mathjaxutils.js
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/static/notebook/js/menubar.js to IPython/html/static/notebook/js/menubar.js
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/static/notebook/js/notebook.js to IPython/html/static/notebook/js/notebook.js
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/static/notebook/js/notificationarea.js to IPython/html/static/notebook/js/notificationarea.js
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/static/notebook/js/notificationwidget.js to IPython/html/static/notebook/js/notificationwidget.js
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/static/notebook/js/outputarea.js to IPython/html/static/notebook/js/outputarea.js
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/static/notebook/js/pager.js to IPython/html/static/notebook/js/pager.js
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/static/notebook/js/quickhelp.js to IPython/html/static/notebook/js/quickhelp.js
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/static/notebook/js/savewidget.js to IPython/html/static/notebook/js/savewidget.js
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/static/notebook/js/textcell.js to IPython/html/static/notebook/js/textcell.js
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/static/notebook/js/toolbar.js to IPython/html/static/notebook/js/toolbar.js
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/static/notebook/js/tooltip.js to IPython/html/static/notebook/js/tooltip.js
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/static/notebook/less/ansicolors.less to IPython/html/static/notebook/less/ansicolors.less
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/static/notebook/less/cell.less to IPython/html/static/notebook/less/cell.less
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/static/notebook/less/celltoolbar.less to IPython/html/static/notebook/less/celltoolbar.less
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/static/notebook/less/codecell.less to IPython/html/static/notebook/less/codecell.less
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/static/notebook/less/codemirror.less to IPython/html/static/notebook/less/codemirror.less
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/static/notebook/less/completer.less to IPython/html/static/notebook/less/completer.less
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/static/notebook/less/highlight.less to IPython/html/static/notebook/less/highlight.less
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/static/notebook/less/menubar.less to IPython/html/static/notebook/less/menubar.less
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/static/notebook/less/notebook.less to IPython/html/static/notebook/less/notebook.less
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/static/notebook/less/notificationarea.less to IPython/html/static/notebook/less/notificationarea.less
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/static/notebook/less/notificationwidget.less to IPython/html/static/notebook/less/notificationwidget.less
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/static/notebook/less/outputarea.less to IPython/html/static/notebook/less/outputarea.less
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/static/notebook/less/pager.less to IPython/html/static/notebook/less/pager.less
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/static/notebook/less/quickhelp.less to IPython/html/static/notebook/less/quickhelp.less
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/static/notebook/less/renderedhtml.less to IPython/html/static/notebook/less/renderedhtml.less
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/static/notebook/less/savewidget.less to IPython/html/static/notebook/less/savewidget.less
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/static/notebook/less/style.less to IPython/html/static/notebook/less/style.less
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/static/notebook/less/textcell.less to IPython/html/static/notebook/less/textcell.less
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/static/notebook/less/toolbar.less to IPython/html/static/notebook/less/toolbar.less
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/static/notebook/less/tooltip.less to IPython/html/static/notebook/less/tooltip.less
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/static/notebook/less/variables.less to IPython/html/static/notebook/less/variables.less
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/static/services/kernels/js/kernel.js to IPython/html/static/services/kernels/js/kernel.js
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/static/style/style.less to IPython/html/static/style/style.less
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/static/style/style.min.css to IPython/html/static/style/style.min.css
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/static/tree/css/override.css to IPython/html/static/tree/css/override.css
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/static/tree/js/clusterlist.js to IPython/html/static/tree/js/clusterlist.js
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/static/tree/js/main.js to IPython/html/static/tree/js/main.js
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/static/tree/js/notebooklist.js to IPython/html/static/tree/js/notebooklist.js
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/static/tree/less/altuploadform.less to IPython/html/static/tree/less/altuploadform.less
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/static/tree/less/style.less to IPython/html/static/tree/less/style.less
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/static/tree/less/tree.less to IPython/html/static/tree/less/tree.less
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/templates/login.html to IPython/html/templates/login.html
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/templates/logout.html to IPython/html/templates/logout.html
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/templates/notebook.html to IPython/html/templates/notebook.html
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/templates/page.html to IPython/html/templates/page.html
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/templates/tree.html to IPython/html/templates/tree.html
1 NO CONTENT: file renamed from IPython/frontend/__init__.py to IPython/html/tests/__init__.py
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/tests/test_hist.sqlite to IPython/html/tests/test_hist.sqlite
1 NO CONTENT: file renamed from IPython/frontend/html/__init__.py to IPython/html/tree/__init__.py
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/tree/handlers.py to IPython/html/tree/handlers.py
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/utils.py to IPython/html/utils.py
@@ -1,540 +1,540 b''
1 1 """Utilities for connecting to kernels
2 2
3 3 Authors:
4 4
5 5 * Min Ragan-Kelley
6 6
7 7 """
8 8
9 9 #-----------------------------------------------------------------------------
10 10 # Copyright (C) 2013 The IPython Development Team
11 11 #
12 12 # Distributed under the terms of the BSD License. The full license is in
13 13 # the file COPYING, distributed as part of this software.
14 14 #-----------------------------------------------------------------------------
15 15
16 16 #-----------------------------------------------------------------------------
17 17 # Imports
18 18 #-----------------------------------------------------------------------------
19 19
20 20 from __future__ import absolute_import
21 21
22 22 import glob
23 23 import json
24 24 import os
25 25 import socket
26 26 import sys
27 27 from getpass import getpass
28 28 from subprocess import Popen, PIPE
29 29 import tempfile
30 30
31 31 import zmq
32 32
33 33 # external imports
34 34 from IPython.external.ssh import tunnel
35 35
36 36 # IPython imports
37 37 # from IPython.config import Configurable
38 38 from IPython.core.profiledir import ProfileDir
39 39 from IPython.utils.localinterfaces import LOCALHOST
40 40 from IPython.utils.path import filefind, get_ipython_dir
41 41 from IPython.utils.py3compat import str_to_bytes, bytes_to_str
42 42 from IPython.utils.traitlets import (
43 43 Bool, Integer, Unicode, CaselessStrEnum,
44 44 HasTraits,
45 45 )
46 46
47 47
48 48 #-----------------------------------------------------------------------------
49 49 # Working with Connection Files
50 50 #-----------------------------------------------------------------------------
51 51
52 52 def write_connection_file(fname=None, shell_port=0, iopub_port=0, stdin_port=0, hb_port=0,
53 53 control_port=0, ip=LOCALHOST, key=b'', transport='tcp'):
54 54 """Generates a JSON config file, including the selection of random ports.
55 55
56 56 Parameters
57 57 ----------
58 58
59 59 fname : unicode
60 60 The path to the file to write
61 61
62 62 shell_port : int, optional
63 63 The port to use for ROUTER (shell) channel.
64 64
65 65 iopub_port : int, optional
66 66 The port to use for the SUB channel.
67 67
68 68 stdin_port : int, optional
69 69 The port to use for the ROUTER (raw input) channel.
70 70
71 71 control_port : int, optional
72 72 The port to use for the ROUTER (control) channel.
73 73
74 74 hb_port : int, optional
75 75 The port to use for the heartbeat REP channel.
76 76
77 77 ip : str, optional
78 78 The ip address the kernel will bind to.
79 79
80 80 key : str, optional
81 81 The Session key used for HMAC authentication.
82 82
83 83 """
84 84 # default to temporary connector file
85 85 if not fname:
86 86 fname = tempfile.mktemp('.json')
87 87
88 88 # Find open ports as necessary.
89 89
90 90 ports = []
91 91 ports_needed = int(shell_port <= 0) + \
92 92 int(iopub_port <= 0) + \
93 93 int(stdin_port <= 0) + \
94 94 int(control_port <= 0) + \
95 95 int(hb_port <= 0)
96 96 if transport == 'tcp':
97 97 for i in range(ports_needed):
98 98 sock = socket.socket()
99 99 sock.bind(('', 0))
100 100 ports.append(sock)
101 101 for i, sock in enumerate(ports):
102 102 port = sock.getsockname()[1]
103 103 sock.close()
104 104 ports[i] = port
105 105 else:
106 106 N = 1
107 107 for i in range(ports_needed):
108 108 while os.path.exists("%s-%s" % (ip, str(N))):
109 109 N += 1
110 110 ports.append(N)
111 111 N += 1
112 112 if shell_port <= 0:
113 113 shell_port = ports.pop(0)
114 114 if iopub_port <= 0:
115 115 iopub_port = ports.pop(0)
116 116 if stdin_port <= 0:
117 117 stdin_port = ports.pop(0)
118 118 if control_port <= 0:
119 119 control_port = ports.pop(0)
120 120 if hb_port <= 0:
121 121 hb_port = ports.pop(0)
122 122
123 123 cfg = dict( shell_port=shell_port,
124 124 iopub_port=iopub_port,
125 125 stdin_port=stdin_port,
126 126 control_port=control_port,
127 127 hb_port=hb_port,
128 128 )
129 129 cfg['ip'] = ip
130 130 cfg['key'] = bytes_to_str(key)
131 131 cfg['transport'] = transport
132 132
133 133 with open(fname, 'w') as f:
134 134 f.write(json.dumps(cfg, indent=2))
135 135
136 136 return fname, cfg
137 137
138 138
139 139 def get_connection_file(app=None):
140 140 """Return the path to the connection file of an app
141 141
142 142 Parameters
143 143 ----------
144 144 app : IPKernelApp instance [optional]
145 145 If unspecified, the currently running app will be used
146 146 """
147 147 if app is None:
148 148 from IPython.kernel.zmq.kernelapp import IPKernelApp
149 149 if not IPKernelApp.initialized():
150 150 raise RuntimeError("app not specified, and not in a running Kernel")
151 151
152 152 app = IPKernelApp.instance()
153 153 return filefind(app.connection_file, ['.', app.profile_dir.security_dir])
154 154
155 155
156 156 def find_connection_file(filename, profile=None):
157 157 """find a connection file, and return its absolute path.
158 158
159 159 The current working directory and the profile's security
160 160 directory will be searched for the file if it is not given by
161 161 absolute path.
162 162
163 163 If profile is unspecified, then the current running application's
164 164 profile will be used, or 'default', if not run from IPython.
165 165
166 166 If the argument does not match an existing file, it will be interpreted as a
167 167 fileglob, and the matching file in the profile's security dir with
168 168 the latest access time will be used.
169 169
170 170 Parameters
171 171 ----------
172 172 filename : str
173 173 The connection file or fileglob to search for.
174 174 profile : str [optional]
175 175 The name of the profile to use when searching for the connection file,
176 176 if different from the current IPython session or 'default'.
177 177
178 178 Returns
179 179 -------
180 180 str : The absolute path of the connection file.
181 181 """
182 182 from IPython.core.application import BaseIPythonApplication as IPApp
183 183 try:
184 184 # quick check for absolute path, before going through logic
185 185 return filefind(filename)
186 186 except IOError:
187 187 pass
188 188
189 189 if profile is None:
190 190 # profile unspecified, check if running from an IPython app
191 191 if IPApp.initialized():
192 192 app = IPApp.instance()
193 193 profile_dir = app.profile_dir
194 194 else:
195 195 # not running in IPython, use default profile
196 196 profile_dir = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), 'default')
197 197 else:
198 198 # find profiledir by profile name:
199 199 profile_dir = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), profile)
200 200 security_dir = profile_dir.security_dir
201 201
202 202 try:
203 203 # first, try explicit name
204 204 return filefind(filename, ['.', security_dir])
205 205 except IOError:
206 206 pass
207 207
208 208 # not found by full name
209 209
210 210 if '*' in filename:
211 211 # given as a glob already
212 212 pat = filename
213 213 else:
214 214 # accept any substring match
215 215 pat = '*%s*' % filename
216 216 matches = glob.glob( os.path.join(security_dir, pat) )
217 217 if not matches:
218 218 raise IOError("Could not find %r in %r" % (filename, security_dir))
219 219 elif len(matches) == 1:
220 220 return matches[0]
221 221 else:
222 222 # get most recent match, by access time:
223 223 return sorted(matches, key=lambda f: os.stat(f).st_atime)[-1]
224 224
225 225
226 226 def get_connection_info(connection_file=None, unpack=False, profile=None):
227 227 """Return the connection information for the current Kernel.
228 228
229 229 Parameters
230 230 ----------
231 231 connection_file : str [optional]
232 232 The connection file to be used. Can be given by absolute path, or
233 233 IPython will search in the security directory of a given profile.
234 234 If run from IPython,
235 235
236 236 If unspecified, the connection file for the currently running
237 237 IPython Kernel will be used, which is only allowed from inside a kernel.
238 238 unpack : bool [default: False]
239 239 if True, return the unpacked dict, otherwise just the string contents
240 240 of the file.
241 241 profile : str [optional]
242 242 The name of the profile to use when searching for the connection file,
243 243 if different from the current IPython session or 'default'.
244 244
245 245
246 246 Returns
247 247 -------
248 248 The connection dictionary of the current kernel, as string or dict,
249 249 depending on `unpack`.
250 250 """
251 251 if connection_file is None:
252 252 # get connection file from current kernel
253 253 cf = get_connection_file()
254 254 else:
255 255 # connection file specified, allow shortnames:
256 256 cf = find_connection_file(connection_file, profile=profile)
257 257
258 258 with open(cf) as f:
259 259 info = f.read()
260 260
261 261 if unpack:
262 262 info = json.loads(info)
263 263 # ensure key is bytes:
264 264 info['key'] = str_to_bytes(info.get('key', ''))
265 265 return info
266 266
267 267
268 268 def connect_qtconsole(connection_file=None, argv=None, profile=None):
269 269 """Connect a qtconsole to the current kernel.
270 270
271 271 This is useful for connecting a second qtconsole to a kernel, or to a
272 272 local notebook.
273 273
274 274 Parameters
275 275 ----------
276 276 connection_file : str [optional]
277 277 The connection file to be used. Can be given by absolute path, or
278 278 IPython will search in the security directory of a given profile.
279 279 If run from IPython,
280 280
281 281 If unspecified, the connection file for the currently running
282 282 IPython Kernel will be used, which is only allowed from inside a kernel.
283 283 argv : list [optional]
284 284 Any extra args to be passed to the console.
285 285 profile : str [optional]
286 286 The name of the profile to use when searching for the connection file,
287 287 if different from the current IPython session or 'default'.
288 288
289 289
290 290 Returns
291 291 -------
292 292 subprocess.Popen instance running the qtconsole frontend
293 293 """
294 294 argv = [] if argv is None else argv
295 295
296 296 if connection_file is None:
297 297 # get connection file from current kernel
298 298 cf = get_connection_file()
299 299 else:
300 300 cf = find_connection_file(connection_file, profile=profile)
301 301
302 302 cmd = ';'.join([
303 "from IPython.frontend.qt.console import qtconsoleapp",
303 "from IPython.qt.console import qtconsoleapp",
304 304 "qtconsoleapp.main()"
305 305 ])
306 306
307 307 return Popen([sys.executable, '-c', cmd, '--existing', cf] + argv,
308 308 stdout=PIPE, stderr=PIPE, close_fds=True,
309 309 )
310 310
311 311
312 312 def tunnel_to_kernel(connection_info, sshserver, sshkey=None):
313 313 """tunnel connections to a kernel via ssh
314 314
315 315 This will open four SSH tunnels from localhost on this machine to the
316 316 ports associated with the kernel. They can be either direct
317 317 localhost-localhost tunnels, or if an intermediate server is necessary,
318 318 the kernel must be listening on a public IP.
319 319
320 320 Parameters
321 321 ----------
322 322 connection_info : dict or str (path)
323 323 Either a connection dict, or the path to a JSON connection file
324 324 sshserver : str
325 325 The ssh sever to use to tunnel to the kernel. Can be a full
326 326 `user@server:port` string. ssh config aliases are respected.
327 327 sshkey : str [optional]
328 328 Path to file containing ssh key to use for authentication.
329 329 Only necessary if your ssh config does not already associate
330 330 a keyfile with the host.
331 331
332 332 Returns
333 333 -------
334 334
335 335 (shell, iopub, stdin, hb) : ints
336 336 The four ports on localhost that have been forwarded to the kernel.
337 337 """
338 338 if isinstance(connection_info, basestring):
339 339 # it's a path, unpack it
340 340 with open(connection_info) as f:
341 341 connection_info = json.loads(f.read())
342 342
343 343 cf = connection_info
344 344
345 345 lports = tunnel.select_random_ports(4)
346 346 rports = cf['shell_port'], cf['iopub_port'], cf['stdin_port'], cf['hb_port']
347 347
348 348 remote_ip = cf['ip']
349 349
350 350 if tunnel.try_passwordless_ssh(sshserver, sshkey):
351 351 password=False
352 352 else:
353 353 password = getpass("SSH Password for %s: "%sshserver)
354 354
355 355 for lp,rp in zip(lports, rports):
356 356 tunnel.ssh_tunnel(lp, rp, sshserver, remote_ip, sshkey, password)
357 357
358 358 return tuple(lports)
359 359
360 360
361 361 #-----------------------------------------------------------------------------
362 362 # Mixin for classes that work with connection files
363 363 #-----------------------------------------------------------------------------
364 364
365 365 channel_socket_types = {
366 366 'hb' : zmq.REQ,
367 367 'shell' : zmq.DEALER,
368 368 'iopub' : zmq.SUB,
369 369 'stdin' : zmq.DEALER,
370 370 'control': zmq.DEALER,
371 371 }
372 372
373 373 port_names = [ "%s_port" % channel for channel in ('shell', 'stdin', 'iopub', 'hb', 'control')]
374 374
375 375 class ConnectionFileMixin(HasTraits):
376 376 """Mixin for configurable classes that work with connection files"""
377 377
378 378 # The addresses for the communication channels
379 379 connection_file = Unicode('')
380 380 _connection_file_written = Bool(False)
381 381
382 382 transport = CaselessStrEnum(['tcp', 'ipc'], default_value='tcp', config=True)
383 383
384 384 ip = Unicode(LOCALHOST, config=True,
385 385 help="""Set the kernel\'s IP address [default localhost].
386 386 If the IP address is something other than localhost, then
387 387 Consoles on other machines will be able to connect
388 388 to the Kernel, so be careful!"""
389 389 )
390 390
391 391 def _ip_default(self):
392 392 if self.transport == 'ipc':
393 393 if self.connection_file:
394 394 return os.path.splitext(self.connection_file)[0] + '-ipc'
395 395 else:
396 396 return 'kernel-ipc'
397 397 else:
398 398 return LOCALHOST
399 399
400 400 def _ip_changed(self, name, old, new):
401 401 if new == '*':
402 402 self.ip = '0.0.0.0'
403 403
404 404 # protected traits
405 405
406 406 shell_port = Integer(0)
407 407 iopub_port = Integer(0)
408 408 stdin_port = Integer(0)
409 409 control_port = Integer(0)
410 410 hb_port = Integer(0)
411 411
412 412 @property
413 413 def ports(self):
414 414 return [ getattr(self, name) for name in port_names ]
415 415
416 416 #--------------------------------------------------------------------------
417 417 # Connection and ipc file management
418 418 #--------------------------------------------------------------------------
419 419
420 420 def get_connection_info(self):
421 421 """return the connection info as a dict"""
422 422 return dict(
423 423 transport=self.transport,
424 424 ip=self.ip,
425 425 shell_port=self.shell_port,
426 426 iopub_port=self.iopub_port,
427 427 stdin_port=self.stdin_port,
428 428 hb_port=self.hb_port,
429 429 control_port=self.control_port,
430 430 )
431 431
432 432 def cleanup_connection_file(self):
433 433 """Cleanup connection file *if we wrote it*
434 434
435 435 Will not raise if the connection file was already removed somehow.
436 436 """
437 437 if self._connection_file_written:
438 438 # cleanup connection files on full shutdown of kernel we started
439 439 self._connection_file_written = False
440 440 try:
441 441 os.remove(self.connection_file)
442 442 except (IOError, OSError, AttributeError):
443 443 pass
444 444
445 445 def cleanup_ipc_files(self):
446 446 """Cleanup ipc files if we wrote them."""
447 447 if self.transport != 'ipc':
448 448 return
449 449 for port in self.ports:
450 450 ipcfile = "%s-%i" % (self.ip, port)
451 451 try:
452 452 os.remove(ipcfile)
453 453 except (IOError, OSError):
454 454 pass
455 455
456 456 def write_connection_file(self):
457 457 """Write connection info to JSON dict in self.connection_file."""
458 458 if self._connection_file_written:
459 459 return
460 460
461 461 self.connection_file, cfg = write_connection_file(self.connection_file,
462 462 transport=self.transport, ip=self.ip, key=self.session.key,
463 463 stdin_port=self.stdin_port, iopub_port=self.iopub_port,
464 464 shell_port=self.shell_port, hb_port=self.hb_port,
465 465 control_port=self.control_port,
466 466 )
467 467 # write_connection_file also sets default ports:
468 468 for name in port_names:
469 469 setattr(self, name, cfg[name])
470 470
471 471 self._connection_file_written = True
472 472
473 473 def load_connection_file(self):
474 474 """Load connection info from JSON dict in self.connection_file."""
475 475 with open(self.connection_file) as f:
476 476 cfg = json.loads(f.read())
477 477
478 478 self.transport = cfg.get('transport', 'tcp')
479 479 self.ip = cfg['ip']
480 480 for name in port_names:
481 481 setattr(self, name, cfg[name])
482 482 self.session.key = str_to_bytes(cfg['key'])
483 483
484 484 #--------------------------------------------------------------------------
485 485 # Creating connected sockets
486 486 #--------------------------------------------------------------------------
487 487
488 488 def _make_url(self, channel):
489 489 """Make a ZeroMQ URL for a given channel."""
490 490 transport = self.transport
491 491 ip = self.ip
492 492 port = getattr(self, '%s_port' % channel)
493 493
494 494 if transport == 'tcp':
495 495 return "tcp://%s:%i" % (ip, port)
496 496 else:
497 497 return "%s://%s-%s" % (transport, ip, port)
498 498
499 499 def _create_connected_socket(self, channel, identity=None):
500 500 """Create a zmq Socket and connect it to the kernel."""
501 501 url = self._make_url(channel)
502 502 socket_type = channel_socket_types[channel]
503 503 self.log.info("Connecting to: %s" % url)
504 504 sock = self.context.socket(socket_type)
505 505 if identity:
506 506 sock.identity = identity
507 507 sock.connect(url)
508 508 return sock
509 509
510 510 def connect_iopub(self, identity=None):
511 511 """return zmq Socket connected to the IOPub channel"""
512 512 sock = self._create_connected_socket('iopub', identity=identity)
513 513 sock.setsockopt(zmq.SUBSCRIBE, b'')
514 514 return sock
515 515
516 516 def connect_shell(self, identity=None):
517 517 """return zmq Socket connected to the Shell channel"""
518 518 return self._create_connected_socket('shell', identity=identity)
519 519
520 520 def connect_stdin(self, identity=None):
521 521 """return zmq Socket connected to the StdIn channel"""
522 522 return self._create_connected_socket('stdin', identity=identity)
523 523
524 524 def connect_hb(self, identity=None):
525 525 """return zmq Socket connected to the Heartbeat channel"""
526 526 return self._create_connected_socket('hb', identity=identity)
527 527
528 528 def connect_control(self, identity=None):
529 529 """return zmq Socket connected to the Heartbeat channel"""
530 530 return self._create_connected_socket('control', identity=identity)
531 531
532 532
533 533 __all__ = [
534 534 'write_connection_file',
535 535 'get_connection_file',
536 536 'find_connection_file',
537 537 'get_connection_info',
538 538 'connect_qtconsole',
539 539 'tunnel_to_kernel',
540 540 ]
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/auth/__init__.py to IPython/qt/__init__.py
1 NO CONTENT: file renamed from IPython/frontend/qt/base_frontend_mixin.py to IPython/qt/base_frontend_mixin.py
1 NO CONTENT: file renamed from IPython/frontend/qt/client.py to IPython/qt/client.py
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/base/__init__.py to IPython/qt/console/__init__.py
1 NO CONTENT: file renamed from IPython/frontend/qt/console/ansi_code_processor.py to IPython/qt/console/ansi_code_processor.py
1 NO CONTENT: file renamed from IPython/frontend/qt/console/bracket_matcher.py to IPython/qt/console/bracket_matcher.py
1 NO CONTENT: file renamed from IPython/frontend/qt/console/call_tip_widget.py to IPython/qt/console/call_tip_widget.py
1 NO CONTENT: file renamed from IPython/frontend/qt/console/completion_html.py to IPython/qt/console/completion_html.py
1 NO CONTENT: file renamed from IPython/frontend/qt/console/completion_lexer.py to IPython/qt/console/completion_lexer.py
1 NO CONTENT: file renamed from IPython/frontend/qt/console/completion_plain.py to IPython/qt/console/completion_plain.py
1 NO CONTENT: file renamed from IPython/frontend/qt/console/completion_widget.py to IPython/qt/console/completion_widget.py
@@ -1,2009 +1,2009 b''
1 1 """ An abstract base class for console-type widgets.
2 2 """
3 3 #-----------------------------------------------------------------------------
4 4 # Imports
5 5 #-----------------------------------------------------------------------------
6 6
7 7 # Standard library imports
8 8 import os.path
9 9 import re
10 10 import sys
11 11 from textwrap import dedent
12 12 from unicodedata import category
13 13 import webbrowser
14 14
15 15 # System library imports
16 16 from IPython.external.qt import QtCore, QtGui
17 17
18 18 # Local imports
19 19 from IPython.config.configurable import LoggingConfigurable
20 20 from IPython.core.inputsplitter import ESC_SEQUENCES
21 from IPython.frontend.qt.rich_text import HtmlExporter
22 from IPython.frontend.qt.util import MetaQObjectHasTraits, get_font
21 from IPython.qt.rich_text import HtmlExporter
22 from IPython.qt.util import MetaQObjectHasTraits, get_font
23 23 from IPython.utils.text import columnize
24 24 from IPython.utils.traitlets import Bool, Enum, Integer, Unicode
25 25 from ansi_code_processor import QtAnsiCodeProcessor
26 26 from completion_widget import CompletionWidget
27 27 from completion_html import CompletionHtml
28 28 from completion_plain import CompletionPlain
29 29 from kill_ring import QtKillRing
30 30
31 31
32 32 #-----------------------------------------------------------------------------
33 33 # Functions
34 34 #-----------------------------------------------------------------------------
35 35
36 36 ESCAPE_CHARS = ''.join(ESC_SEQUENCES)
37 37 ESCAPE_RE = re.compile("^["+ESCAPE_CHARS+"]+")
38 38
39 39 def commonprefix(items):
40 40 """Get common prefix for completions
41 41
42 42 Return the longest common prefix of a list of strings, but with special
43 43 treatment of escape characters that might precede commands in IPython,
44 44 such as %magic functions. Used in tab completion.
45 45
46 46 For a more general function, see os.path.commonprefix
47 47 """
48 48 # the last item will always have the least leading % symbol
49 49 # min / max are first/last in alphabetical order
50 50 first_match = ESCAPE_RE.match(min(items))
51 51 last_match = ESCAPE_RE.match(max(items))
52 52 # common suffix is (common prefix of reversed items) reversed
53 53 if first_match and last_match:
54 54 prefix = os.path.commonprefix((first_match.group(0)[::-1], last_match.group(0)[::-1]))[::-1]
55 55 else:
56 56 prefix = ''
57 57
58 58 items = [s.lstrip(ESCAPE_CHARS) for s in items]
59 59 return prefix+os.path.commonprefix(items)
60 60
61 61 def is_letter_or_number(char):
62 62 """ Returns whether the specified unicode character is a letter or a number.
63 63 """
64 64 cat = category(char)
65 65 return cat.startswith('L') or cat.startswith('N')
66 66
67 67 #-----------------------------------------------------------------------------
68 68 # Classes
69 69 #-----------------------------------------------------------------------------
70 70
71 71 class ConsoleWidget(LoggingConfigurable, QtGui.QWidget):
72 72 """ An abstract base class for console-type widgets. This class has
73 73 functionality for:
74 74
75 75 * Maintaining a prompt and editing region
76 76 * Providing the traditional Unix-style console keyboard shortcuts
77 77 * Performing tab completion
78 78 * Paging text
79 79 * Handling ANSI escape codes
80 80
81 81 ConsoleWidget also provides a number of utility methods that will be
82 82 convenient to implementors of a console-style widget.
83 83 """
84 84 __metaclass__ = MetaQObjectHasTraits
85 85
86 86 #------ Configuration ------------------------------------------------------
87 87
88 88 ansi_codes = Bool(True, config=True,
89 89 help="Whether to process ANSI escape codes."
90 90 )
91 91 buffer_size = Integer(500, config=True,
92 92 help="""
93 93 The maximum number of lines of text before truncation. Specifying a
94 94 non-positive number disables text truncation (not recommended).
95 95 """
96 96 )
97 97 gui_completion = Enum(['plain', 'droplist', 'ncurses'], config=True,
98 98 default_value = 'ncurses',
99 99 help="""
100 100 The type of completer to use. Valid values are:
101 101
102 102 'plain' : Show the availlable completion as a text list
103 103 Below the editting area.
104 104 'droplist': Show the completion in a drop down list navigable
105 105 by the arrow keys, and from which you can select
106 106 completion by pressing Return.
107 107 'ncurses' : Show the completion as a text list which is navigable by
108 108 `tab` and arrow keys.
109 109 """
110 110 )
111 111 # NOTE: this value can only be specified during initialization.
112 112 kind = Enum(['plain', 'rich'], default_value='plain', config=True,
113 113 help="""
114 114 The type of underlying text widget to use. Valid values are 'plain',
115 115 which specifies a QPlainTextEdit, and 'rich', which specifies a
116 116 QTextEdit.
117 117 """
118 118 )
119 119 # NOTE: this value can only be specified during initialization.
120 120 paging = Enum(['inside', 'hsplit', 'vsplit', 'custom', 'none'],
121 121 default_value='inside', config=True,
122 122 help="""
123 123 The type of paging to use. Valid values are:
124 124
125 125 'inside' : The widget pages like a traditional terminal.
126 126 'hsplit' : When paging is requested, the widget is split
127 127 horizontally. The top pane contains the console, and the
128 128 bottom pane contains the paged text.
129 129 'vsplit' : Similar to 'hsplit', except that a vertical splitter
130 130 used.
131 131 'custom' : No action is taken by the widget beyond emitting a
132 132 'custom_page_requested(str)' signal.
133 133 'none' : The text is written directly to the console.
134 134 """)
135 135
136 136 font_family = Unicode(config=True,
137 137 help="""The font family to use for the console.
138 138 On OSX this defaults to Monaco, on Windows the default is
139 139 Consolas with fallback of Courier, and on other platforms
140 140 the default is Monospace.
141 141 """)
142 142 def _font_family_default(self):
143 143 if sys.platform == 'win32':
144 144 # Consolas ships with Vista/Win7, fallback to Courier if needed
145 145 return 'Consolas'
146 146 elif sys.platform == 'darwin':
147 147 # OSX always has Monaco, no need for a fallback
148 148 return 'Monaco'
149 149 else:
150 150 # Monospace should always exist, no need for a fallback
151 151 return 'Monospace'
152 152
153 153 font_size = Integer(config=True,
154 154 help="""The font size. If unconfigured, Qt will be entrusted
155 155 with the size of the font.
156 156 """)
157 157
158 158 width = Integer(81, config=True,
159 159 help="""The width of the console at start time in number
160 160 of characters (will double with `hsplit` paging)
161 161 """)
162 162
163 163 height = Integer(25, config=True,
164 164 help="""The height of the console at start time in number
165 165 of characters (will double with `vsplit` paging)
166 166 """)
167 167
168 168 # Whether to override ShortcutEvents for the keybindings defined by this
169 169 # widget (Ctrl+n, Ctrl+a, etc). Enable this if you want this widget to take
170 170 # priority (when it has focus) over, e.g., window-level menu shortcuts.
171 171 override_shortcuts = Bool(False)
172 172
173 173 # ------ Custom Qt Widgets -------------------------------------------------
174 174
175 175 # For other projects to easily override the Qt widgets used by the console
176 176 # (e.g. Spyder)
177 177 custom_control = None
178 178 custom_page_control = None
179 179
180 180 #------ Signals ------------------------------------------------------------
181 181
182 182 # Signals that indicate ConsoleWidget state.
183 183 copy_available = QtCore.Signal(bool)
184 184 redo_available = QtCore.Signal(bool)
185 185 undo_available = QtCore.Signal(bool)
186 186
187 187 # Signal emitted when paging is needed and the paging style has been
188 188 # specified as 'custom'.
189 189 custom_page_requested = QtCore.Signal(object)
190 190
191 191 # Signal emitted when the font is changed.
192 192 font_changed = QtCore.Signal(QtGui.QFont)
193 193
194 194 #------ Protected class variables ------------------------------------------
195 195
196 196 # control handles
197 197 _control = None
198 198 _page_control = None
199 199 _splitter = None
200 200
201 201 # When the control key is down, these keys are mapped.
202 202 _ctrl_down_remap = { QtCore.Qt.Key_B : QtCore.Qt.Key_Left,
203 203 QtCore.Qt.Key_F : QtCore.Qt.Key_Right,
204 204 QtCore.Qt.Key_A : QtCore.Qt.Key_Home,
205 205 QtCore.Qt.Key_P : QtCore.Qt.Key_Up,
206 206 QtCore.Qt.Key_N : QtCore.Qt.Key_Down,
207 207 QtCore.Qt.Key_H : QtCore.Qt.Key_Backspace, }
208 208 if not sys.platform == 'darwin':
209 209 # On OS X, Ctrl-E already does the right thing, whereas End moves the
210 210 # cursor to the bottom of the buffer.
211 211 _ctrl_down_remap[QtCore.Qt.Key_E] = QtCore.Qt.Key_End
212 212
213 213 # The shortcuts defined by this widget. We need to keep track of these to
214 214 # support 'override_shortcuts' above.
215 215 _shortcuts = set(_ctrl_down_remap.keys() +
216 216 [ QtCore.Qt.Key_C, QtCore.Qt.Key_G, QtCore.Qt.Key_O,
217 217 QtCore.Qt.Key_V ])
218 218
219 219 _temp_buffer_filled = False
220 220
221 221 #---------------------------------------------------------------------------
222 222 # 'QObject' interface
223 223 #---------------------------------------------------------------------------
224 224
225 225 def __init__(self, parent=None, **kw):
226 226 """ Create a ConsoleWidget.
227 227
228 228 Parameters:
229 229 -----------
230 230 parent : QWidget, optional [default None]
231 231 The parent for this widget.
232 232 """
233 233 QtGui.QWidget.__init__(self, parent)
234 234 LoggingConfigurable.__init__(self, **kw)
235 235
236 236 # While scrolling the pager on Mac OS X, it tears badly. The
237 237 # NativeGesture is platform and perhaps build-specific hence
238 238 # we take adequate precautions here.
239 239 self._pager_scroll_events = [QtCore.QEvent.Wheel]
240 240 if hasattr(QtCore.QEvent, 'NativeGesture'):
241 241 self._pager_scroll_events.append(QtCore.QEvent.NativeGesture)
242 242
243 243 # Create the layout and underlying text widget.
244 244 layout = QtGui.QStackedLayout(self)
245 245 layout.setContentsMargins(0, 0, 0, 0)
246 246 self._control = self._create_control()
247 247 if self.paging in ('hsplit', 'vsplit'):
248 248 self._splitter = QtGui.QSplitter()
249 249 if self.paging == 'hsplit':
250 250 self._splitter.setOrientation(QtCore.Qt.Horizontal)
251 251 else:
252 252 self._splitter.setOrientation(QtCore.Qt.Vertical)
253 253 self._splitter.addWidget(self._control)
254 254 layout.addWidget(self._splitter)
255 255 else:
256 256 layout.addWidget(self._control)
257 257
258 258 # Create the paging widget, if necessary.
259 259 if self.paging in ('inside', 'hsplit', 'vsplit'):
260 260 self._page_control = self._create_page_control()
261 261 if self._splitter:
262 262 self._page_control.hide()
263 263 self._splitter.addWidget(self._page_control)
264 264 else:
265 265 layout.addWidget(self._page_control)
266 266
267 267 # Initialize protected variables. Some variables contain useful state
268 268 # information for subclasses; they should be considered read-only.
269 269 self._append_before_prompt_pos = 0
270 270 self._ansi_processor = QtAnsiCodeProcessor()
271 271 if self.gui_completion == 'ncurses':
272 272 self._completion_widget = CompletionHtml(self)
273 273 elif self.gui_completion == 'droplist':
274 274 self._completion_widget = CompletionWidget(self)
275 275 elif self.gui_completion == 'plain':
276 276 self._completion_widget = CompletionPlain(self)
277 277
278 278 self._continuation_prompt = '> '
279 279 self._continuation_prompt_html = None
280 280 self._executing = False
281 281 self._filter_resize = False
282 282 self._html_exporter = HtmlExporter(self._control)
283 283 self._input_buffer_executing = ''
284 284 self._input_buffer_pending = ''
285 285 self._kill_ring = QtKillRing(self._control)
286 286 self._prompt = ''
287 287 self._prompt_html = None
288 288 self._prompt_pos = 0
289 289 self._prompt_sep = ''
290 290 self._reading = False
291 291 self._reading_callback = None
292 292 self._tab_width = 8
293 293
294 294 # Set a monospaced font.
295 295 self.reset_font()
296 296
297 297 # Configure actions.
298 298 action = QtGui.QAction('Print', None)
299 299 action.setEnabled(True)
300 300 printkey = QtGui.QKeySequence(QtGui.QKeySequence.Print)
301 301 if printkey.matches("Ctrl+P") and sys.platform != 'darwin':
302 302 # Only override the default if there is a collision.
303 303 # Qt ctrl = cmd on OSX, so the match gets a false positive on OSX.
304 304 printkey = "Ctrl+Shift+P"
305 305 action.setShortcut(printkey)
306 306 action.setShortcutContext(QtCore.Qt.WidgetWithChildrenShortcut)
307 307 action.triggered.connect(self.print_)
308 308 self.addAction(action)
309 309 self.print_action = action
310 310
311 311 action = QtGui.QAction('Save as HTML/XML', None)
312 312 action.setShortcut(QtGui.QKeySequence.Save)
313 313 action.setShortcutContext(QtCore.Qt.WidgetWithChildrenShortcut)
314 314 action.triggered.connect(self.export_html)
315 315 self.addAction(action)
316 316 self.export_action = action
317 317
318 318 action = QtGui.QAction('Select All', None)
319 319 action.setEnabled(True)
320 320 selectall = QtGui.QKeySequence(QtGui.QKeySequence.SelectAll)
321 321 if selectall.matches("Ctrl+A") and sys.platform != 'darwin':
322 322 # Only override the default if there is a collision.
323 323 # Qt ctrl = cmd on OSX, so the match gets a false positive on OSX.
324 324 selectall = "Ctrl+Shift+A"
325 325 action.setShortcut(selectall)
326 326 action.setShortcutContext(QtCore.Qt.WidgetWithChildrenShortcut)
327 327 action.triggered.connect(self.select_all)
328 328 self.addAction(action)
329 329 self.select_all_action = action
330 330
331 331 self.increase_font_size = QtGui.QAction("Bigger Font",
332 332 self,
333 333 shortcut=QtGui.QKeySequence.ZoomIn,
334 334 shortcutContext=QtCore.Qt.WidgetWithChildrenShortcut,
335 335 statusTip="Increase the font size by one point",
336 336 triggered=self._increase_font_size)
337 337 self.addAction(self.increase_font_size)
338 338
339 339 self.decrease_font_size = QtGui.QAction("Smaller Font",
340 340 self,
341 341 shortcut=QtGui.QKeySequence.ZoomOut,
342 342 shortcutContext=QtCore.Qt.WidgetWithChildrenShortcut,
343 343 statusTip="Decrease the font size by one point",
344 344 triggered=self._decrease_font_size)
345 345 self.addAction(self.decrease_font_size)
346 346
347 347 self.reset_font_size = QtGui.QAction("Normal Font",
348 348 self,
349 349 shortcut="Ctrl+0",
350 350 shortcutContext=QtCore.Qt.WidgetWithChildrenShortcut,
351 351 statusTip="Restore the Normal font size",
352 352 triggered=self.reset_font)
353 353 self.addAction(self.reset_font_size)
354 354
355 355 # Accept drag and drop events here. Drops were already turned off
356 356 # in self._control when that widget was created.
357 357 self.setAcceptDrops(True)
358 358
359 359 #---------------------------------------------------------------------------
360 360 # Drag and drop support
361 361 #---------------------------------------------------------------------------
362 362
363 363 def dragEnterEvent(self, e):
364 364 if e.mimeData().hasUrls():
365 365 # The link action should indicate to that the drop will insert
366 366 # the file anme.
367 367 e.setDropAction(QtCore.Qt.LinkAction)
368 368 e.accept()
369 369 elif e.mimeData().hasText():
370 370 # By changing the action to copy we don't need to worry about
371 371 # the user accidentally moving text around in the widget.
372 372 e.setDropAction(QtCore.Qt.CopyAction)
373 373 e.accept()
374 374
375 375 def dragMoveEvent(self, e):
376 376 if e.mimeData().hasUrls():
377 377 pass
378 378 elif e.mimeData().hasText():
379 379 cursor = self._control.cursorForPosition(e.pos())
380 380 if self._in_buffer(cursor.position()):
381 381 e.setDropAction(QtCore.Qt.CopyAction)
382 382 self._control.setTextCursor(cursor)
383 383 else:
384 384 e.setDropAction(QtCore.Qt.IgnoreAction)
385 385 e.accept()
386 386
387 387 def dropEvent(self, e):
388 388 if e.mimeData().hasUrls():
389 389 self._keep_cursor_in_buffer()
390 390 cursor = self._control.textCursor()
391 391 filenames = [url.toLocalFile() for url in e.mimeData().urls()]
392 392 text = ', '.join("'" + f.replace("'", "'\"'\"'") + "'"
393 393 for f in filenames)
394 394 self._insert_plain_text_into_buffer(cursor, text)
395 395 elif e.mimeData().hasText():
396 396 cursor = self._control.cursorForPosition(e.pos())
397 397 if self._in_buffer(cursor.position()):
398 398 text = e.mimeData().text()
399 399 self._insert_plain_text_into_buffer(cursor, text)
400 400
401 401 def eventFilter(self, obj, event):
402 402 """ Reimplemented to ensure a console-like behavior in the underlying
403 403 text widgets.
404 404 """
405 405 etype = event.type()
406 406 if etype == QtCore.QEvent.KeyPress:
407 407
408 408 # Re-map keys for all filtered widgets.
409 409 key = event.key()
410 410 if self._control_key_down(event.modifiers()) and \
411 411 key in self._ctrl_down_remap:
412 412 new_event = QtGui.QKeyEvent(QtCore.QEvent.KeyPress,
413 413 self._ctrl_down_remap[key],
414 414 QtCore.Qt.NoModifier)
415 415 QtGui.qApp.sendEvent(obj, new_event)
416 416 return True
417 417
418 418 elif obj == self._control:
419 419 return self._event_filter_console_keypress(event)
420 420
421 421 elif obj == self._page_control:
422 422 return self._event_filter_page_keypress(event)
423 423
424 424 # Make middle-click paste safe.
425 425 elif etype == QtCore.QEvent.MouseButtonRelease and \
426 426 event.button() == QtCore.Qt.MidButton and \
427 427 obj == self._control.viewport():
428 428 cursor = self._control.cursorForPosition(event.pos())
429 429 self._control.setTextCursor(cursor)
430 430 self.paste(QtGui.QClipboard.Selection)
431 431 return True
432 432
433 433 # Manually adjust the scrollbars *after* a resize event is dispatched.
434 434 elif etype == QtCore.QEvent.Resize and not self._filter_resize:
435 435 self._filter_resize = True
436 436 QtGui.qApp.sendEvent(obj, event)
437 437 self._adjust_scrollbars()
438 438 self._filter_resize = False
439 439 return True
440 440
441 441 # Override shortcuts for all filtered widgets.
442 442 elif etype == QtCore.QEvent.ShortcutOverride and \
443 443 self.override_shortcuts and \
444 444 self._control_key_down(event.modifiers()) and \
445 445 event.key() in self._shortcuts:
446 446 event.accept()
447 447
448 448 # Handle scrolling of the vsplit pager. This hack attempts to solve
449 449 # problems with tearing of the help text inside the pager window. This
450 450 # happens only on Mac OS X with both PySide and PyQt. This fix isn't
451 451 # perfect but makes the pager more usable.
452 452 elif etype in self._pager_scroll_events and \
453 453 obj == self._page_control:
454 454 self._page_control.repaint()
455 455 return True
456 456
457 457 elif etype == QtCore.QEvent.MouseMove:
458 458 anchor = self._control.anchorAt(event.pos())
459 459 QtGui.QToolTip.showText(event.globalPos(), anchor)
460 460
461 461 return super(ConsoleWidget, self).eventFilter(obj, event)
462 462
463 463 #---------------------------------------------------------------------------
464 464 # 'QWidget' interface
465 465 #---------------------------------------------------------------------------
466 466
467 467 def sizeHint(self):
468 468 """ Reimplemented to suggest a size that is 80 characters wide and
469 469 25 lines high.
470 470 """
471 471 font_metrics = QtGui.QFontMetrics(self.font)
472 472 margin = (self._control.frameWidth() +
473 473 self._control.document().documentMargin()) * 2
474 474 style = self.style()
475 475 splitwidth = style.pixelMetric(QtGui.QStyle.PM_SplitterWidth)
476 476
477 477 # Note 1: Despite my best efforts to take the various margins into
478 478 # account, the width is still coming out a bit too small, so we include
479 479 # a fudge factor of one character here.
480 480 # Note 2: QFontMetrics.maxWidth is not used here or anywhere else due
481 481 # to a Qt bug on certain Mac OS systems where it returns 0.
482 482 width = font_metrics.width(' ') * self.width + margin
483 483 width += style.pixelMetric(QtGui.QStyle.PM_ScrollBarExtent)
484 484 if self.paging == 'hsplit':
485 485 width = width * 2 + splitwidth
486 486
487 487 height = font_metrics.height() * self.height + margin
488 488 if self.paging == 'vsplit':
489 489 height = height * 2 + splitwidth
490 490
491 491 return QtCore.QSize(width, height)
492 492
493 493 #---------------------------------------------------------------------------
494 494 # 'ConsoleWidget' public interface
495 495 #---------------------------------------------------------------------------
496 496
497 497 def can_copy(self):
498 498 """ Returns whether text can be copied to the clipboard.
499 499 """
500 500 return self._control.textCursor().hasSelection()
501 501
502 502 def can_cut(self):
503 503 """ Returns whether text can be cut to the clipboard.
504 504 """
505 505 cursor = self._control.textCursor()
506 506 return (cursor.hasSelection() and
507 507 self._in_buffer(cursor.anchor()) and
508 508 self._in_buffer(cursor.position()))
509 509
510 510 def can_paste(self):
511 511 """ Returns whether text can be pasted from the clipboard.
512 512 """
513 513 if self._control.textInteractionFlags() & QtCore.Qt.TextEditable:
514 514 return bool(QtGui.QApplication.clipboard().text())
515 515 return False
516 516
517 517 def clear(self, keep_input=True):
518 518 """ Clear the console.
519 519
520 520 Parameters:
521 521 -----------
522 522 keep_input : bool, optional (default True)
523 523 If set, restores the old input buffer if a new prompt is written.
524 524 """
525 525 if self._executing:
526 526 self._control.clear()
527 527 else:
528 528 if keep_input:
529 529 input_buffer = self.input_buffer
530 530 self._control.clear()
531 531 self._show_prompt()
532 532 if keep_input:
533 533 self.input_buffer = input_buffer
534 534
535 535 def copy(self):
536 536 """ Copy the currently selected text to the clipboard.
537 537 """
538 538 self.layout().currentWidget().copy()
539 539
540 540 def copy_anchor(self, anchor):
541 541 """ Copy anchor text to the clipboard
542 542 """
543 543 QtGui.QApplication.clipboard().setText(anchor)
544 544
545 545 def cut(self):
546 546 """ Copy the currently selected text to the clipboard and delete it
547 547 if it's inside the input buffer.
548 548 """
549 549 self.copy()
550 550 if self.can_cut():
551 551 self._control.textCursor().removeSelectedText()
552 552
553 553 def execute(self, source=None, hidden=False, interactive=False):
554 554 """ Executes source or the input buffer, possibly prompting for more
555 555 input.
556 556
557 557 Parameters:
558 558 -----------
559 559 source : str, optional
560 560
561 561 The source to execute. If not specified, the input buffer will be
562 562 used. If specified and 'hidden' is False, the input buffer will be
563 563 replaced with the source before execution.
564 564
565 565 hidden : bool, optional (default False)
566 566
567 567 If set, no output will be shown and the prompt will not be modified.
568 568 In other words, it will be completely invisible to the user that
569 569 an execution has occurred.
570 570
571 571 interactive : bool, optional (default False)
572 572
573 573 Whether the console is to treat the source as having been manually
574 574 entered by the user. The effect of this parameter depends on the
575 575 subclass implementation.
576 576
577 577 Raises:
578 578 -------
579 579 RuntimeError
580 580 If incomplete input is given and 'hidden' is True. In this case,
581 581 it is not possible to prompt for more input.
582 582
583 583 Returns:
584 584 --------
585 585 A boolean indicating whether the source was executed.
586 586 """
587 587 # WARNING: The order in which things happen here is very particular, in
588 588 # large part because our syntax highlighting is fragile. If you change
589 589 # something, test carefully!
590 590
591 591 # Decide what to execute.
592 592 if source is None:
593 593 source = self.input_buffer
594 594 if not hidden:
595 595 # A newline is appended later, but it should be considered part
596 596 # of the input buffer.
597 597 source += '\n'
598 598 elif not hidden:
599 599 self.input_buffer = source
600 600
601 601 # Execute the source or show a continuation prompt if it is incomplete.
602 602 complete = self._is_complete(source, interactive)
603 603 if hidden:
604 604 if complete:
605 605 self._execute(source, hidden)
606 606 else:
607 607 error = 'Incomplete noninteractive input: "%s"'
608 608 raise RuntimeError(error % source)
609 609 else:
610 610 if complete:
611 611 self._append_plain_text('\n')
612 612 self._input_buffer_executing = self.input_buffer
613 613 self._executing = True
614 614 self._prompt_finished()
615 615
616 616 # The maximum block count is only in effect during execution.
617 617 # This ensures that _prompt_pos does not become invalid due to
618 618 # text truncation.
619 619 self._control.document().setMaximumBlockCount(self.buffer_size)
620 620
621 621 # Setting a positive maximum block count will automatically
622 622 # disable the undo/redo history, but just to be safe:
623 623 self._control.setUndoRedoEnabled(False)
624 624
625 625 # Perform actual execution.
626 626 self._execute(source, hidden)
627 627
628 628 else:
629 629 # Do this inside an edit block so continuation prompts are
630 630 # removed seamlessly via undo/redo.
631 631 cursor = self._get_end_cursor()
632 632 cursor.beginEditBlock()
633 633 cursor.insertText('\n')
634 634 self._insert_continuation_prompt(cursor)
635 635 cursor.endEditBlock()
636 636
637 637 # Do not do this inside the edit block. It works as expected
638 638 # when using a QPlainTextEdit control, but does not have an
639 639 # effect when using a QTextEdit. I believe this is a Qt bug.
640 640 self._control.moveCursor(QtGui.QTextCursor.End)
641 641
642 642 return complete
643 643
644 644 def export_html(self):
645 645 """ Shows a dialog to export HTML/XML in various formats.
646 646 """
647 647 self._html_exporter.export()
648 648
649 649 def _get_input_buffer(self, force=False):
650 650 """ The text that the user has entered entered at the current prompt.
651 651
652 652 If the console is currently executing, the text that is executing will
653 653 always be returned.
654 654 """
655 655 # If we're executing, the input buffer may not even exist anymore due to
656 656 # the limit imposed by 'buffer_size'. Therefore, we store it.
657 657 if self._executing and not force:
658 658 return self._input_buffer_executing
659 659
660 660 cursor = self._get_end_cursor()
661 661 cursor.setPosition(self._prompt_pos, QtGui.QTextCursor.KeepAnchor)
662 662 input_buffer = cursor.selection().toPlainText()
663 663
664 664 # Strip out continuation prompts.
665 665 return input_buffer.replace('\n' + self._continuation_prompt, '\n')
666 666
667 667 def _set_input_buffer(self, string):
668 668 """ Sets the text in the input buffer.
669 669
670 670 If the console is currently executing, this call has no *immediate*
671 671 effect. When the execution is finished, the input buffer will be updated
672 672 appropriately.
673 673 """
674 674 # If we're executing, store the text for later.
675 675 if self._executing:
676 676 self._input_buffer_pending = string
677 677 return
678 678
679 679 # Remove old text.
680 680 cursor = self._get_end_cursor()
681 681 cursor.beginEditBlock()
682 682 cursor.setPosition(self._prompt_pos, QtGui.QTextCursor.KeepAnchor)
683 683 cursor.removeSelectedText()
684 684
685 685 # Insert new text with continuation prompts.
686 686 self._insert_plain_text_into_buffer(self._get_prompt_cursor(), string)
687 687 cursor.endEditBlock()
688 688 self._control.moveCursor(QtGui.QTextCursor.End)
689 689
690 690 input_buffer = property(_get_input_buffer, _set_input_buffer)
691 691
692 692 def _get_font(self):
693 693 """ The base font being used by the ConsoleWidget.
694 694 """
695 695 return self._control.document().defaultFont()
696 696
697 697 def _set_font(self, font):
698 698 """ Sets the base font for the ConsoleWidget to the specified QFont.
699 699 """
700 700 font_metrics = QtGui.QFontMetrics(font)
701 701 self._control.setTabStopWidth(self.tab_width * font_metrics.width(' '))
702 702
703 703 self._completion_widget.setFont(font)
704 704 self._control.document().setDefaultFont(font)
705 705 if self._page_control:
706 706 self._page_control.document().setDefaultFont(font)
707 707
708 708 self.font_changed.emit(font)
709 709
710 710 font = property(_get_font, _set_font)
711 711
712 712 def open_anchor(self, anchor):
713 713 """ Open selected anchor in the default webbrowser
714 714 """
715 715 webbrowser.open( anchor )
716 716
717 717 def paste(self, mode=QtGui.QClipboard.Clipboard):
718 718 """ Paste the contents of the clipboard into the input region.
719 719
720 720 Parameters:
721 721 -----------
722 722 mode : QClipboard::Mode, optional [default QClipboard::Clipboard]
723 723
724 724 Controls which part of the system clipboard is used. This can be
725 725 used to access the selection clipboard in X11 and the Find buffer
726 726 in Mac OS. By default, the regular clipboard is used.
727 727 """
728 728 if self._control.textInteractionFlags() & QtCore.Qt.TextEditable:
729 729 # Make sure the paste is safe.
730 730 self._keep_cursor_in_buffer()
731 731 cursor = self._control.textCursor()
732 732
733 733 # Remove any trailing newline, which confuses the GUI and forces the
734 734 # user to backspace.
735 735 text = QtGui.QApplication.clipboard().text(mode).rstrip()
736 736 self._insert_plain_text_into_buffer(cursor, dedent(text))
737 737
738 738 def print_(self, printer = None):
739 739 """ Print the contents of the ConsoleWidget to the specified QPrinter.
740 740 """
741 741 if (not printer):
742 742 printer = QtGui.QPrinter()
743 743 if(QtGui.QPrintDialog(printer).exec_() != QtGui.QDialog.Accepted):
744 744 return
745 745 self._control.print_(printer)
746 746
747 747 def prompt_to_top(self):
748 748 """ Moves the prompt to the top of the viewport.
749 749 """
750 750 if not self._executing:
751 751 prompt_cursor = self._get_prompt_cursor()
752 752 if self._get_cursor().blockNumber() < prompt_cursor.blockNumber():
753 753 self._set_cursor(prompt_cursor)
754 754 self._set_top_cursor(prompt_cursor)
755 755
756 756 def redo(self):
757 757 """ Redo the last operation. If there is no operation to redo, nothing
758 758 happens.
759 759 """
760 760 self._control.redo()
761 761
762 762 def reset_font(self):
763 763 """ Sets the font to the default fixed-width font for this platform.
764 764 """
765 765 if sys.platform == 'win32':
766 766 # Consolas ships with Vista/Win7, fallback to Courier if needed
767 767 fallback = 'Courier'
768 768 elif sys.platform == 'darwin':
769 769 # OSX always has Monaco
770 770 fallback = 'Monaco'
771 771 else:
772 772 # Monospace should always exist
773 773 fallback = 'Monospace'
774 774 font = get_font(self.font_family, fallback)
775 775 if self.font_size:
776 776 font.setPointSize(self.font_size)
777 777 else:
778 778 font.setPointSize(QtGui.qApp.font().pointSize())
779 779 font.setStyleHint(QtGui.QFont.TypeWriter)
780 780 self._set_font(font)
781 781
782 782 def change_font_size(self, delta):
783 783 """Change the font size by the specified amount (in points).
784 784 """
785 785 font = self.font
786 786 size = max(font.pointSize() + delta, 1) # minimum 1 point
787 787 font.setPointSize(size)
788 788 self._set_font(font)
789 789
790 790 def _increase_font_size(self):
791 791 self.change_font_size(1)
792 792
793 793 def _decrease_font_size(self):
794 794 self.change_font_size(-1)
795 795
796 796 def select_all(self):
797 797 """ Selects all the text in the buffer.
798 798 """
799 799 self._control.selectAll()
800 800
801 801 def _get_tab_width(self):
802 802 """ The width (in terms of space characters) for tab characters.
803 803 """
804 804 return self._tab_width
805 805
806 806 def _set_tab_width(self, tab_width):
807 807 """ Sets the width (in terms of space characters) for tab characters.
808 808 """
809 809 font_metrics = QtGui.QFontMetrics(self.font)
810 810 self._control.setTabStopWidth(tab_width * font_metrics.width(' '))
811 811
812 812 self._tab_width = tab_width
813 813
814 814 tab_width = property(_get_tab_width, _set_tab_width)
815 815
816 816 def undo(self):
817 817 """ Undo the last operation. If there is no operation to undo, nothing
818 818 happens.
819 819 """
820 820 self._control.undo()
821 821
822 822 #---------------------------------------------------------------------------
823 823 # 'ConsoleWidget' abstract interface
824 824 #---------------------------------------------------------------------------
825 825
826 826 def _is_complete(self, source, interactive):
827 827 """ Returns whether 'source' can be executed. When triggered by an
828 828 Enter/Return key press, 'interactive' is True; otherwise, it is
829 829 False.
830 830 """
831 831 raise NotImplementedError
832 832
833 833 def _execute(self, source, hidden):
834 834 """ Execute 'source'. If 'hidden', do not show any output.
835 835 """
836 836 raise NotImplementedError
837 837
838 838 def _prompt_started_hook(self):
839 839 """ Called immediately after a new prompt is displayed.
840 840 """
841 841 pass
842 842
843 843 def _prompt_finished_hook(self):
844 844 """ Called immediately after a prompt is finished, i.e. when some input
845 845 will be processed and a new prompt displayed.
846 846 """
847 847 pass
848 848
849 849 def _up_pressed(self, shift_modifier):
850 850 """ Called when the up key is pressed. Returns whether to continue
851 851 processing the event.
852 852 """
853 853 return True
854 854
855 855 def _down_pressed(self, shift_modifier):
856 856 """ Called when the down key is pressed. Returns whether to continue
857 857 processing the event.
858 858 """
859 859 return True
860 860
861 861 def _tab_pressed(self):
862 862 """ Called when the tab key is pressed. Returns whether to continue
863 863 processing the event.
864 864 """
865 865 return False
866 866
867 867 #--------------------------------------------------------------------------
868 868 # 'ConsoleWidget' protected interface
869 869 #--------------------------------------------------------------------------
870 870
871 871 def _append_custom(self, insert, input, before_prompt=False):
872 872 """ A low-level method for appending content to the end of the buffer.
873 873
874 874 If 'before_prompt' is enabled, the content will be inserted before the
875 875 current prompt, if there is one.
876 876 """
877 877 # Determine where to insert the content.
878 878 cursor = self._control.textCursor()
879 879 if before_prompt and (self._reading or not self._executing):
880 880 cursor.setPosition(self._append_before_prompt_pos)
881 881 else:
882 882 cursor.movePosition(QtGui.QTextCursor.End)
883 883 start_pos = cursor.position()
884 884
885 885 # Perform the insertion.
886 886 result = insert(cursor, input)
887 887
888 888 # Adjust the prompt position if we have inserted before it. This is safe
889 889 # because buffer truncation is disabled when not executing.
890 890 if before_prompt and not self._executing:
891 891 diff = cursor.position() - start_pos
892 892 self._append_before_prompt_pos += diff
893 893 self._prompt_pos += diff
894 894
895 895 return result
896 896
897 897 def _append_block(self, block_format=None, before_prompt=False):
898 898 """ Appends an new QTextBlock to the end of the console buffer.
899 899 """
900 900 self._append_custom(self._insert_block, block_format, before_prompt)
901 901
902 902 def _append_html(self, html, before_prompt=False):
903 903 """ Appends HTML at the end of the console buffer.
904 904 """
905 905 self._append_custom(self._insert_html, html, before_prompt)
906 906
907 907 def _append_html_fetching_plain_text(self, html, before_prompt=False):
908 908 """ Appends HTML, then returns the plain text version of it.
909 909 """
910 910 return self._append_custom(self._insert_html_fetching_plain_text,
911 911 html, before_prompt)
912 912
913 913 def _append_plain_text(self, text, before_prompt=False):
914 914 """ Appends plain text, processing ANSI codes if enabled.
915 915 """
916 916 self._append_custom(self._insert_plain_text, text, before_prompt)
917 917
918 918 def _cancel_completion(self):
919 919 """ If text completion is progress, cancel it.
920 920 """
921 921 self._completion_widget.cancel_completion()
922 922
923 923 def _clear_temporary_buffer(self):
924 924 """ Clears the "temporary text" buffer, i.e. all the text following
925 925 the prompt region.
926 926 """
927 927 # Select and remove all text below the input buffer.
928 928 cursor = self._get_prompt_cursor()
929 929 prompt = self._continuation_prompt.lstrip()
930 930 if(self._temp_buffer_filled):
931 931 self._temp_buffer_filled = False
932 932 while cursor.movePosition(QtGui.QTextCursor.NextBlock):
933 933 temp_cursor = QtGui.QTextCursor(cursor)
934 934 temp_cursor.select(QtGui.QTextCursor.BlockUnderCursor)
935 935 text = temp_cursor.selection().toPlainText().lstrip()
936 936 if not text.startswith(prompt):
937 937 break
938 938 else:
939 939 # We've reached the end of the input buffer and no text follows.
940 940 return
941 941 cursor.movePosition(QtGui.QTextCursor.Left) # Grab the newline.
942 942 cursor.movePosition(QtGui.QTextCursor.End,
943 943 QtGui.QTextCursor.KeepAnchor)
944 944 cursor.removeSelectedText()
945 945
946 946 # After doing this, we have no choice but to clear the undo/redo
947 947 # history. Otherwise, the text is not "temporary" at all, because it
948 948 # can be recalled with undo/redo. Unfortunately, Qt does not expose
949 949 # fine-grained control to the undo/redo system.
950 950 if self._control.isUndoRedoEnabled():
951 951 self._control.setUndoRedoEnabled(False)
952 952 self._control.setUndoRedoEnabled(True)
953 953
954 954 def _complete_with_items(self, cursor, items):
955 955 """ Performs completion with 'items' at the specified cursor location.
956 956 """
957 957 self._cancel_completion()
958 958
959 959 if len(items) == 1:
960 960 cursor.setPosition(self._control.textCursor().position(),
961 961 QtGui.QTextCursor.KeepAnchor)
962 962 cursor.insertText(items[0])
963 963
964 964 elif len(items) > 1:
965 965 current_pos = self._control.textCursor().position()
966 966 prefix = commonprefix(items)
967 967 if prefix:
968 968 cursor.setPosition(current_pos, QtGui.QTextCursor.KeepAnchor)
969 969 cursor.insertText(prefix)
970 970 current_pos = cursor.position()
971 971
972 972 cursor.movePosition(QtGui.QTextCursor.Left, n=len(prefix))
973 973 self._completion_widget.show_items(cursor, items)
974 974
975 975
976 976 def _fill_temporary_buffer(self, cursor, text, html=False):
977 977 """fill the area below the active editting zone with text"""
978 978
979 979 current_pos = self._control.textCursor().position()
980 980
981 981 cursor.beginEditBlock()
982 982 self._append_plain_text('\n')
983 983 self._page(text, html=html)
984 984 cursor.endEditBlock()
985 985
986 986 cursor.setPosition(current_pos)
987 987 self._control.moveCursor(QtGui.QTextCursor.End)
988 988 self._control.setTextCursor(cursor)
989 989
990 990 self._temp_buffer_filled = True
991 991
992 992
993 993 def _context_menu_make(self, pos):
994 994 """ Creates a context menu for the given QPoint (in widget coordinates).
995 995 """
996 996 menu = QtGui.QMenu(self)
997 997
998 998 self.cut_action = menu.addAction('Cut', self.cut)
999 999 self.cut_action.setEnabled(self.can_cut())
1000 1000 self.cut_action.setShortcut(QtGui.QKeySequence.Cut)
1001 1001
1002 1002 self.copy_action = menu.addAction('Copy', self.copy)
1003 1003 self.copy_action.setEnabled(self.can_copy())
1004 1004 self.copy_action.setShortcut(QtGui.QKeySequence.Copy)
1005 1005
1006 1006 self.paste_action = menu.addAction('Paste', self.paste)
1007 1007 self.paste_action.setEnabled(self.can_paste())
1008 1008 self.paste_action.setShortcut(QtGui.QKeySequence.Paste)
1009 1009
1010 1010 anchor = self._control.anchorAt(pos)
1011 1011 if anchor:
1012 1012 menu.addSeparator()
1013 1013 self.copy_link_action = menu.addAction(
1014 1014 'Copy Link Address', lambda: self.copy_anchor(anchor=anchor))
1015 1015 self.open_link_action = menu.addAction(
1016 1016 'Open Link', lambda: self.open_anchor(anchor=anchor))
1017 1017
1018 1018 menu.addSeparator()
1019 1019 menu.addAction(self.select_all_action)
1020 1020
1021 1021 menu.addSeparator()
1022 1022 menu.addAction(self.export_action)
1023 1023 menu.addAction(self.print_action)
1024 1024
1025 1025 return menu
1026 1026
1027 1027 def _control_key_down(self, modifiers, include_command=False):
1028 1028 """ Given a KeyboardModifiers flags object, return whether the Control
1029 1029 key is down.
1030 1030
1031 1031 Parameters:
1032 1032 -----------
1033 1033 include_command : bool, optional (default True)
1034 1034 Whether to treat the Command key as a (mutually exclusive) synonym
1035 1035 for Control when in Mac OS.
1036 1036 """
1037 1037 # Note that on Mac OS, ControlModifier corresponds to the Command key
1038 1038 # while MetaModifier corresponds to the Control key.
1039 1039 if sys.platform == 'darwin':
1040 1040 down = include_command and (modifiers & QtCore.Qt.ControlModifier)
1041 1041 return bool(down) ^ bool(modifiers & QtCore.Qt.MetaModifier)
1042 1042 else:
1043 1043 return bool(modifiers & QtCore.Qt.ControlModifier)
1044 1044
1045 1045 def _create_control(self):
1046 1046 """ Creates and connects the underlying text widget.
1047 1047 """
1048 1048 # Create the underlying control.
1049 1049 if self.custom_control:
1050 1050 control = self.custom_control()
1051 1051 elif self.kind == 'plain':
1052 1052 control = QtGui.QPlainTextEdit()
1053 1053 elif self.kind == 'rich':
1054 1054 control = QtGui.QTextEdit()
1055 1055 control.setAcceptRichText(False)
1056 1056 control.setMouseTracking(True)
1057 1057
1058 1058 # Prevent the widget from handling drops, as we already provide
1059 1059 # the logic in this class.
1060 1060 control.setAcceptDrops(False)
1061 1061
1062 1062 # Install event filters. The filter on the viewport is needed for
1063 1063 # mouse events.
1064 1064 control.installEventFilter(self)
1065 1065 control.viewport().installEventFilter(self)
1066 1066
1067 1067 # Connect signals.
1068 1068 control.customContextMenuRequested.connect(
1069 1069 self._custom_context_menu_requested)
1070 1070 control.copyAvailable.connect(self.copy_available)
1071 1071 control.redoAvailable.connect(self.redo_available)
1072 1072 control.undoAvailable.connect(self.undo_available)
1073 1073
1074 1074 # Hijack the document size change signal to prevent Qt from adjusting
1075 1075 # the viewport's scrollbar. We are relying on an implementation detail
1076 1076 # of Q(Plain)TextEdit here, which is potentially dangerous, but without
1077 1077 # this functionality we cannot create a nice terminal interface.
1078 1078 layout = control.document().documentLayout()
1079 1079 layout.documentSizeChanged.disconnect()
1080 1080 layout.documentSizeChanged.connect(self._adjust_scrollbars)
1081 1081
1082 1082 # Configure the control.
1083 1083 control.setAttribute(QtCore.Qt.WA_InputMethodEnabled, True)
1084 1084 control.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
1085 1085 control.setReadOnly(True)
1086 1086 control.setUndoRedoEnabled(False)
1087 1087 control.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
1088 1088 return control
1089 1089
1090 1090 def _create_page_control(self):
1091 1091 """ Creates and connects the underlying paging widget.
1092 1092 """
1093 1093 if self.custom_page_control:
1094 1094 control = self.custom_page_control()
1095 1095 elif self.kind == 'plain':
1096 1096 control = QtGui.QPlainTextEdit()
1097 1097 elif self.kind == 'rich':
1098 1098 control = QtGui.QTextEdit()
1099 1099 control.installEventFilter(self)
1100 1100 viewport = control.viewport()
1101 1101 viewport.installEventFilter(self)
1102 1102 control.setReadOnly(True)
1103 1103 control.setUndoRedoEnabled(False)
1104 1104 control.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
1105 1105 return control
1106 1106
1107 1107 def _event_filter_console_keypress(self, event):
1108 1108 """ Filter key events for the underlying text widget to create a
1109 1109 console-like interface.
1110 1110 """
1111 1111 intercepted = False
1112 1112 cursor = self._control.textCursor()
1113 1113 position = cursor.position()
1114 1114 key = event.key()
1115 1115 ctrl_down = self._control_key_down(event.modifiers())
1116 1116 alt_down = event.modifiers() & QtCore.Qt.AltModifier
1117 1117 shift_down = event.modifiers() & QtCore.Qt.ShiftModifier
1118 1118
1119 1119 #------ Special sequences ----------------------------------------------
1120 1120
1121 1121 if event.matches(QtGui.QKeySequence.Copy):
1122 1122 self.copy()
1123 1123 intercepted = True
1124 1124
1125 1125 elif event.matches(QtGui.QKeySequence.Cut):
1126 1126 self.cut()
1127 1127 intercepted = True
1128 1128
1129 1129 elif event.matches(QtGui.QKeySequence.Paste):
1130 1130 self.paste()
1131 1131 intercepted = True
1132 1132
1133 1133 #------ Special modifier logic -----------------------------------------
1134 1134
1135 1135 elif key in (QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter):
1136 1136 intercepted = True
1137 1137
1138 1138 # Special handling when tab completing in text mode.
1139 1139 self._cancel_completion()
1140 1140
1141 1141 if self._in_buffer(position):
1142 1142 # Special handling when a reading a line of raw input.
1143 1143 if self._reading:
1144 1144 self._append_plain_text('\n')
1145 1145 self._reading = False
1146 1146 if self._reading_callback:
1147 1147 self._reading_callback()
1148 1148
1149 1149 # If the input buffer is a single line or there is only
1150 1150 # whitespace after the cursor, execute. Otherwise, split the
1151 1151 # line with a continuation prompt.
1152 1152 elif not self._executing:
1153 1153 cursor.movePosition(QtGui.QTextCursor.End,
1154 1154 QtGui.QTextCursor.KeepAnchor)
1155 1155 at_end = len(cursor.selectedText().strip()) == 0
1156 1156 single_line = (self._get_end_cursor().blockNumber() ==
1157 1157 self._get_prompt_cursor().blockNumber())
1158 1158 if (at_end or shift_down or single_line) and not ctrl_down:
1159 1159 self.execute(interactive = not shift_down)
1160 1160 else:
1161 1161 # Do this inside an edit block for clean undo/redo.
1162 1162 cursor.beginEditBlock()
1163 1163 cursor.setPosition(position)
1164 1164 cursor.insertText('\n')
1165 1165 self._insert_continuation_prompt(cursor)
1166 1166 cursor.endEditBlock()
1167 1167
1168 1168 # Ensure that the whole input buffer is visible.
1169 1169 # FIXME: This will not be usable if the input buffer is
1170 1170 # taller than the console widget.
1171 1171 self._control.moveCursor(QtGui.QTextCursor.End)
1172 1172 self._control.setTextCursor(cursor)
1173 1173
1174 1174 #------ Control/Cmd modifier -------------------------------------------
1175 1175
1176 1176 elif ctrl_down:
1177 1177 if key == QtCore.Qt.Key_G:
1178 1178 self._keyboard_quit()
1179 1179 intercepted = True
1180 1180
1181 1181 elif key == QtCore.Qt.Key_K:
1182 1182 if self._in_buffer(position):
1183 1183 cursor.clearSelection()
1184 1184 cursor.movePosition(QtGui.QTextCursor.EndOfLine,
1185 1185 QtGui.QTextCursor.KeepAnchor)
1186 1186 if not cursor.hasSelection():
1187 1187 # Line deletion (remove continuation prompt)
1188 1188 cursor.movePosition(QtGui.QTextCursor.NextBlock,
1189 1189 QtGui.QTextCursor.KeepAnchor)
1190 1190 cursor.movePosition(QtGui.QTextCursor.Right,
1191 1191 QtGui.QTextCursor.KeepAnchor,
1192 1192 len(self._continuation_prompt))
1193 1193 self._kill_ring.kill_cursor(cursor)
1194 1194 self._set_cursor(cursor)
1195 1195 intercepted = True
1196 1196
1197 1197 elif key == QtCore.Qt.Key_L:
1198 1198 self.prompt_to_top()
1199 1199 intercepted = True
1200 1200
1201 1201 elif key == QtCore.Qt.Key_O:
1202 1202 if self._page_control and self._page_control.isVisible():
1203 1203 self._page_control.setFocus()
1204 1204 intercepted = True
1205 1205
1206 1206 elif key == QtCore.Qt.Key_U:
1207 1207 if self._in_buffer(position):
1208 1208 cursor.clearSelection()
1209 1209 start_line = cursor.blockNumber()
1210 1210 if start_line == self._get_prompt_cursor().blockNumber():
1211 1211 offset = len(self._prompt)
1212 1212 else:
1213 1213 offset = len(self._continuation_prompt)
1214 1214 cursor.movePosition(QtGui.QTextCursor.StartOfBlock,
1215 1215 QtGui.QTextCursor.KeepAnchor)
1216 1216 cursor.movePosition(QtGui.QTextCursor.Right,
1217 1217 QtGui.QTextCursor.KeepAnchor, offset)
1218 1218 self._kill_ring.kill_cursor(cursor)
1219 1219 self._set_cursor(cursor)
1220 1220 intercepted = True
1221 1221
1222 1222 elif key == QtCore.Qt.Key_Y:
1223 1223 self._keep_cursor_in_buffer()
1224 1224 self._kill_ring.yank()
1225 1225 intercepted = True
1226 1226
1227 1227 elif key in (QtCore.Qt.Key_Backspace, QtCore.Qt.Key_Delete):
1228 1228 if key == QtCore.Qt.Key_Backspace:
1229 1229 cursor = self._get_word_start_cursor(position)
1230 1230 else: # key == QtCore.Qt.Key_Delete
1231 1231 cursor = self._get_word_end_cursor(position)
1232 1232 cursor.setPosition(position, QtGui.QTextCursor.KeepAnchor)
1233 1233 self._kill_ring.kill_cursor(cursor)
1234 1234 intercepted = True
1235 1235
1236 1236 elif key == QtCore.Qt.Key_D:
1237 1237 if len(self.input_buffer) == 0:
1238 1238 self.exit_requested.emit(self)
1239 1239 else:
1240 1240 new_event = QtGui.QKeyEvent(QtCore.QEvent.KeyPress,
1241 1241 QtCore.Qt.Key_Delete,
1242 1242 QtCore.Qt.NoModifier)
1243 1243 QtGui.qApp.sendEvent(self._control, new_event)
1244 1244 intercepted = True
1245 1245
1246 1246 #------ Alt modifier ---------------------------------------------------
1247 1247
1248 1248 elif alt_down:
1249 1249 if key == QtCore.Qt.Key_B:
1250 1250 self._set_cursor(self._get_word_start_cursor(position))
1251 1251 intercepted = True
1252 1252
1253 1253 elif key == QtCore.Qt.Key_F:
1254 1254 self._set_cursor(self._get_word_end_cursor(position))
1255 1255 intercepted = True
1256 1256
1257 1257 elif key == QtCore.Qt.Key_Y:
1258 1258 self._kill_ring.rotate()
1259 1259 intercepted = True
1260 1260
1261 1261 elif key == QtCore.Qt.Key_Backspace:
1262 1262 cursor = self._get_word_start_cursor(position)
1263 1263 cursor.setPosition(position, QtGui.QTextCursor.KeepAnchor)
1264 1264 self._kill_ring.kill_cursor(cursor)
1265 1265 intercepted = True
1266 1266
1267 1267 elif key == QtCore.Qt.Key_D:
1268 1268 cursor = self._get_word_end_cursor(position)
1269 1269 cursor.setPosition(position, QtGui.QTextCursor.KeepAnchor)
1270 1270 self._kill_ring.kill_cursor(cursor)
1271 1271 intercepted = True
1272 1272
1273 1273 elif key == QtCore.Qt.Key_Delete:
1274 1274 intercepted = True
1275 1275
1276 1276 elif key == QtCore.Qt.Key_Greater:
1277 1277 self._control.moveCursor(QtGui.QTextCursor.End)
1278 1278 intercepted = True
1279 1279
1280 1280 elif key == QtCore.Qt.Key_Less:
1281 1281 self._control.setTextCursor(self._get_prompt_cursor())
1282 1282 intercepted = True
1283 1283
1284 1284 #------ No modifiers ---------------------------------------------------
1285 1285
1286 1286 else:
1287 1287 if shift_down:
1288 1288 anchormode = QtGui.QTextCursor.KeepAnchor
1289 1289 else:
1290 1290 anchormode = QtGui.QTextCursor.MoveAnchor
1291 1291
1292 1292 if key == QtCore.Qt.Key_Escape:
1293 1293 self._keyboard_quit()
1294 1294 intercepted = True
1295 1295
1296 1296 elif key == QtCore.Qt.Key_Up:
1297 1297 if self._reading or not self._up_pressed(shift_down):
1298 1298 intercepted = True
1299 1299 else:
1300 1300 prompt_line = self._get_prompt_cursor().blockNumber()
1301 1301 intercepted = cursor.blockNumber() <= prompt_line
1302 1302
1303 1303 elif key == QtCore.Qt.Key_Down:
1304 1304 if self._reading or not self._down_pressed(shift_down):
1305 1305 intercepted = True
1306 1306 else:
1307 1307 end_line = self._get_end_cursor().blockNumber()
1308 1308 intercepted = cursor.blockNumber() == end_line
1309 1309
1310 1310 elif key == QtCore.Qt.Key_Tab:
1311 1311 if not self._reading:
1312 1312 if self._tab_pressed():
1313 1313 # real tab-key, insert four spaces
1314 1314 cursor.insertText(' '*4)
1315 1315 intercepted = True
1316 1316
1317 1317 elif key == QtCore.Qt.Key_Left:
1318 1318
1319 1319 # Move to the previous line
1320 1320 line, col = cursor.blockNumber(), cursor.columnNumber()
1321 1321 if line > self._get_prompt_cursor().blockNumber() and \
1322 1322 col == len(self._continuation_prompt):
1323 1323 self._control.moveCursor(QtGui.QTextCursor.PreviousBlock,
1324 1324 mode=anchormode)
1325 1325 self._control.moveCursor(QtGui.QTextCursor.EndOfBlock,
1326 1326 mode=anchormode)
1327 1327 intercepted = True
1328 1328
1329 1329 # Regular left movement
1330 1330 else:
1331 1331 intercepted = not self._in_buffer(position - 1)
1332 1332
1333 1333 elif key == QtCore.Qt.Key_Right:
1334 1334 original_block_number = cursor.blockNumber()
1335 1335 cursor.movePosition(QtGui.QTextCursor.Right,
1336 1336 mode=anchormode)
1337 1337 if cursor.blockNumber() != original_block_number:
1338 1338 cursor.movePosition(QtGui.QTextCursor.Right,
1339 1339 n=len(self._continuation_prompt),
1340 1340 mode=anchormode)
1341 1341 self._set_cursor(cursor)
1342 1342 intercepted = True
1343 1343
1344 1344 elif key == QtCore.Qt.Key_Home:
1345 1345 start_line = cursor.blockNumber()
1346 1346 if start_line == self._get_prompt_cursor().blockNumber():
1347 1347 start_pos = self._prompt_pos
1348 1348 else:
1349 1349 cursor.movePosition(QtGui.QTextCursor.StartOfBlock,
1350 1350 QtGui.QTextCursor.KeepAnchor)
1351 1351 start_pos = cursor.position()
1352 1352 start_pos += len(self._continuation_prompt)
1353 1353 cursor.setPosition(position)
1354 1354 if shift_down and self._in_buffer(position):
1355 1355 cursor.setPosition(start_pos, QtGui.QTextCursor.KeepAnchor)
1356 1356 else:
1357 1357 cursor.setPosition(start_pos)
1358 1358 self._set_cursor(cursor)
1359 1359 intercepted = True
1360 1360
1361 1361 elif key == QtCore.Qt.Key_Backspace:
1362 1362
1363 1363 # Line deletion (remove continuation prompt)
1364 1364 line, col = cursor.blockNumber(), cursor.columnNumber()
1365 1365 if not self._reading and \
1366 1366 col == len(self._continuation_prompt) and \
1367 1367 line > self._get_prompt_cursor().blockNumber():
1368 1368 cursor.beginEditBlock()
1369 1369 cursor.movePosition(QtGui.QTextCursor.StartOfBlock,
1370 1370 QtGui.QTextCursor.KeepAnchor)
1371 1371 cursor.removeSelectedText()
1372 1372 cursor.deletePreviousChar()
1373 1373 cursor.endEditBlock()
1374 1374 intercepted = True
1375 1375
1376 1376 # Regular backwards deletion
1377 1377 else:
1378 1378 anchor = cursor.anchor()
1379 1379 if anchor == position:
1380 1380 intercepted = not self._in_buffer(position - 1)
1381 1381 else:
1382 1382 intercepted = not self._in_buffer(min(anchor, position))
1383 1383
1384 1384 elif key == QtCore.Qt.Key_Delete:
1385 1385
1386 1386 # Line deletion (remove continuation prompt)
1387 1387 if not self._reading and self._in_buffer(position) and \
1388 1388 cursor.atBlockEnd() and not cursor.hasSelection():
1389 1389 cursor.movePosition(QtGui.QTextCursor.NextBlock,
1390 1390 QtGui.QTextCursor.KeepAnchor)
1391 1391 cursor.movePosition(QtGui.QTextCursor.Right,
1392 1392 QtGui.QTextCursor.KeepAnchor,
1393 1393 len(self._continuation_prompt))
1394 1394 cursor.removeSelectedText()
1395 1395 intercepted = True
1396 1396
1397 1397 # Regular forwards deletion:
1398 1398 else:
1399 1399 anchor = cursor.anchor()
1400 1400 intercepted = (not self._in_buffer(anchor) or
1401 1401 not self._in_buffer(position))
1402 1402
1403 1403 # Don't move the cursor if Control/Cmd is pressed to allow copy-paste
1404 1404 # using the keyboard in any part of the buffer. Also, permit scrolling
1405 1405 # with Page Up/Down keys. Finally, if we're executing, don't move the
1406 1406 # cursor (if even this made sense, we can't guarantee that the prompt
1407 1407 # position is still valid due to text truncation).
1408 1408 if not (self._control_key_down(event.modifiers(), include_command=True)
1409 1409 or key in (QtCore.Qt.Key_PageUp, QtCore.Qt.Key_PageDown)
1410 1410 or (self._executing and not self._reading)):
1411 1411 self._keep_cursor_in_buffer()
1412 1412
1413 1413 return intercepted
1414 1414
1415 1415 def _event_filter_page_keypress(self, event):
1416 1416 """ Filter key events for the paging widget to create console-like
1417 1417 interface.
1418 1418 """
1419 1419 key = event.key()
1420 1420 ctrl_down = self._control_key_down(event.modifiers())
1421 1421 alt_down = event.modifiers() & QtCore.Qt.AltModifier
1422 1422
1423 1423 if ctrl_down:
1424 1424 if key == QtCore.Qt.Key_O:
1425 1425 self._control.setFocus()
1426 1426 intercept = True
1427 1427
1428 1428 elif alt_down:
1429 1429 if key == QtCore.Qt.Key_Greater:
1430 1430 self._page_control.moveCursor(QtGui.QTextCursor.End)
1431 1431 intercepted = True
1432 1432
1433 1433 elif key == QtCore.Qt.Key_Less:
1434 1434 self._page_control.moveCursor(QtGui.QTextCursor.Start)
1435 1435 intercepted = True
1436 1436
1437 1437 elif key in (QtCore.Qt.Key_Q, QtCore.Qt.Key_Escape):
1438 1438 if self._splitter:
1439 1439 self._page_control.hide()
1440 1440 self._control.setFocus()
1441 1441 else:
1442 1442 self.layout().setCurrentWidget(self._control)
1443 1443 return True
1444 1444
1445 1445 elif key in (QtCore.Qt.Key_Enter, QtCore.Qt.Key_Return,
1446 1446 QtCore.Qt.Key_Tab):
1447 1447 new_event = QtGui.QKeyEvent(QtCore.QEvent.KeyPress,
1448 1448 QtCore.Qt.Key_PageDown,
1449 1449 QtCore.Qt.NoModifier)
1450 1450 QtGui.qApp.sendEvent(self._page_control, new_event)
1451 1451 return True
1452 1452
1453 1453 elif key == QtCore.Qt.Key_Backspace:
1454 1454 new_event = QtGui.QKeyEvent(QtCore.QEvent.KeyPress,
1455 1455 QtCore.Qt.Key_PageUp,
1456 1456 QtCore.Qt.NoModifier)
1457 1457 QtGui.qApp.sendEvent(self._page_control, new_event)
1458 1458 return True
1459 1459
1460 1460 return False
1461 1461
1462 1462 def _format_as_columns(self, items, separator=' '):
1463 1463 """ Transform a list of strings into a single string with columns.
1464 1464
1465 1465 Parameters
1466 1466 ----------
1467 1467 items : sequence of strings
1468 1468 The strings to process.
1469 1469
1470 1470 separator : str, optional [default is two spaces]
1471 1471 The string that separates columns.
1472 1472
1473 1473 Returns
1474 1474 -------
1475 1475 The formatted string.
1476 1476 """
1477 1477 # Calculate the number of characters available.
1478 1478 width = self._control.viewport().width()
1479 1479 char_width = QtGui.QFontMetrics(self.font).width(' ')
1480 1480 displaywidth = max(10, (width / char_width) - 1)
1481 1481
1482 1482 return columnize(items, separator, displaywidth)
1483 1483
1484 1484 def _get_block_plain_text(self, block):
1485 1485 """ Given a QTextBlock, return its unformatted text.
1486 1486 """
1487 1487 cursor = QtGui.QTextCursor(block)
1488 1488 cursor.movePosition(QtGui.QTextCursor.StartOfBlock)
1489 1489 cursor.movePosition(QtGui.QTextCursor.EndOfBlock,
1490 1490 QtGui.QTextCursor.KeepAnchor)
1491 1491 return cursor.selection().toPlainText()
1492 1492
1493 1493 def _get_cursor(self):
1494 1494 """ Convenience method that returns a cursor for the current position.
1495 1495 """
1496 1496 return self._control.textCursor()
1497 1497
1498 1498 def _get_end_cursor(self):
1499 1499 """ Convenience method that returns a cursor for the last character.
1500 1500 """
1501 1501 cursor = self._control.textCursor()
1502 1502 cursor.movePosition(QtGui.QTextCursor.End)
1503 1503 return cursor
1504 1504
1505 1505 def _get_input_buffer_cursor_column(self):
1506 1506 """ Returns the column of the cursor in the input buffer, excluding the
1507 1507 contribution by the prompt, or -1 if there is no such column.
1508 1508 """
1509 1509 prompt = self._get_input_buffer_cursor_prompt()
1510 1510 if prompt is None:
1511 1511 return -1
1512 1512 else:
1513 1513 cursor = self._control.textCursor()
1514 1514 return cursor.columnNumber() - len(prompt)
1515 1515
1516 1516 def _get_input_buffer_cursor_line(self):
1517 1517 """ Returns the text of the line of the input buffer that contains the
1518 1518 cursor, or None if there is no such line.
1519 1519 """
1520 1520 prompt = self._get_input_buffer_cursor_prompt()
1521 1521 if prompt is None:
1522 1522 return None
1523 1523 else:
1524 1524 cursor = self._control.textCursor()
1525 1525 text = self._get_block_plain_text(cursor.block())
1526 1526 return text[len(prompt):]
1527 1527
1528 1528 def _get_input_buffer_cursor_prompt(self):
1529 1529 """ Returns the (plain text) prompt for line of the input buffer that
1530 1530 contains the cursor, or None if there is no such line.
1531 1531 """
1532 1532 if self._executing:
1533 1533 return None
1534 1534 cursor = self._control.textCursor()
1535 1535 if cursor.position() >= self._prompt_pos:
1536 1536 if cursor.blockNumber() == self._get_prompt_cursor().blockNumber():
1537 1537 return self._prompt
1538 1538 else:
1539 1539 return self._continuation_prompt
1540 1540 else:
1541 1541 return None
1542 1542
1543 1543 def _get_prompt_cursor(self):
1544 1544 """ Convenience method that returns a cursor for the prompt position.
1545 1545 """
1546 1546 cursor = self._control.textCursor()
1547 1547 cursor.setPosition(self._prompt_pos)
1548 1548 return cursor
1549 1549
1550 1550 def _get_selection_cursor(self, start, end):
1551 1551 """ Convenience method that returns a cursor with text selected between
1552 1552 the positions 'start' and 'end'.
1553 1553 """
1554 1554 cursor = self._control.textCursor()
1555 1555 cursor.setPosition(start)
1556 1556 cursor.setPosition(end, QtGui.QTextCursor.KeepAnchor)
1557 1557 return cursor
1558 1558
1559 1559 def _get_word_start_cursor(self, position):
1560 1560 """ Find the start of the word to the left the given position. If a
1561 1561 sequence of non-word characters precedes the first word, skip over
1562 1562 them. (This emulates the behavior of bash, emacs, etc.)
1563 1563 """
1564 1564 document = self._control.document()
1565 1565 position -= 1
1566 1566 while position >= self._prompt_pos and \
1567 1567 not is_letter_or_number(document.characterAt(position)):
1568 1568 position -= 1
1569 1569 while position >= self._prompt_pos and \
1570 1570 is_letter_or_number(document.characterAt(position)):
1571 1571 position -= 1
1572 1572 cursor = self._control.textCursor()
1573 1573 cursor.setPosition(position + 1)
1574 1574 return cursor
1575 1575
1576 1576 def _get_word_end_cursor(self, position):
1577 1577 """ Find the end of the word to the right the given position. If a
1578 1578 sequence of non-word characters precedes the first word, skip over
1579 1579 them. (This emulates the behavior of bash, emacs, etc.)
1580 1580 """
1581 1581 document = self._control.document()
1582 1582 end = self._get_end_cursor().position()
1583 1583 while position < end and \
1584 1584 not is_letter_or_number(document.characterAt(position)):
1585 1585 position += 1
1586 1586 while position < end and \
1587 1587 is_letter_or_number(document.characterAt(position)):
1588 1588 position += 1
1589 1589 cursor = self._control.textCursor()
1590 1590 cursor.setPosition(position)
1591 1591 return cursor
1592 1592
1593 1593 def _insert_continuation_prompt(self, cursor):
1594 1594 """ Inserts new continuation prompt using the specified cursor.
1595 1595 """
1596 1596 if self._continuation_prompt_html is None:
1597 1597 self._insert_plain_text(cursor, self._continuation_prompt)
1598 1598 else:
1599 1599 self._continuation_prompt = self._insert_html_fetching_plain_text(
1600 1600 cursor, self._continuation_prompt_html)
1601 1601
1602 1602 def _insert_block(self, cursor, block_format=None):
1603 1603 """ Inserts an empty QTextBlock using the specified cursor.
1604 1604 """
1605 1605 if block_format is None:
1606 1606 block_format = QtGui.QTextBlockFormat()
1607 1607 cursor.insertBlock(block_format)
1608 1608
1609 1609 def _insert_html(self, cursor, html):
1610 1610 """ Inserts HTML using the specified cursor in such a way that future
1611 1611 formatting is unaffected.
1612 1612 """
1613 1613 cursor.beginEditBlock()
1614 1614 cursor.insertHtml(html)
1615 1615
1616 1616 # After inserting HTML, the text document "remembers" it's in "html
1617 1617 # mode", which means that subsequent calls adding plain text will result
1618 1618 # in unwanted formatting, lost tab characters, etc. The following code
1619 1619 # hacks around this behavior, which I consider to be a bug in Qt, by
1620 1620 # (crudely) resetting the document's style state.
1621 1621 cursor.movePosition(QtGui.QTextCursor.Left,
1622 1622 QtGui.QTextCursor.KeepAnchor)
1623 1623 if cursor.selection().toPlainText() == ' ':
1624 1624 cursor.removeSelectedText()
1625 1625 else:
1626 1626 cursor.movePosition(QtGui.QTextCursor.Right)
1627 1627 cursor.insertText(' ', QtGui.QTextCharFormat())
1628 1628 cursor.endEditBlock()
1629 1629
1630 1630 def _insert_html_fetching_plain_text(self, cursor, html):
1631 1631 """ Inserts HTML using the specified cursor, then returns its plain text
1632 1632 version.
1633 1633 """
1634 1634 cursor.beginEditBlock()
1635 1635 cursor.removeSelectedText()
1636 1636
1637 1637 start = cursor.position()
1638 1638 self._insert_html(cursor, html)
1639 1639 end = cursor.position()
1640 1640 cursor.setPosition(start, QtGui.QTextCursor.KeepAnchor)
1641 1641 text = cursor.selection().toPlainText()
1642 1642
1643 1643 cursor.setPosition(end)
1644 1644 cursor.endEditBlock()
1645 1645 return text
1646 1646
1647 1647 def _insert_plain_text(self, cursor, text):
1648 1648 """ Inserts plain text using the specified cursor, processing ANSI codes
1649 1649 if enabled.
1650 1650 """
1651 1651 cursor.beginEditBlock()
1652 1652 if self.ansi_codes:
1653 1653 for substring in self._ansi_processor.split_string(text):
1654 1654 for act in self._ansi_processor.actions:
1655 1655
1656 1656 # Unlike real terminal emulators, we don't distinguish
1657 1657 # between the screen and the scrollback buffer. A screen
1658 1658 # erase request clears everything.
1659 1659 if act.action == 'erase' and act.area == 'screen':
1660 1660 cursor.select(QtGui.QTextCursor.Document)
1661 1661 cursor.removeSelectedText()
1662 1662
1663 1663 # Simulate a form feed by scrolling just past the last line.
1664 1664 elif act.action == 'scroll' and act.unit == 'page':
1665 1665 cursor.insertText('\n')
1666 1666 cursor.endEditBlock()
1667 1667 self._set_top_cursor(cursor)
1668 1668 cursor.joinPreviousEditBlock()
1669 1669 cursor.deletePreviousChar()
1670 1670
1671 1671 elif act.action == 'carriage-return':
1672 1672 cursor.movePosition(
1673 1673 cursor.StartOfLine, cursor.KeepAnchor)
1674 1674
1675 1675 elif act.action == 'beep':
1676 1676 QtGui.qApp.beep()
1677 1677
1678 1678 elif act.action == 'backspace':
1679 1679 if not cursor.atBlockStart():
1680 1680 cursor.movePosition(
1681 1681 cursor.PreviousCharacter, cursor.KeepAnchor)
1682 1682
1683 1683 elif act.action == 'newline':
1684 1684 cursor.movePosition(cursor.EndOfLine)
1685 1685
1686 1686 format = self._ansi_processor.get_format()
1687 1687
1688 1688 selection = cursor.selectedText()
1689 1689 if len(selection) == 0:
1690 1690 cursor.insertText(substring, format)
1691 1691 elif substring is not None:
1692 1692 # BS and CR are treated as a change in print
1693 1693 # position, rather than a backwards character
1694 1694 # deletion for output equivalence with (I)Python
1695 1695 # terminal.
1696 1696 if len(substring) >= len(selection):
1697 1697 cursor.insertText(substring, format)
1698 1698 else:
1699 1699 old_text = selection[len(substring):]
1700 1700 cursor.insertText(substring + old_text, format)
1701 1701 cursor.movePosition(cursor.PreviousCharacter,
1702 1702 cursor.KeepAnchor, len(old_text))
1703 1703 else:
1704 1704 cursor.insertText(text)
1705 1705 cursor.endEditBlock()
1706 1706
1707 1707 def _insert_plain_text_into_buffer(self, cursor, text):
1708 1708 """ Inserts text into the input buffer using the specified cursor (which
1709 1709 must be in the input buffer), ensuring that continuation prompts are
1710 1710 inserted as necessary.
1711 1711 """
1712 1712 lines = text.splitlines(True)
1713 1713 if lines:
1714 1714 cursor.beginEditBlock()
1715 1715 cursor.insertText(lines[0])
1716 1716 for line in lines[1:]:
1717 1717 if self._continuation_prompt_html is None:
1718 1718 cursor.insertText(self._continuation_prompt)
1719 1719 else:
1720 1720 self._continuation_prompt = \
1721 1721 self._insert_html_fetching_plain_text(
1722 1722 cursor, self._continuation_prompt_html)
1723 1723 cursor.insertText(line)
1724 1724 cursor.endEditBlock()
1725 1725
1726 1726 def _in_buffer(self, position=None):
1727 1727 """ Returns whether the current cursor (or, if specified, a position) is
1728 1728 inside the editing region.
1729 1729 """
1730 1730 cursor = self._control.textCursor()
1731 1731 if position is None:
1732 1732 position = cursor.position()
1733 1733 else:
1734 1734 cursor.setPosition(position)
1735 1735 line = cursor.blockNumber()
1736 1736 prompt_line = self._get_prompt_cursor().blockNumber()
1737 1737 if line == prompt_line:
1738 1738 return position >= self._prompt_pos
1739 1739 elif line > prompt_line:
1740 1740 cursor.movePosition(QtGui.QTextCursor.StartOfBlock)
1741 1741 prompt_pos = cursor.position() + len(self._continuation_prompt)
1742 1742 return position >= prompt_pos
1743 1743 return False
1744 1744
1745 1745 def _keep_cursor_in_buffer(self):
1746 1746 """ Ensures that the cursor is inside the editing region. Returns
1747 1747 whether the cursor was moved.
1748 1748 """
1749 1749 moved = not self._in_buffer()
1750 1750 if moved:
1751 1751 cursor = self._control.textCursor()
1752 1752 cursor.movePosition(QtGui.QTextCursor.End)
1753 1753 self._control.setTextCursor(cursor)
1754 1754 return moved
1755 1755
1756 1756 def _keyboard_quit(self):
1757 1757 """ Cancels the current editing task ala Ctrl-G in Emacs.
1758 1758 """
1759 1759 if self._temp_buffer_filled :
1760 1760 self._cancel_completion()
1761 1761 self._clear_temporary_buffer()
1762 1762 else:
1763 1763 self.input_buffer = ''
1764 1764
1765 1765 def _page(self, text, html=False):
1766 1766 """ Displays text using the pager if it exceeds the height of the
1767 1767 viewport.
1768 1768
1769 1769 Parameters:
1770 1770 -----------
1771 1771 html : bool, optional (default False)
1772 1772 If set, the text will be interpreted as HTML instead of plain text.
1773 1773 """
1774 1774 line_height = QtGui.QFontMetrics(self.font).height()
1775 1775 minlines = self._control.viewport().height() / line_height
1776 1776 if self.paging != 'none' and \
1777 1777 re.match("(?:[^\n]*\n){%i}" % minlines, text):
1778 1778 if self.paging == 'custom':
1779 1779 self.custom_page_requested.emit(text)
1780 1780 else:
1781 1781 self._page_control.clear()
1782 1782 cursor = self._page_control.textCursor()
1783 1783 if html:
1784 1784 self._insert_html(cursor, text)
1785 1785 else:
1786 1786 self._insert_plain_text(cursor, text)
1787 1787 self._page_control.moveCursor(QtGui.QTextCursor.Start)
1788 1788
1789 1789 self._page_control.viewport().resize(self._control.size())
1790 1790 if self._splitter:
1791 1791 self._page_control.show()
1792 1792 self._page_control.setFocus()
1793 1793 else:
1794 1794 self.layout().setCurrentWidget(self._page_control)
1795 1795 elif html:
1796 1796 self._append_html(text)
1797 1797 else:
1798 1798 self._append_plain_text(text)
1799 1799
1800 1800 def _set_paging(self, paging):
1801 1801 """
1802 1802 Change the pager to `paging` style.
1803 1803
1804 1804 XXX: currently, this is limited to switching between 'hsplit' and
1805 1805 'vsplit'.
1806 1806
1807 1807 Parameters:
1808 1808 -----------
1809 1809 paging : string
1810 1810 Either "hsplit", "vsplit", or "inside"
1811 1811 """
1812 1812 if self._splitter is None:
1813 1813 raise NotImplementedError("""can only switch if --paging=hsplit or
1814 1814 --paging=vsplit is used.""")
1815 1815 if paging == 'hsplit':
1816 1816 self._splitter.setOrientation(QtCore.Qt.Horizontal)
1817 1817 elif paging == 'vsplit':
1818 1818 self._splitter.setOrientation(QtCore.Qt.Vertical)
1819 1819 elif paging == 'inside':
1820 1820 raise NotImplementedError("""switching to 'inside' paging not
1821 1821 supported yet.""")
1822 1822 else:
1823 1823 raise ValueError("unknown paging method '%s'" % paging)
1824 1824 self.paging = paging
1825 1825
1826 1826 def _prompt_finished(self):
1827 1827 """ Called immediately after a prompt is finished, i.e. when some input
1828 1828 will be processed and a new prompt displayed.
1829 1829 """
1830 1830 self._control.setReadOnly(True)
1831 1831 self._prompt_finished_hook()
1832 1832
1833 1833 def _prompt_started(self):
1834 1834 """ Called immediately after a new prompt is displayed.
1835 1835 """
1836 1836 # Temporarily disable the maximum block count to permit undo/redo and
1837 1837 # to ensure that the prompt position does not change due to truncation.
1838 1838 self._control.document().setMaximumBlockCount(0)
1839 1839 self._control.setUndoRedoEnabled(True)
1840 1840
1841 1841 # Work around bug in QPlainTextEdit: input method is not re-enabled
1842 1842 # when read-only is disabled.
1843 1843 self._control.setReadOnly(False)
1844 1844 self._control.setAttribute(QtCore.Qt.WA_InputMethodEnabled, True)
1845 1845
1846 1846 if not self._reading:
1847 1847 self._executing = False
1848 1848 self._prompt_started_hook()
1849 1849
1850 1850 # If the input buffer has changed while executing, load it.
1851 1851 if self._input_buffer_pending:
1852 1852 self.input_buffer = self._input_buffer_pending
1853 1853 self._input_buffer_pending = ''
1854 1854
1855 1855 self._control.moveCursor(QtGui.QTextCursor.End)
1856 1856
1857 1857 def _readline(self, prompt='', callback=None):
1858 1858 """ Reads one line of input from the user.
1859 1859
1860 1860 Parameters
1861 1861 ----------
1862 1862 prompt : str, optional
1863 1863 The prompt to print before reading the line.
1864 1864
1865 1865 callback : callable, optional
1866 1866 A callback to execute with the read line. If not specified, input is
1867 1867 read *synchronously* and this method does not return until it has
1868 1868 been read.
1869 1869
1870 1870 Returns
1871 1871 -------
1872 1872 If a callback is specified, returns nothing. Otherwise, returns the
1873 1873 input string with the trailing newline stripped.
1874 1874 """
1875 1875 if self._reading:
1876 1876 raise RuntimeError('Cannot read a line. Widget is already reading.')
1877 1877
1878 1878 if not callback and not self.isVisible():
1879 1879 # If the user cannot see the widget, this function cannot return.
1880 1880 raise RuntimeError('Cannot synchronously read a line if the widget '
1881 1881 'is not visible!')
1882 1882
1883 1883 self._reading = True
1884 1884 self._show_prompt(prompt, newline=False)
1885 1885
1886 1886 if callback is None:
1887 1887 self._reading_callback = None
1888 1888 while self._reading:
1889 1889 QtCore.QCoreApplication.processEvents()
1890 1890 return self._get_input_buffer(force=True).rstrip('\n')
1891 1891
1892 1892 else:
1893 1893 self._reading_callback = lambda: \
1894 1894 callback(self._get_input_buffer(force=True).rstrip('\n'))
1895 1895
1896 1896 def _set_continuation_prompt(self, prompt, html=False):
1897 1897 """ Sets the continuation prompt.
1898 1898
1899 1899 Parameters
1900 1900 ----------
1901 1901 prompt : str
1902 1902 The prompt to show when more input is needed.
1903 1903
1904 1904 html : bool, optional (default False)
1905 1905 If set, the prompt will be inserted as formatted HTML. Otherwise,
1906 1906 the prompt will be treated as plain text, though ANSI color codes
1907 1907 will be handled.
1908 1908 """
1909 1909 if html:
1910 1910 self._continuation_prompt_html = prompt
1911 1911 else:
1912 1912 self._continuation_prompt = prompt
1913 1913 self._continuation_prompt_html = None
1914 1914
1915 1915 def _set_cursor(self, cursor):
1916 1916 """ Convenience method to set the current cursor.
1917 1917 """
1918 1918 self._control.setTextCursor(cursor)
1919 1919
1920 1920 def _set_top_cursor(self, cursor):
1921 1921 """ Scrolls the viewport so that the specified cursor is at the top.
1922 1922 """
1923 1923 scrollbar = self._control.verticalScrollBar()
1924 1924 scrollbar.setValue(scrollbar.maximum())
1925 1925 original_cursor = self._control.textCursor()
1926 1926 self._control.setTextCursor(cursor)
1927 1927 self._control.ensureCursorVisible()
1928 1928 self._control.setTextCursor(original_cursor)
1929 1929
1930 1930 def _show_prompt(self, prompt=None, html=False, newline=True):
1931 1931 """ Writes a new prompt at the end of the buffer.
1932 1932
1933 1933 Parameters
1934 1934 ----------
1935 1935 prompt : str, optional
1936 1936 The prompt to show. If not specified, the previous prompt is used.
1937 1937
1938 1938 html : bool, optional (default False)
1939 1939 Only relevant when a prompt is specified. If set, the prompt will
1940 1940 be inserted as formatted HTML. Otherwise, the prompt will be treated
1941 1941 as plain text, though ANSI color codes will be handled.
1942 1942
1943 1943 newline : bool, optional (default True)
1944 1944 If set, a new line will be written before showing the prompt if
1945 1945 there is not already a newline at the end of the buffer.
1946 1946 """
1947 1947 # Save the current end position to support _append*(before_prompt=True).
1948 1948 cursor = self._get_end_cursor()
1949 1949 self._append_before_prompt_pos = cursor.position()
1950 1950
1951 1951 # Insert a preliminary newline, if necessary.
1952 1952 if newline and cursor.position() > 0:
1953 1953 cursor.movePosition(QtGui.QTextCursor.Left,
1954 1954 QtGui.QTextCursor.KeepAnchor)
1955 1955 if cursor.selection().toPlainText() != '\n':
1956 1956 self._append_block()
1957 1957
1958 1958 # Write the prompt.
1959 1959 self._append_plain_text(self._prompt_sep)
1960 1960 if prompt is None:
1961 1961 if self._prompt_html is None:
1962 1962 self._append_plain_text(self._prompt)
1963 1963 else:
1964 1964 self._append_html(self._prompt_html)
1965 1965 else:
1966 1966 if html:
1967 1967 self._prompt = self._append_html_fetching_plain_text(prompt)
1968 1968 self._prompt_html = prompt
1969 1969 else:
1970 1970 self._append_plain_text(prompt)
1971 1971 self._prompt = prompt
1972 1972 self._prompt_html = None
1973 1973
1974 1974 self._prompt_pos = self._get_end_cursor().position()
1975 1975 self._prompt_started()
1976 1976
1977 1977 #------ Signal handlers ----------------------------------------------------
1978 1978
1979 1979 def _adjust_scrollbars(self):
1980 1980 """ Expands the vertical scrollbar beyond the range set by Qt.
1981 1981 """
1982 1982 # This code is adapted from _q_adjustScrollbars in qplaintextedit.cpp
1983 1983 # and qtextedit.cpp.
1984 1984 document = self._control.document()
1985 1985 scrollbar = self._control.verticalScrollBar()
1986 1986 viewport_height = self._control.viewport().height()
1987 1987 if isinstance(self._control, QtGui.QPlainTextEdit):
1988 1988 maximum = max(0, document.lineCount() - 1)
1989 1989 step = viewport_height / self._control.fontMetrics().lineSpacing()
1990 1990 else:
1991 1991 # QTextEdit does not do line-based layout and blocks will not in
1992 1992 # general have the same height. Therefore it does not make sense to
1993 1993 # attempt to scroll in line height increments.
1994 1994 maximum = document.size().height()
1995 1995 step = viewport_height
1996 1996 diff = maximum - scrollbar.maximum()
1997 1997 scrollbar.setRange(0, maximum)
1998 1998 scrollbar.setPageStep(step)
1999 1999
2000 2000 # Compensate for undesirable scrolling that occurs automatically due to
2001 2001 # maximumBlockCount() text truncation.
2002 2002 if diff < 0 and document.blockCount() == document.maximumBlockCount():
2003 2003 scrollbar.setValue(scrollbar.value() + diff)
2004 2004
2005 2005 def _custom_context_menu_requested(self, pos):
2006 2006 """ Shows a context menu at the given QPoint (in widget coordinates).
2007 2007 """
2008 2008 menu = self._context_menu_make(pos)
2009 2009 menu.exec_(self._control.mapToGlobal(pos))
@@ -1,785 +1,785 b''
1 1 from __future__ import print_function
2 2
3 3 # Standard library imports
4 4 from collections import namedtuple
5 5 import sys
6 6 import time
7 7 import uuid
8 8
9 9 # System library imports
10 10 from pygments.lexers import PythonLexer
11 11 from IPython.external import qt
12 12 from IPython.external.qt import QtCore, QtGui
13 13
14 14 # Local imports
15 15 from IPython.core.inputsplitter import InputSplitter, IPythonInputSplitter
16 16 from IPython.core.inputtransformer import classic_prompt
17 17 from IPython.core.oinspect import call_tip
18 from IPython.frontend.qt.base_frontend_mixin import BaseFrontendMixin
18 from IPython.qt.base_frontend_mixin import BaseFrontendMixin
19 19 from IPython.utils.traitlets import Bool, Instance, Unicode
20 20 from bracket_matcher import BracketMatcher
21 21 from call_tip_widget import CallTipWidget
22 22 from completion_lexer import CompletionLexer
23 23 from history_console_widget import HistoryConsoleWidget
24 24 from pygments_highlighter import PygmentsHighlighter
25 25
26 26
27 27 class FrontendHighlighter(PygmentsHighlighter):
28 28 """ A PygmentsHighlighter that understands and ignores prompts.
29 29 """
30 30
31 31 def __init__(self, frontend):
32 32 super(FrontendHighlighter, self).__init__(frontend._control.document())
33 33 self._current_offset = 0
34 34 self._frontend = frontend
35 35 self.highlighting_on = False
36 36
37 37 def highlightBlock(self, string):
38 38 """ Highlight a block of text. Reimplemented to highlight selectively.
39 39 """
40 40 if not self.highlighting_on:
41 41 return
42 42
43 43 # The input to this function is a unicode string that may contain
44 44 # paragraph break characters, non-breaking spaces, etc. Here we acquire
45 45 # the string as plain text so we can compare it.
46 46 current_block = self.currentBlock()
47 47 string = self._frontend._get_block_plain_text(current_block)
48 48
49 49 # Decide whether to check for the regular or continuation prompt.
50 50 if current_block.contains(self._frontend._prompt_pos):
51 51 prompt = self._frontend._prompt
52 52 else:
53 53 prompt = self._frontend._continuation_prompt
54 54
55 55 # Only highlight if we can identify a prompt, but make sure not to
56 56 # highlight the prompt.
57 57 if string.startswith(prompt):
58 58 self._current_offset = len(prompt)
59 59 string = string[len(prompt):]
60 60 super(FrontendHighlighter, self).highlightBlock(string)
61 61
62 62 def rehighlightBlock(self, block):
63 63 """ Reimplemented to temporarily enable highlighting if disabled.
64 64 """
65 65 old = self.highlighting_on
66 66 self.highlighting_on = True
67 67 super(FrontendHighlighter, self).rehighlightBlock(block)
68 68 self.highlighting_on = old
69 69
70 70 def setFormat(self, start, count, format):
71 71 """ Reimplemented to highlight selectively.
72 72 """
73 73 start += self._current_offset
74 74 super(FrontendHighlighter, self).setFormat(start, count, format)
75 75
76 76
77 77 class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):
78 78 """ A Qt frontend for a generic Python kernel.
79 79 """
80 80
81 81 # The text to show when the kernel is (re)started.
82 82 banner = Unicode()
83 83
84 84 # An option and corresponding signal for overriding the default kernel
85 85 # interrupt behavior.
86 86 custom_interrupt = Bool(False)
87 87 custom_interrupt_requested = QtCore.Signal()
88 88
89 89 # An option and corresponding signals for overriding the default kernel
90 90 # restart behavior.
91 91 custom_restart = Bool(False)
92 92 custom_restart_kernel_died = QtCore.Signal(float)
93 93 custom_restart_requested = QtCore.Signal()
94 94
95 95 # Whether to automatically show calltips on open-parentheses.
96 96 enable_calltips = Bool(True, config=True,
97 97 help="Whether to draw information calltips on open-parentheses.")
98 98
99 99 clear_on_kernel_restart = Bool(True, config=True,
100 100 help="Whether to clear the console when the kernel is restarted")
101 101
102 102 confirm_restart = Bool(True, config=True,
103 103 help="Whether to ask for user confirmation when restarting kernel")
104 104
105 105 # Emitted when a user visible 'execute_request' has been submitted to the
106 106 # kernel from the FrontendWidget. Contains the code to be executed.
107 107 executing = QtCore.Signal(object)
108 108
109 109 # Emitted when a user-visible 'execute_reply' has been received from the
110 110 # kernel and processed by the FrontendWidget. Contains the response message.
111 111 executed = QtCore.Signal(object)
112 112
113 113 # Emitted when an exit request has been received from the kernel.
114 114 exit_requested = QtCore.Signal(object)
115 115
116 116 # Protected class variables.
117 117 _prompt_transformer = IPythonInputSplitter(physical_line_transforms=[classic_prompt()],
118 118 logical_line_transforms=[],
119 119 python_line_transforms=[],
120 120 )
121 121 _CallTipRequest = namedtuple('_CallTipRequest', ['id', 'pos'])
122 122 _CompletionRequest = namedtuple('_CompletionRequest', ['id', 'pos'])
123 123 _ExecutionRequest = namedtuple('_ExecutionRequest', ['id', 'kind'])
124 124 _input_splitter_class = InputSplitter
125 125 _local_kernel = False
126 126 _highlighter = Instance(FrontendHighlighter)
127 127
128 128 #---------------------------------------------------------------------------
129 129 # 'object' interface
130 130 #---------------------------------------------------------------------------
131 131
132 132 def __init__(self, *args, **kw):
133 133 super(FrontendWidget, self).__init__(*args, **kw)
134 134 # FIXME: remove this when PySide min version is updated past 1.0.7
135 135 # forcefully disable calltips if PySide is < 1.0.7, because they crash
136 136 if qt.QT_API == qt.QT_API_PYSIDE:
137 137 import PySide
138 138 if PySide.__version_info__ < (1,0,7):
139 139 self.log.warn("PySide %s < 1.0.7 detected, disabling calltips" % PySide.__version__)
140 140 self.enable_calltips = False
141 141
142 142 # FrontendWidget protected variables.
143 143 self._bracket_matcher = BracketMatcher(self._control)
144 144 self._call_tip_widget = CallTipWidget(self._control)
145 145 self._completion_lexer = CompletionLexer(PythonLexer())
146 146 self._copy_raw_action = QtGui.QAction('Copy (Raw Text)', None)
147 147 self._hidden = False
148 148 self._highlighter = FrontendHighlighter(self)
149 149 self._input_splitter = self._input_splitter_class()
150 150 self._kernel_manager = None
151 151 self._kernel_client = None
152 152 self._request_info = {}
153 153 self._request_info['execute'] = {};
154 154 self._callback_dict = {}
155 155
156 156 # Configure the ConsoleWidget.
157 157 self.tab_width = 4
158 158 self._set_continuation_prompt('... ')
159 159
160 160 # Configure the CallTipWidget.
161 161 self._call_tip_widget.setFont(self.font)
162 162 self.font_changed.connect(self._call_tip_widget.setFont)
163 163
164 164 # Configure actions.
165 165 action = self._copy_raw_action
166 166 key = QtCore.Qt.CTRL | QtCore.Qt.SHIFT | QtCore.Qt.Key_C
167 167 action.setEnabled(False)
168 168 action.setShortcut(QtGui.QKeySequence(key))
169 169 action.setShortcutContext(QtCore.Qt.WidgetWithChildrenShortcut)
170 170 action.triggered.connect(self.copy_raw)
171 171 self.copy_available.connect(action.setEnabled)
172 172 self.addAction(action)
173 173
174 174 # Connect signal handlers.
175 175 document = self._control.document()
176 176 document.contentsChange.connect(self._document_contents_change)
177 177
178 178 # Set flag for whether we are connected via localhost.
179 179 self._local_kernel = kw.get('local_kernel',
180 180 FrontendWidget._local_kernel)
181 181
182 182 #---------------------------------------------------------------------------
183 183 # 'ConsoleWidget' public interface
184 184 #---------------------------------------------------------------------------
185 185
186 186 def copy(self):
187 187 """ Copy the currently selected text to the clipboard, removing prompts.
188 188 """
189 189 if self._page_control is not None and self._page_control.hasFocus():
190 190 self._page_control.copy()
191 191 elif self._control.hasFocus():
192 192 text = self._control.textCursor().selection().toPlainText()
193 193 if text:
194 194 text = self._prompt_transformer.transform_cell(text)
195 195 QtGui.QApplication.clipboard().setText(text)
196 196 else:
197 197 self.log.debug("frontend widget : unknown copy target")
198 198
199 199 #---------------------------------------------------------------------------
200 200 # 'ConsoleWidget' abstract interface
201 201 #---------------------------------------------------------------------------
202 202
203 203 def _is_complete(self, source, interactive):
204 204 """ Returns whether 'source' can be completely processed and a new
205 205 prompt created. When triggered by an Enter/Return key press,
206 206 'interactive' is True; otherwise, it is False.
207 207 """
208 208 self._input_splitter.reset()
209 209 complete = self._input_splitter.push(source)
210 210 if interactive:
211 211 complete = not self._input_splitter.push_accepts_more()
212 212 return complete
213 213
214 214 def _execute(self, source, hidden):
215 215 """ Execute 'source'. If 'hidden', do not show any output.
216 216
217 217 See parent class :meth:`execute` docstring for full details.
218 218 """
219 219 msg_id = self.kernel_client.execute(source, hidden)
220 220 self._request_info['execute'][msg_id] = self._ExecutionRequest(msg_id, 'user')
221 221 self._hidden = hidden
222 222 if not hidden:
223 223 self.executing.emit(source)
224 224
225 225 def _prompt_started_hook(self):
226 226 """ Called immediately after a new prompt is displayed.
227 227 """
228 228 if not self._reading:
229 229 self._highlighter.highlighting_on = True
230 230
231 231 def _prompt_finished_hook(self):
232 232 """ Called immediately after a prompt is finished, i.e. when some input
233 233 will be processed and a new prompt displayed.
234 234 """
235 235 # Flush all state from the input splitter so the next round of
236 236 # reading input starts with a clean buffer.
237 237 self._input_splitter.reset()
238 238
239 239 if not self._reading:
240 240 self._highlighter.highlighting_on = False
241 241
242 242 def _tab_pressed(self):
243 243 """ Called when the tab key is pressed. Returns whether to continue
244 244 processing the event.
245 245 """
246 246 # Perform tab completion if:
247 247 # 1) The cursor is in the input buffer.
248 248 # 2) There is a non-whitespace character before the cursor.
249 249 text = self._get_input_buffer_cursor_line()
250 250 if text is None:
251 251 return False
252 252 complete = bool(text[:self._get_input_buffer_cursor_column()].strip())
253 253 if complete:
254 254 self._complete()
255 255 return not complete
256 256
257 257 #---------------------------------------------------------------------------
258 258 # 'ConsoleWidget' protected interface
259 259 #---------------------------------------------------------------------------
260 260
261 261 def _context_menu_make(self, pos):
262 262 """ Reimplemented to add an action for raw copy.
263 263 """
264 264 menu = super(FrontendWidget, self)._context_menu_make(pos)
265 265 for before_action in menu.actions():
266 266 if before_action.shortcut().matches(QtGui.QKeySequence.Paste) == \
267 267 QtGui.QKeySequence.ExactMatch:
268 268 menu.insertAction(before_action, self._copy_raw_action)
269 269 break
270 270 return menu
271 271
272 272 def request_interrupt_kernel(self):
273 273 if self._executing:
274 274 self.interrupt_kernel()
275 275
276 276 def request_restart_kernel(self):
277 277 message = 'Are you sure you want to restart the kernel?'
278 278 self.restart_kernel(message, now=False)
279 279
280 280 def _event_filter_console_keypress(self, event):
281 281 """ Reimplemented for execution interruption and smart backspace.
282 282 """
283 283 key = event.key()
284 284 if self._control_key_down(event.modifiers(), include_command=False):
285 285
286 286 if key == QtCore.Qt.Key_C and self._executing:
287 287 self.request_interrupt_kernel()
288 288 return True
289 289
290 290 elif key == QtCore.Qt.Key_Period:
291 291 self.request_restart_kernel()
292 292 return True
293 293
294 294 elif not event.modifiers() & QtCore.Qt.AltModifier:
295 295
296 296 # Smart backspace: remove four characters in one backspace if:
297 297 # 1) everything left of the cursor is whitespace
298 298 # 2) the four characters immediately left of the cursor are spaces
299 299 if key == QtCore.Qt.Key_Backspace:
300 300 col = self._get_input_buffer_cursor_column()
301 301 cursor = self._control.textCursor()
302 302 if col > 3 and not cursor.hasSelection():
303 303 text = self._get_input_buffer_cursor_line()[:col]
304 304 if text.endswith(' ') and not text.strip():
305 305 cursor.movePosition(QtGui.QTextCursor.Left,
306 306 QtGui.QTextCursor.KeepAnchor, 4)
307 307 cursor.removeSelectedText()
308 308 return True
309 309
310 310 return super(FrontendWidget, self)._event_filter_console_keypress(event)
311 311
312 312 def _insert_continuation_prompt(self, cursor):
313 313 """ Reimplemented for auto-indentation.
314 314 """
315 315 super(FrontendWidget, self)._insert_continuation_prompt(cursor)
316 316 cursor.insertText(' ' * self._input_splitter.indent_spaces)
317 317
318 318 #---------------------------------------------------------------------------
319 319 # 'BaseFrontendMixin' abstract interface
320 320 #---------------------------------------------------------------------------
321 321
322 322 def _handle_complete_reply(self, rep):
323 323 """ Handle replies for tab completion.
324 324 """
325 325 self.log.debug("complete: %s", rep.get('content', ''))
326 326 cursor = self._get_cursor()
327 327 info = self._request_info.get('complete')
328 328 if info and info.id == rep['parent_header']['msg_id'] and \
329 329 info.pos == cursor.position():
330 330 text = '.'.join(self._get_context())
331 331 cursor.movePosition(QtGui.QTextCursor.Left, n=len(text))
332 332 self._complete_with_items(cursor, rep['content']['matches'])
333 333
334 334 def _silent_exec_callback(self, expr, callback):
335 335 """Silently execute `expr` in the kernel and call `callback` with reply
336 336
337 337 the `expr` is evaluated silently in the kernel (without) output in
338 338 the frontend. Call `callback` with the
339 339 `repr <http://docs.python.org/library/functions.html#repr> `_ as first argument
340 340
341 341 Parameters
342 342 ----------
343 343 expr : string
344 344 valid string to be executed by the kernel.
345 345 callback : function
346 346 function accepting one argument, as a string. The string will be
347 347 the `repr` of the result of evaluating `expr`
348 348
349 349 The `callback` is called with the `repr()` of the result of `expr` as
350 350 first argument. To get the object, do `eval()` on the passed value.
351 351
352 352 See Also
353 353 --------
354 354 _handle_exec_callback : private method, deal with calling callback with reply
355 355
356 356 """
357 357
358 358 # generate uuid, which would be used as an indication of whether or
359 359 # not the unique request originated from here (can use msg id ?)
360 360 local_uuid = str(uuid.uuid1())
361 361 msg_id = self.kernel_client.execute('',
362 362 silent=True, user_expressions={ local_uuid:expr })
363 363 self._callback_dict[local_uuid] = callback
364 364 self._request_info['execute'][msg_id] = self._ExecutionRequest(msg_id, 'silent_exec_callback')
365 365
366 366 def _handle_exec_callback(self, msg):
367 367 """Execute `callback` corresponding to `msg` reply, after ``_silent_exec_callback``
368 368
369 369 Parameters
370 370 ----------
371 371 msg : raw message send by the kernel containing an `user_expressions`
372 372 and having a 'silent_exec_callback' kind.
373 373
374 374 Notes
375 375 -----
376 376 This function will look for a `callback` associated with the
377 377 corresponding message id. Association has been made by
378 378 `_silent_exec_callback`. `callback` is then called with the `repr()`
379 379 of the value of corresponding `user_expressions` as argument.
380 380 `callback` is then removed from the known list so that any message
381 381 coming again with the same id won't trigger it.
382 382
383 383 """
384 384
385 385 user_exp = msg['content'].get('user_expressions')
386 386 if not user_exp:
387 387 return
388 388 for expression in user_exp:
389 389 if expression in self._callback_dict:
390 390 self._callback_dict.pop(expression)(user_exp[expression])
391 391
392 392 def _handle_execute_reply(self, msg):
393 393 """ Handles replies for code execution.
394 394 """
395 395 self.log.debug("execute: %s", msg.get('content', ''))
396 396 msg_id = msg['parent_header']['msg_id']
397 397 info = self._request_info['execute'].get(msg_id)
398 398 # unset reading flag, because if execute finished, raw_input can't
399 399 # still be pending.
400 400 self._reading = False
401 401 if info and info.kind == 'user' and not self._hidden:
402 402 # Make sure that all output from the SUB channel has been processed
403 403 # before writing a new prompt.
404 404 self.kernel_client.iopub_channel.flush()
405 405
406 406 # Reset the ANSI style information to prevent bad text in stdout
407 407 # from messing up our colors. We're not a true terminal so we're
408 408 # allowed to do this.
409 409 if self.ansi_codes:
410 410 self._ansi_processor.reset_sgr()
411 411
412 412 content = msg['content']
413 413 status = content['status']
414 414 if status == 'ok':
415 415 self._process_execute_ok(msg)
416 416 elif status == 'error':
417 417 self._process_execute_error(msg)
418 418 elif status == 'aborted':
419 419 self._process_execute_abort(msg)
420 420
421 421 self._show_interpreter_prompt_for_reply(msg)
422 422 self.executed.emit(msg)
423 423 self._request_info['execute'].pop(msg_id)
424 424 elif info and info.kind == 'silent_exec_callback' and not self._hidden:
425 425 self._handle_exec_callback(msg)
426 426 self._request_info['execute'].pop(msg_id)
427 427 else:
428 428 super(FrontendWidget, self)._handle_execute_reply(msg)
429 429
430 430 def _handle_input_request(self, msg):
431 431 """ Handle requests for raw_input.
432 432 """
433 433 self.log.debug("input: %s", msg.get('content', ''))
434 434 if self._hidden:
435 435 raise RuntimeError('Request for raw input during hidden execution.')
436 436
437 437 # Make sure that all output from the SUB channel has been processed
438 438 # before entering readline mode.
439 439 self.kernel_client.iopub_channel.flush()
440 440
441 441 def callback(line):
442 442 self.kernel_client.stdin_channel.input(line)
443 443 if self._reading:
444 444 self.log.debug("Got second input request, assuming first was interrupted.")
445 445 self._reading = False
446 446 self._readline(msg['content']['prompt'], callback=callback)
447 447
448 448 def _kernel_restarted_message(self, died=True):
449 449 msg = "Kernel died, restarting" if died else "Kernel restarting"
450 450 self._append_html("<br>%s<hr><br>" % msg,
451 451 before_prompt=False
452 452 )
453 453
454 454 def _handle_kernel_died(self, since_last_heartbeat):
455 455 """Handle the kernel's death (if we do not own the kernel).
456 456 """
457 457 self.log.warn("kernel died: %s", since_last_heartbeat)
458 458 if self.custom_restart:
459 459 self.custom_restart_kernel_died.emit(since_last_heartbeat)
460 460 else:
461 461 self._kernel_restarted_message(died=True)
462 462 self.reset()
463 463
464 464 def _handle_kernel_restarted(self, died=True):
465 465 """Notice that the autorestarter restarted the kernel.
466 466
467 467 There's nothing to do but show a message.
468 468 """
469 469 self.log.warn("kernel restarted")
470 470 self._kernel_restarted_message(died=died)
471 471 self.reset()
472 472
473 473 def _handle_object_info_reply(self, rep):
474 474 """ Handle replies for call tips.
475 475 """
476 476 self.log.debug("oinfo: %s", rep.get('content', ''))
477 477 cursor = self._get_cursor()
478 478 info = self._request_info.get('call_tip')
479 479 if info and info.id == rep['parent_header']['msg_id'] and \
480 480 info.pos == cursor.position():
481 481 # Get the information for a call tip. For now we format the call
482 482 # line as string, later we can pass False to format_call and
483 483 # syntax-highlight it ourselves for nicer formatting in the
484 484 # calltip.
485 485 content = rep['content']
486 486 # if this is from pykernel, 'docstring' will be the only key
487 487 if content.get('ismagic', False):
488 488 # Don't generate a call-tip for magics. Ideally, we should
489 489 # generate a tooltip, but not on ( like we do for actual
490 490 # callables.
491 491 call_info, doc = None, None
492 492 else:
493 493 call_info, doc = call_tip(content, format_call=True)
494 494 if call_info or doc:
495 495 self._call_tip_widget.show_call_info(call_info, doc)
496 496
497 497 def _handle_pyout(self, msg):
498 498 """ Handle display hook output.
499 499 """
500 500 self.log.debug("pyout: %s", msg.get('content', ''))
501 501 if not self._hidden and self._is_from_this_session(msg):
502 502 text = msg['content']['data']
503 503 self._append_plain_text(text + '\n', before_prompt=True)
504 504
505 505 def _handle_stream(self, msg):
506 506 """ Handle stdout, stderr, and stdin.
507 507 """
508 508 self.log.debug("stream: %s", msg.get('content', ''))
509 509 if not self._hidden and self._is_from_this_session(msg):
510 510 # Most consoles treat tabs as being 8 space characters. Convert tabs
511 511 # to spaces so that output looks as expected regardless of this
512 512 # widget's tab width.
513 513 text = msg['content']['data'].expandtabs(8)
514 514
515 515 self._append_plain_text(text, before_prompt=True)
516 516 self._control.moveCursor(QtGui.QTextCursor.End)
517 517
518 518 def _handle_shutdown_reply(self, msg):
519 519 """ Handle shutdown signal, only if from other console.
520 520 """
521 521 self.log.warn("shutdown: %s", msg.get('content', ''))
522 522 restart = msg.get('content', {}).get('restart', False)
523 523 if not self._hidden and not self._is_from_this_session(msg):
524 524 # got shutdown reply, request came from session other than ours
525 525 if restart:
526 526 # someone restarted the kernel, handle it
527 527 self._handle_kernel_restarted(died=False)
528 528 else:
529 529 # kernel was shutdown permanently
530 530 # this triggers exit_requested if the kernel was local,
531 531 # and a dialog if the kernel was remote,
532 532 # so we don't suddenly clear the qtconsole without asking.
533 533 if self._local_kernel:
534 534 self.exit_requested.emit(self)
535 535 else:
536 536 title = self.window().windowTitle()
537 537 reply = QtGui.QMessageBox.question(self, title,
538 538 "Kernel has been shutdown permanently. "
539 539 "Close the Console?",
540 540 QtGui.QMessageBox.Yes,QtGui.QMessageBox.No)
541 541 if reply == QtGui.QMessageBox.Yes:
542 542 self.exit_requested.emit(self)
543 543
544 544 def _handle_status(self, msg):
545 545 """Handle status message"""
546 546 # This is where a busy/idle indicator would be triggered,
547 547 # when we make one.
548 548 state = msg['content'].get('execution_state', '')
549 549 if state == 'starting':
550 550 # kernel started while we were running
551 551 if self._executing:
552 552 self._handle_kernel_restarted(died=True)
553 553 elif state == 'idle':
554 554 pass
555 555 elif state == 'busy':
556 556 pass
557 557
558 558 def _started_channels(self):
559 559 """ Called when the KernelManager channels have started listening or
560 560 when the frontend is assigned an already listening KernelManager.
561 561 """
562 562 self.reset(clear=True)
563 563
564 564 #---------------------------------------------------------------------------
565 565 # 'FrontendWidget' public interface
566 566 #---------------------------------------------------------------------------
567 567
568 568 def copy_raw(self):
569 569 """ Copy the currently selected text to the clipboard without attempting
570 570 to remove prompts or otherwise alter the text.
571 571 """
572 572 self._control.copy()
573 573
574 574 def execute_file(self, path, hidden=False):
575 575 """ Attempts to execute file with 'path'. If 'hidden', no output is
576 576 shown.
577 577 """
578 578 self.execute('execfile(%r)' % path, hidden=hidden)
579 579
580 580 def interrupt_kernel(self):
581 581 """ Attempts to interrupt the running kernel.
582 582
583 583 Also unsets _reading flag, to avoid runtime errors
584 584 if raw_input is called again.
585 585 """
586 586 if self.custom_interrupt:
587 587 self._reading = False
588 588 self.custom_interrupt_requested.emit()
589 589 elif self.kernel_manager:
590 590 self._reading = False
591 591 self.kernel_manager.interrupt_kernel()
592 592 else:
593 593 self._append_plain_text('Cannot interrupt a kernel I did not start.\n')
594 594
595 595 def reset(self, clear=False):
596 596 """ Resets the widget to its initial state if ``clear`` parameter
597 597 is True, otherwise
598 598 prints a visual indication of the fact that the kernel restarted, but
599 599 does not clear the traces from previous usage of the kernel before it
600 600 was restarted. With ``clear=True``, it is similar to ``%clear``, but
601 601 also re-writes the banner and aborts execution if necessary.
602 602 """
603 603 if self._executing:
604 604 self._executing = False
605 605 self._request_info['execute'] = {}
606 606 self._reading = False
607 607 self._highlighter.highlighting_on = False
608 608
609 609 if clear:
610 610 self._control.clear()
611 611 self._append_plain_text(self.banner)
612 612 # update output marker for stdout/stderr, so that startup
613 613 # messages appear after banner:
614 614 self._append_before_prompt_pos = self._get_cursor().position()
615 615 self._show_interpreter_prompt()
616 616
617 617 def restart_kernel(self, message, now=False):
618 618 """ Attempts to restart the running kernel.
619 619 """
620 620 # FIXME: now should be configurable via a checkbox in the dialog. Right
621 621 # now at least the heartbeat path sets it to True and the manual restart
622 622 # to False. But those should just be the pre-selected states of a
623 623 # checkbox that the user could override if so desired. But I don't know
624 624 # enough Qt to go implementing the checkbox now.
625 625
626 626 if self.custom_restart:
627 627 self.custom_restart_requested.emit()
628 628 return
629 629
630 630 if self.kernel_manager:
631 631 # Pause the heart beat channel to prevent further warnings.
632 632 self.kernel_client.hb_channel.pause()
633 633
634 634 # Prompt the user to restart the kernel. Un-pause the heartbeat if
635 635 # they decline. (If they accept, the heartbeat will be un-paused
636 636 # automatically when the kernel is restarted.)
637 637 if self.confirm_restart:
638 638 buttons = QtGui.QMessageBox.Yes | QtGui.QMessageBox.No
639 639 result = QtGui.QMessageBox.question(self, 'Restart kernel?',
640 640 message, buttons)
641 641 do_restart = result == QtGui.QMessageBox.Yes
642 642 else:
643 643 # confirm_restart is False, so we don't need to ask user
644 644 # anything, just do the restart
645 645 do_restart = True
646 646 if do_restart:
647 647 try:
648 648 self.kernel_manager.restart_kernel(now=now)
649 649 except RuntimeError as e:
650 650 self._append_plain_text(
651 651 'Error restarting kernel: %s\n' % e,
652 652 before_prompt=True
653 653 )
654 654 else:
655 655 self._append_html("<br>Restarting kernel...\n<hr><br>",
656 656 before_prompt=True,
657 657 )
658 658 else:
659 659 self.kernel_client.hb_channel.unpause()
660 660
661 661 else:
662 662 self._append_plain_text(
663 663 'Cannot restart a Kernel I did not start\n',
664 664 before_prompt=True
665 665 )
666 666
667 667 #---------------------------------------------------------------------------
668 668 # 'FrontendWidget' protected interface
669 669 #---------------------------------------------------------------------------
670 670
671 671 def _call_tip(self):
672 672 """ Shows a call tip, if appropriate, at the current cursor location.
673 673 """
674 674 # Decide if it makes sense to show a call tip
675 675 if not self.enable_calltips:
676 676 return False
677 677 cursor = self._get_cursor()
678 678 cursor.movePosition(QtGui.QTextCursor.Left)
679 679 if cursor.document().characterAt(cursor.position()) != '(':
680 680 return False
681 681 context = self._get_context(cursor)
682 682 if not context:
683 683 return False
684 684
685 685 # Send the metadata request to the kernel
686 686 name = '.'.join(context)
687 687 msg_id = self.kernel_client.object_info(name)
688 688 pos = self._get_cursor().position()
689 689 self._request_info['call_tip'] = self._CallTipRequest(msg_id, pos)
690 690 return True
691 691
692 692 def _complete(self):
693 693 """ Performs completion at the current cursor location.
694 694 """
695 695 context = self._get_context()
696 696 if context:
697 697 # Send the completion request to the kernel
698 698 msg_id = self.kernel_client.complete(
699 699 '.'.join(context), # text
700 700 self._get_input_buffer_cursor_line(), # line
701 701 self._get_input_buffer_cursor_column(), # cursor_pos
702 702 self.input_buffer) # block
703 703 pos = self._get_cursor().position()
704 704 info = self._CompletionRequest(msg_id, pos)
705 705 self._request_info['complete'] = info
706 706
707 707 def _get_context(self, cursor=None):
708 708 """ Gets the context for the specified cursor (or the current cursor
709 709 if none is specified).
710 710 """
711 711 if cursor is None:
712 712 cursor = self._get_cursor()
713 713 cursor.movePosition(QtGui.QTextCursor.StartOfBlock,
714 714 QtGui.QTextCursor.KeepAnchor)
715 715 text = cursor.selection().toPlainText()
716 716 return self._completion_lexer.get_context(text)
717 717
718 718 def _process_execute_abort(self, msg):
719 719 """ Process a reply for an aborted execution request.
720 720 """
721 721 self._append_plain_text("ERROR: execution aborted\n")
722 722
723 723 def _process_execute_error(self, msg):
724 724 """ Process a reply for an execution request that resulted in an error.
725 725 """
726 726 content = msg['content']
727 727 # If a SystemExit is passed along, this means exit() was called - also
728 728 # all the ipython %exit magic syntax of '-k' to be used to keep
729 729 # the kernel running
730 730 if content['ename']=='SystemExit':
731 731 keepkernel = content['evalue']=='-k' or content['evalue']=='True'
732 732 self._keep_kernel_on_exit = keepkernel
733 733 self.exit_requested.emit(self)
734 734 else:
735 735 traceback = ''.join(content['traceback'])
736 736 self._append_plain_text(traceback)
737 737
738 738 def _process_execute_ok(self, msg):
739 739 """ Process a reply for a successful execution request.
740 740 """
741 741 payload = msg['content']['payload']
742 742 for item in payload:
743 743 if not self._process_execute_payload(item):
744 744 warning = 'Warning: received unknown payload of type %s'
745 745 print(warning % repr(item['source']))
746 746
747 747 def _process_execute_payload(self, item):
748 748 """ Process a single payload item from the list of payload items in an
749 749 execution reply. Returns whether the payload was handled.
750 750 """
751 751 # The basic FrontendWidget doesn't handle payloads, as they are a
752 752 # mechanism for going beyond the standard Python interpreter model.
753 753 return False
754 754
755 755 def _show_interpreter_prompt(self):
756 756 """ Shows a prompt for the interpreter.
757 757 """
758 758 self._show_prompt('>>> ')
759 759
760 760 def _show_interpreter_prompt_for_reply(self, msg):
761 761 """ Shows a prompt for the interpreter given an 'execute_reply' message.
762 762 """
763 763 self._show_interpreter_prompt()
764 764
765 765 #------ Signal handlers ----------------------------------------------------
766 766
767 767 def _document_contents_change(self, position, removed, added):
768 768 """ Called whenever the document's content changes. Display a call tip
769 769 if appropriate.
770 770 """
771 771 # Calculate where the cursor should be *after* the change:
772 772 position += added
773 773
774 774 document = self._control.document()
775 775 if position == self._get_cursor().position():
776 776 self._call_tip()
777 777
778 778 #------ Trait default initializers -----------------------------------------
779 779
780 780 def _banner_default(self):
781 781 """ Returns the standard Python banner.
782 782 """
783 783 banner = 'Python %s on %s\nType "help", "copyright", "credits" or ' \
784 784 '"license" for more information.'
785 785 return banner % (sys.version, sys.platform)
1 NO CONTENT: file renamed from IPython/frontend/qt/console/history_console_widget.py to IPython/qt/console/history_console_widget.py
1 NO CONTENT: file renamed from IPython/frontend/qt/console/ipython_widget.py to IPython/qt/console/ipython_widget.py
1 NO CONTENT: file renamed from IPython/frontend/qt/console/kill_ring.py to IPython/qt/console/kill_ring.py
1 NO CONTENT: file renamed from IPython/frontend/qt/console/mainwindow.py to IPython/qt/console/mainwindow.py
1 NO CONTENT: file renamed from IPython/frontend/qt/console/pygments_highlighter.py to IPython/qt/console/pygments_highlighter.py
@@ -1,388 +1,388 b''
1 1 """ A minimal application using the Qt console-style IPython frontend.
2 2
3 3 This is not a complete console app, as subprocess will not be able to receive
4 4 input, there is no real readline support, among other limitations.
5 5
6 6 Authors:
7 7
8 8 * Evan Patterson
9 9 * Min RK
10 10 * Erik Tollerud
11 11 * Fernando Perez
12 12 * Bussonnier Matthias
13 13 * Thomas Kluyver
14 14 * Paul Ivanov
15 15
16 16 """
17 17
18 18 #-----------------------------------------------------------------------------
19 19 # Imports
20 20 #-----------------------------------------------------------------------------
21 21
22 22 # stdlib imports
23 23 import os
24 24 import signal
25 25 import sys
26 26
27 27 # If run on Windows, install an exception hook which pops up a
28 28 # message box. Pythonw.exe hides the console, so without this
29 29 # the application silently fails to load.
30 30 #
31 31 # We always install this handler, because the expectation is for
32 32 # qtconsole to bring up a GUI even if called from the console.
33 33 # The old handler is called, so the exception is printed as well.
34 34 # If desired, check for pythonw with an additional condition
35 35 # (sys.executable.lower().find('pythonw.exe') >= 0).
36 36 if os.name == 'nt':
37 37 old_excepthook = sys.excepthook
38 38
39 39 def gui_excepthook(exctype, value, tb):
40 40 try:
41 41 import ctypes, traceback
42 42 MB_ICONERROR = 0x00000010L
43 43 title = u'Error starting IPython QtConsole'
44 44 msg = u''.join(traceback.format_exception(exctype, value, tb))
45 45 ctypes.windll.user32.MessageBoxW(0, msg, title, MB_ICONERROR)
46 46 finally:
47 47 # Also call the old exception hook to let it do
48 48 # its thing too.
49 49 old_excepthook(exctype, value, tb)
50 50
51 51 sys.excepthook = gui_excepthook
52 52
53 53 # System library imports
54 54 from IPython.external.qt import QtCore, QtGui
55 55
56 56 # Local imports
57 57 from IPython.config.application import boolean_flag, catch_config_error
58 58 from IPython.core.application import BaseIPythonApplication
59 59 from IPython.core.profiledir import ProfileDir
60 from IPython.frontend.qt.console.ipython_widget import IPythonWidget
61 from IPython.frontend.qt.console.rich_ipython_widget import RichIPythonWidget
62 from IPython.frontend.qt.console import styles
63 from IPython.frontend.qt.console.mainwindow import MainWindow
64 from IPython.frontend.qt.client import QtKernelClient
65 from IPython.frontend.qt.manager import QtKernelManager
60 from IPython.qt.console.ipython_widget import IPythonWidget
61 from IPython.qt.console.rich_ipython_widget import RichIPythonWidget
62 from IPython.qt.console import styles
63 from IPython.qt.console.mainwindow import MainWindow
64 from IPython.qt.client import QtKernelClient
65 from IPython.qt.manager import QtKernelManager
66 66 from IPython.kernel import tunnel_to_kernel, find_connection_file
67 67 from IPython.utils.traitlets import (
68 68 Dict, List, Unicode, CBool, Any
69 69 )
70 70 from IPython.kernel.zmq.session import default_secure
71 71
72 from IPython.frontend.consoleapp import (
72 from IPython.consoleapp import (
73 73 IPythonConsoleApp, app_aliases, app_flags, flags, aliases
74 74 )
75 75
76 76 #-----------------------------------------------------------------------------
77 77 # Network Constants
78 78 #-----------------------------------------------------------------------------
79 79
80 80 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
81 81
82 82 #-----------------------------------------------------------------------------
83 83 # Globals
84 84 #-----------------------------------------------------------------------------
85 85
86 86 _examples = """
87 87 ipython qtconsole # start the qtconsole
88 88 ipython qtconsole --pylab=inline # start with pylab in inline plotting mode
89 89 """
90 90
91 91 #-----------------------------------------------------------------------------
92 92 # Aliases and Flags
93 93 #-----------------------------------------------------------------------------
94 94
95 95 # start with copy of flags
96 96 flags = dict(flags)
97 97 qt_flags = {
98 98 'plain' : ({'IPythonQtConsoleApp' : {'plain' : True}},
99 99 "Disable rich text support."),
100 100 }
101 101
102 102 # and app_flags from the Console Mixin
103 103 qt_flags.update(app_flags)
104 104 # add frontend flags to the full set
105 105 flags.update(qt_flags)
106 106
107 107 # start with copy of front&backend aliases list
108 108 aliases = dict(aliases)
109 109 qt_aliases = dict(
110 110 style = 'IPythonWidget.syntax_style',
111 111 stylesheet = 'IPythonQtConsoleApp.stylesheet',
112 112 colors = 'ZMQInteractiveShell.colors',
113 113
114 114 editor = 'IPythonWidget.editor',
115 115 paging = 'ConsoleWidget.paging',
116 116 )
117 117 # and app_aliases from the Console Mixin
118 118 qt_aliases.update(app_aliases)
119 119 qt_aliases.update({'gui-completion':'ConsoleWidget.gui_completion'})
120 120 # add frontend aliases to the full set
121 121 aliases.update(qt_aliases)
122 122
123 123 # get flags&aliases into sets, and remove a couple that
124 124 # shouldn't be scrubbed from backend flags:
125 125 qt_aliases = set(qt_aliases.keys())
126 126 qt_aliases.remove('colors')
127 127 qt_flags = set(qt_flags.keys())
128 128
129 129 #-----------------------------------------------------------------------------
130 130 # Classes
131 131 #-----------------------------------------------------------------------------
132 132
133 133 #-----------------------------------------------------------------------------
134 134 # IPythonQtConsole
135 135 #-----------------------------------------------------------------------------
136 136
137 137
138 138 class IPythonQtConsoleApp(BaseIPythonApplication, IPythonConsoleApp):
139 139 name = 'ipython-qtconsole'
140 140
141 141 description = """
142 142 The IPython QtConsole.
143 143
144 144 This launches a Console-style application using Qt. It is not a full
145 145 console, in that launched terminal subprocesses will not be able to accept
146 146 input.
147 147
148 148 The QtConsole supports various extra features beyond the Terminal IPython
149 149 shell, such as inline plotting with matplotlib, via:
150 150
151 151 ipython qtconsole --pylab=inline
152 152
153 153 as well as saving your session as HTML, and printing the output.
154 154
155 155 """
156 156 examples = _examples
157 157
158 158 classes = [IPythonWidget] + IPythonConsoleApp.classes
159 159 flags = Dict(flags)
160 160 aliases = Dict(aliases)
161 161 frontend_flags = Any(qt_flags)
162 162 frontend_aliases = Any(qt_aliases)
163 163 kernel_client_class = QtKernelClient
164 164 kernel_manager_class = QtKernelManager
165 165
166 166 stylesheet = Unicode('', config=True,
167 167 help="path to a custom CSS stylesheet")
168 168
169 169 hide_menubar = CBool(False, config=True,
170 170 help="Start the console window with the menu bar hidden.")
171 171
172 172 maximize = CBool(False, config=True,
173 173 help="Start the console window maximized.")
174 174
175 175 plain = CBool(False, config=True,
176 176 help="Use a plaintext widget instead of rich text (plain can't print/save).")
177 177
178 178 def _plain_changed(self, name, old, new):
179 179 kind = 'plain' if new else 'rich'
180 180 self.config.ConsoleWidget.kind = kind
181 181 if new:
182 182 self.widget_factory = IPythonWidget
183 183 else:
184 184 self.widget_factory = RichIPythonWidget
185 185
186 186 # the factory for creating a widget
187 187 widget_factory = Any(RichIPythonWidget)
188 188
189 189 def parse_command_line(self, argv=None):
190 190 super(IPythonQtConsoleApp, self).parse_command_line(argv)
191 191 self.build_kernel_argv(argv)
192 192
193 193
194 194 def new_frontend_master(self):
195 195 """ Create and return new frontend attached to new kernel, launched on localhost.
196 196 """
197 197 kernel_manager = self.kernel_manager_class(
198 198 connection_file=self._new_connection_file(),
199 199 config=self.config,
200 200 autorestart=True,
201 201 )
202 202 # start the kernel
203 203 kwargs = dict()
204 204 kwargs['extra_arguments'] = self.kernel_argv
205 205 kernel_manager.start_kernel(**kwargs)
206 206 kernel_manager.client_factory = self.kernel_client_class
207 207 kernel_client = kernel_manager.client()
208 208 kernel_client.start_channels(shell=True, iopub=True)
209 209 widget = self.widget_factory(config=self.config,
210 210 local_kernel=True)
211 211 self.init_colors(widget)
212 212 widget.kernel_manager = kernel_manager
213 213 widget.kernel_client = kernel_client
214 214 widget._existing = False
215 215 widget._may_close = True
216 216 widget._confirm_exit = self.confirm_exit
217 217 return widget
218 218
219 219 def new_frontend_slave(self, current_widget):
220 220 """Create and return a new frontend attached to an existing kernel.
221 221
222 222 Parameters
223 223 ----------
224 224 current_widget : IPythonWidget
225 225 The IPythonWidget whose kernel this frontend is to share
226 226 """
227 227 kernel_client = self.kernel_client_class(
228 228 connection_file=current_widget.kernel_client.connection_file,
229 229 config = self.config,
230 230 )
231 231 kernel_client.load_connection_file()
232 232 kernel_client.start_channels()
233 233 widget = self.widget_factory(config=self.config,
234 234 local_kernel=False)
235 235 self.init_colors(widget)
236 236 widget._existing = True
237 237 widget._may_close = False
238 238 widget._confirm_exit = False
239 239 widget.kernel_client = kernel_client
240 240 widget.kernel_manager = current_widget.kernel_manager
241 241 return widget
242 242
243 243 def init_qt_app(self):
244 244 # separate from qt_elements, because it must run first
245 245 self.app = QtGui.QApplication([])
246 246
247 247 def init_qt_elements(self):
248 248 # Create the widget.
249 249
250 250 base_path = os.path.abspath(os.path.dirname(__file__))
251 251 icon_path = os.path.join(base_path, 'resources', 'icon', 'IPythonConsole.svg')
252 252 self.app.icon = QtGui.QIcon(icon_path)
253 253 QtGui.QApplication.setWindowIcon(self.app.icon)
254 254
255 255 try:
256 256 ip = self.config.KernelManager.ip
257 257 except AttributeError:
258 258 ip = LOCALHOST
259 259 local_kernel = (not self.existing) or ip in LOCAL_IPS
260 260 self.widget = self.widget_factory(config=self.config,
261 261 local_kernel=local_kernel)
262 262 self.init_colors(self.widget)
263 263 self.widget._existing = self.existing
264 264 self.widget._may_close = not self.existing
265 265 self.widget._confirm_exit = self.confirm_exit
266 266
267 267 self.widget.kernel_manager = self.kernel_manager
268 268 self.widget.kernel_client = self.kernel_client
269 269 self.window = MainWindow(self.app,
270 270 confirm_exit=self.confirm_exit,
271 271 new_frontend_factory=self.new_frontend_master,
272 272 slave_frontend_factory=self.new_frontend_slave,
273 273 )
274 274 self.window.log = self.log
275 275 self.window.add_tab_with_frontend(self.widget)
276 276 self.window.init_menu_bar()
277 277
278 278 # Ignore on OSX, where there is always a menu bar
279 279 if sys.platform != 'darwin' and self.hide_menubar:
280 280 self.window.menuBar().setVisible(False)
281 281
282 282 self.window.setWindowTitle('IPython')
283 283
284 284 def init_colors(self, widget):
285 285 """Configure the coloring of the widget"""
286 286 # Note: This will be dramatically simplified when colors
287 287 # are removed from the backend.
288 288
289 289 # parse the colors arg down to current known labels
290 290 try:
291 291 colors = self.config.ZMQInteractiveShell.colors
292 292 except AttributeError:
293 293 colors = None
294 294 try:
295 295 style = self.config.IPythonWidget.syntax_style
296 296 except AttributeError:
297 297 style = None
298 298 try:
299 299 sheet = self.config.IPythonWidget.style_sheet
300 300 except AttributeError:
301 301 sheet = None
302 302
303 303 # find the value for colors:
304 304 if colors:
305 305 colors=colors.lower()
306 306 if colors in ('lightbg', 'light'):
307 307 colors='lightbg'
308 308 elif colors in ('dark', 'linux'):
309 309 colors='linux'
310 310 else:
311 311 colors='nocolor'
312 312 elif style:
313 313 if style=='bw':
314 314 colors='nocolor'
315 315 elif styles.dark_style(style):
316 316 colors='linux'
317 317 else:
318 318 colors='lightbg'
319 319 else:
320 320 colors=None
321 321
322 322 # Configure the style
323 323 if style:
324 324 widget.style_sheet = styles.sheet_from_template(style, colors)
325 325 widget.syntax_style = style
326 326 widget._syntax_style_changed()
327 327 widget._style_sheet_changed()
328 328 elif colors:
329 329 # use a default dark/light/bw style
330 330 widget.set_default_style(colors=colors)
331 331
332 332 if self.stylesheet:
333 333 # we got an explicit stylesheet
334 334 if os.path.isfile(self.stylesheet):
335 335 with open(self.stylesheet) as f:
336 336 sheet = f.read()
337 337 else:
338 338 raise IOError("Stylesheet %r not found." % self.stylesheet)
339 339 if sheet:
340 340 widget.style_sheet = sheet
341 341 widget._style_sheet_changed()
342 342
343 343
344 344 def init_signal(self):
345 345 """allow clean shutdown on sigint"""
346 346 signal.signal(signal.SIGINT, lambda sig, frame: self.exit(-2))
347 347 # need a timer, so that QApplication doesn't block until a real
348 348 # Qt event fires (can require mouse movement)
349 349 # timer trick from http://stackoverflow.com/q/4938723/938949
350 350 timer = QtCore.QTimer()
351 351 # Let the interpreter run each 200 ms:
352 352 timer.timeout.connect(lambda: None)
353 353 timer.start(200)
354 354 # hold onto ref, so the timer doesn't get cleaned up
355 355 self._sigint_timer = timer
356 356
357 357 @catch_config_error
358 358 def initialize(self, argv=None):
359 359 self.init_qt_app()
360 360 super(IPythonQtConsoleApp, self).initialize(argv)
361 361 IPythonConsoleApp.initialize(self,argv)
362 362 self.init_qt_elements()
363 363 self.init_signal()
364 364
365 365 def start(self):
366 366
367 367 # draw the window
368 368 if self.maximize:
369 369 self.window.showMaximized()
370 370 else:
371 371 self.window.show()
372 372 self.window.raise_()
373 373
374 374 # Start the application main loop.
375 375 self.app.exec_()
376 376
377 377 #-----------------------------------------------------------------------------
378 378 # Main entry point
379 379 #-----------------------------------------------------------------------------
380 380
381 381 def main():
382 382 app = IPythonQtConsoleApp()
383 383 app.initialize()
384 384 app.start()
385 385
386 386
387 387 if __name__ == '__main__':
388 388 main()
1 NO CONTENT: file renamed from IPython/frontend/qt/console/resources/icon/IPythonConsole.svg to IPython/qt/console/resources/icon/IPythonConsole.svg
@@ -1,325 +1,325 b''
1 1 #-----------------------------------------------------------------------------
2 2 # Copyright (c) 2010, IPython Development Team.
3 3 #
4 4 # Distributed under the terms of the Modified BSD License.
5 5 #
6 6 # The full license is in the file COPYING.txt, distributed with this software.
7 7 #-----------------------------------------------------------------------------
8 8
9 9 # Standard libary imports.
10 10 from base64 import decodestring
11 11 import os
12 12 import re
13 13
14 14 # System libary imports.
15 15 from IPython.external.qt import QtCore, QtGui
16 16
17 17 # Local imports
18 18 from IPython.utils.traitlets import Bool
19 from IPython.frontend.qt.svg import save_svg, svg_to_clipboard, svg_to_image
19 from IPython.qt.svg import save_svg, svg_to_clipboard, svg_to_image
20 20 from ipython_widget import IPythonWidget
21 21
22 22
23 23 class RichIPythonWidget(IPythonWidget):
24 24 """ An IPythonWidget that supports rich text, including lists, images, and
25 25 tables. Note that raw performance will be reduced compared to the plain
26 26 text version.
27 27 """
28 28
29 29 # RichIPythonWidget protected class variables.
30 30 _payload_source_plot = 'IPython.kernel.zmq.pylab.backend_payload.add_plot_payload'
31 31 _jpg_supported = Bool(False)
32 32
33 33 # Used to determine whether a given html export attempt has already
34 34 # displayed a warning about being unable to convert a png to svg.
35 35 _svg_warning_displayed = False
36 36
37 37 #---------------------------------------------------------------------------
38 38 # 'object' interface
39 39 #---------------------------------------------------------------------------
40 40
41 41 def __init__(self, *args, **kw):
42 42 """ Create a RichIPythonWidget.
43 43 """
44 44 kw['kind'] = 'rich'
45 45 super(RichIPythonWidget, self).__init__(*args, **kw)
46 46
47 47 # Configure the ConsoleWidget HTML exporter for our formats.
48 48 self._html_exporter.image_tag = self._get_image_tag
49 49
50 50 # Dictionary for resolving document resource names to SVG data.
51 51 self._name_to_svg_map = {}
52 52
53 53 # Do we support jpg ?
54 54 # it seems that sometime jpg support is a plugin of QT, so try to assume
55 55 # it is not always supported.
56 56 _supported_format = map(str, QtGui.QImageReader.supportedImageFormats())
57 57 self._jpg_supported = 'jpeg' in _supported_format
58 58
59 59
60 60 #---------------------------------------------------------------------------
61 61 # 'ConsoleWidget' public interface overides
62 62 #---------------------------------------------------------------------------
63 63
64 64 def export_html(self):
65 65 """ Shows a dialog to export HTML/XML in various formats.
66 66
67 67 Overridden in order to reset the _svg_warning_displayed flag prior
68 68 to the export running.
69 69 """
70 70 self._svg_warning_displayed = False
71 71 super(RichIPythonWidget, self).export_html()
72 72
73 73
74 74 #---------------------------------------------------------------------------
75 75 # 'ConsoleWidget' protected interface
76 76 #---------------------------------------------------------------------------
77 77
78 78 def _context_menu_make(self, pos):
79 79 """ Reimplemented to return a custom context menu for images.
80 80 """
81 81 format = self._control.cursorForPosition(pos).charFormat()
82 82 name = format.stringProperty(QtGui.QTextFormat.ImageName)
83 83 if name:
84 84 menu = QtGui.QMenu()
85 85
86 86 menu.addAction('Copy Image', lambda: self._copy_image(name))
87 87 menu.addAction('Save Image As...', lambda: self._save_image(name))
88 88 menu.addSeparator()
89 89
90 90 svg = self._name_to_svg_map.get(name, None)
91 91 if svg is not None:
92 92 menu.addSeparator()
93 93 menu.addAction('Copy SVG', lambda: svg_to_clipboard(svg))
94 94 menu.addAction('Save SVG As...',
95 95 lambda: save_svg(svg, self._control))
96 96 else:
97 97 menu = super(RichIPythonWidget, self)._context_menu_make(pos)
98 98 return menu
99 99
100 100 #---------------------------------------------------------------------------
101 101 # 'BaseFrontendMixin' abstract interface
102 102 #---------------------------------------------------------------------------
103 103 def _pre_image_append(self, msg, prompt_number):
104 104 """ Append the Out[] prompt and make the output nicer
105 105
106 106 Shared code for some the following if statement
107 107 """
108 108 self.log.debug("pyout: %s", msg.get('content', ''))
109 109 self._append_plain_text(self.output_sep, True)
110 110 self._append_html(self._make_out_prompt(prompt_number), True)
111 111 self._append_plain_text('\n', True)
112 112
113 113 def _handle_pyout(self, msg):
114 114 """ Overridden to handle rich data types, like SVG.
115 115 """
116 116 if not self._hidden and self._is_from_this_session(msg):
117 117 content = msg['content']
118 118 prompt_number = content.get('execution_count', 0)
119 119 data = content['data']
120 120 if 'image/svg+xml' in data:
121 121 self._pre_image_append(msg, prompt_number)
122 122 self._append_svg(data['image/svg+xml'], True)
123 123 self._append_html(self.output_sep2, True)
124 124 elif 'image/png' in data:
125 125 self._pre_image_append(msg, prompt_number)
126 126 self._append_png(decodestring(data['image/png'].encode('ascii')), True)
127 127 self._append_html(self.output_sep2, True)
128 128 elif 'image/jpeg' in data and self._jpg_supported:
129 129 self._pre_image_append(msg, prompt_number)
130 130 self._append_jpg(decodestring(data['image/jpeg'].encode('ascii')), True)
131 131 self._append_html(self.output_sep2, True)
132 132 else:
133 133 # Default back to the plain text representation.
134 134 return super(RichIPythonWidget, self)._handle_pyout(msg)
135 135
136 136 def _handle_display_data(self, msg):
137 137 """ Overridden to handle rich data types, like SVG.
138 138 """
139 139 if not self._hidden and self._is_from_this_session(msg):
140 140 source = msg['content']['source']
141 141 data = msg['content']['data']
142 142 metadata = msg['content']['metadata']
143 143 # Try to use the svg or html representations.
144 144 # FIXME: Is this the right ordering of things to try?
145 145 if 'image/svg+xml' in data:
146 146 self.log.debug("display: %s", msg.get('content', ''))
147 147 svg = data['image/svg+xml']
148 148 self._append_svg(svg, True)
149 149 elif 'image/png' in data:
150 150 self.log.debug("display: %s", msg.get('content', ''))
151 151 # PNG data is base64 encoded as it passes over the network
152 152 # in a JSON structure so we decode it.
153 153 png = decodestring(data['image/png'].encode('ascii'))
154 154 self._append_png(png, True)
155 155 elif 'image/jpeg' in data and self._jpg_supported:
156 156 self.log.debug("display: %s", msg.get('content', ''))
157 157 jpg = decodestring(data['image/jpeg'].encode('ascii'))
158 158 self._append_jpg(jpg, True)
159 159 else:
160 160 # Default back to the plain text representation.
161 161 return super(RichIPythonWidget, self)._handle_display_data(msg)
162 162
163 163 #---------------------------------------------------------------------------
164 164 # 'RichIPythonWidget' protected interface
165 165 #---------------------------------------------------------------------------
166 166
167 167 def _append_jpg(self, jpg, before_prompt=False):
168 168 """ Append raw JPG data to the widget."""
169 169 self._append_custom(self._insert_jpg, jpg, before_prompt)
170 170
171 171 def _append_png(self, png, before_prompt=False):
172 172 """ Append raw PNG data to the widget.
173 173 """
174 174 self._append_custom(self._insert_png, png, before_prompt)
175 175
176 176 def _append_svg(self, svg, before_prompt=False):
177 177 """ Append raw SVG data to the widget.
178 178 """
179 179 self._append_custom(self._insert_svg, svg, before_prompt)
180 180
181 181 def _add_image(self, image):
182 182 """ Adds the specified QImage to the document and returns a
183 183 QTextImageFormat that references it.
184 184 """
185 185 document = self._control.document()
186 186 name = str(image.cacheKey())
187 187 document.addResource(QtGui.QTextDocument.ImageResource,
188 188 QtCore.QUrl(name), image)
189 189 format = QtGui.QTextImageFormat()
190 190 format.setName(name)
191 191 return format
192 192
193 193 def _copy_image(self, name):
194 194 """ Copies the ImageResource with 'name' to the clipboard.
195 195 """
196 196 image = self._get_image(name)
197 197 QtGui.QApplication.clipboard().setImage(image)
198 198
199 199 def _get_image(self, name):
200 200 """ Returns the QImage stored as the ImageResource with 'name'.
201 201 """
202 202 document = self._control.document()
203 203 image = document.resource(QtGui.QTextDocument.ImageResource,
204 204 QtCore.QUrl(name))
205 205 return image
206 206
207 207 def _get_image_tag(self, match, path = None, format = "png"):
208 208 """ Return (X)HTML mark-up for the image-tag given by match.
209 209
210 210 Parameters
211 211 ----------
212 212 match : re.SRE_Match
213 213 A match to an HTML image tag as exported by Qt, with
214 214 match.group("Name") containing the matched image ID.
215 215
216 216 path : string|None, optional [default None]
217 217 If not None, specifies a path to which supporting files may be
218 218 written (e.g., for linked images). If None, all images are to be
219 219 included inline.
220 220
221 221 format : "png"|"svg"|"jpg", optional [default "png"]
222 222 Format for returned or referenced images.
223 223 """
224 224 if format in ("png","jpg"):
225 225 try:
226 226 image = self._get_image(match.group("name"))
227 227 except KeyError:
228 228 return "<b>Couldn't find image %s</b>" % match.group("name")
229 229
230 230 if path is not None:
231 231 if not os.path.exists(path):
232 232 os.mkdir(path)
233 233 relpath = os.path.basename(path)
234 234 if image.save("%s/qt_img%s.%s" % (path, match.group("name"), format),
235 235 "PNG"):
236 236 return '<img src="%s/qt_img%s.%s">' % (relpath,
237 237 match.group("name"),format)
238 238 else:
239 239 return "<b>Couldn't save image!</b>"
240 240 else:
241 241 ba = QtCore.QByteArray()
242 242 buffer_ = QtCore.QBuffer(ba)
243 243 buffer_.open(QtCore.QIODevice.WriteOnly)
244 244 image.save(buffer_, format.upper())
245 245 buffer_.close()
246 246 return '<img src="data:image/%s;base64,\n%s\n" />' % (
247 247 format,re.sub(r'(.{60})',r'\1\n',str(ba.toBase64())))
248 248
249 249 elif format == "svg":
250 250 try:
251 251 svg = str(self._name_to_svg_map[match.group("name")])
252 252 except KeyError:
253 253 if not self._svg_warning_displayed:
254 254 QtGui.QMessageBox.warning(self, 'Error converting PNG to SVG.',
255 255 'Cannot convert a PNG to SVG. To fix this, add this '
256 256 'to your ipython config:\n\n'
257 257 '\tc.InlineBackendConfig.figure_format = \'svg\'\n\n'
258 258 'And regenerate the figures.',
259 259 QtGui.QMessageBox.Ok)
260 260 self._svg_warning_displayed = True
261 261 return ("<b>Cannot convert a PNG to SVG.</b> "
262 262 "To fix this, add this to your config: "
263 263 "<span>c.InlineBackendConfig.figure_format = 'svg'</span> "
264 264 "and regenerate the figures.")
265 265
266 266 # Not currently checking path, because it's tricky to find a
267 267 # cross-browser way to embed external SVG images (e.g., via
268 268 # object or embed tags).
269 269
270 270 # Chop stand-alone header from matplotlib SVG
271 271 offset = svg.find("<svg")
272 272 assert(offset > -1)
273 273
274 274 return svg[offset:]
275 275
276 276 else:
277 277 return '<b>Unrecognized image format</b>'
278 278
279 279 def _insert_jpg(self, cursor, jpg):
280 280 """ Insert raw PNG data into the widget."""
281 281 self._insert_img(cursor, jpg, 'jpg')
282 282
283 283 def _insert_png(self, cursor, png):
284 284 """ Insert raw PNG data into the widget.
285 285 """
286 286 self._insert_img(cursor, png, 'png')
287 287
288 288 def _insert_img(self, cursor, img, fmt):
289 289 """ insert a raw image, jpg or png """
290 290 try:
291 291 image = QtGui.QImage()
292 292 image.loadFromData(img, fmt.upper())
293 293 except ValueError:
294 294 self._insert_plain_text(cursor, 'Received invalid %s data.'%fmt)
295 295 else:
296 296 format = self._add_image(image)
297 297 cursor.insertBlock()
298 298 cursor.insertImage(format)
299 299 cursor.insertBlock()
300 300
301 301 def _insert_svg(self, cursor, svg):
302 302 """ Insert raw SVG data into the widet.
303 303 """
304 304 try:
305 305 image = svg_to_image(svg)
306 306 except ValueError:
307 307 self._insert_plain_text(cursor, 'Received invalid SVG data.')
308 308 else:
309 309 format = self._add_image(image)
310 310 self._name_to_svg_map[format.name()] = svg
311 311 cursor.insertBlock()
312 312 cursor.insertImage(format)
313 313 cursor.insertBlock()
314 314
315 315 def _save_image(self, name, format='PNG'):
316 316 """ Shows a save dialog for the ImageResource with 'name'.
317 317 """
318 318 dialog = QtGui.QFileDialog(self._control, 'Save Image')
319 319 dialog.setAcceptMode(QtGui.QFileDialog.AcceptSave)
320 320 dialog.setDefaultSuffix(format.lower())
321 321 dialog.setNameFilter('%s file (*.%s)' % (format, format.lower()))
322 322 if dialog.exec_():
323 323 filename = dialog.selectedFiles()[0]
324 324 image = self._get_image(name)
325 325 image.save(filename, format)
1 NO CONTENT: file renamed from IPython/frontend/qt/console/styles.py to IPython/qt/console/styles.py
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/notebook/__init__.py to IPython/qt/console/tests/__init__.py
@@ -1,171 +1,171 b''
1 1 # Standard library imports
2 2 import unittest
3 3
4 4 # Local imports
5 from IPython.frontend.qt.console.ansi_code_processor import AnsiCodeProcessor
5 from IPython.qt.console.ansi_code_processor import AnsiCodeProcessor
6 6
7 7
8 8 class TestAnsiCodeProcessor(unittest.TestCase):
9 9
10 10 def setUp(self):
11 11 self.processor = AnsiCodeProcessor()
12 12
13 13 def test_clear(self):
14 14 """ Do control sequences for clearing the console work?
15 15 """
16 16 string = '\x1b[2J\x1b[K'
17 17 i = -1
18 18 for i, substring in enumerate(self.processor.split_string(string)):
19 19 if i == 0:
20 20 self.assertEqual(len(self.processor.actions), 1)
21 21 action = self.processor.actions[0]
22 22 self.assertEqual(action.action, 'erase')
23 23 self.assertEqual(action.area, 'screen')
24 24 self.assertEqual(action.erase_to, 'all')
25 25 elif i == 1:
26 26 self.assertEqual(len(self.processor.actions), 1)
27 27 action = self.processor.actions[0]
28 28 self.assertEqual(action.action, 'erase')
29 29 self.assertEqual(action.area, 'line')
30 30 self.assertEqual(action.erase_to, 'end')
31 31 else:
32 32 self.fail('Too many substrings.')
33 33 self.assertEqual(i, 1, 'Too few substrings.')
34 34
35 35 def test_colors(self):
36 36 """ Do basic controls sequences for colors work?
37 37 """
38 38 string = 'first\x1b[34mblue\x1b[0mlast'
39 39 i = -1
40 40 for i, substring in enumerate(self.processor.split_string(string)):
41 41 if i == 0:
42 42 self.assertEqual(substring, 'first')
43 43 self.assertEqual(self.processor.foreground_color, None)
44 44 elif i == 1:
45 45 self.assertEqual(substring, 'blue')
46 46 self.assertEqual(self.processor.foreground_color, 4)
47 47 elif i == 2:
48 48 self.assertEqual(substring, 'last')
49 49 self.assertEqual(self.processor.foreground_color, None)
50 50 else:
51 51 self.fail('Too many substrings.')
52 52 self.assertEqual(i, 2, 'Too few substrings.')
53 53
54 54 def test_colors_xterm(self):
55 55 """ Do xterm-specific control sequences for colors work?
56 56 """
57 57 string = '\x1b]4;20;rgb:ff/ff/ff\x1b' \
58 58 '\x1b]4;25;rgbi:1.0/1.0/1.0\x1b'
59 59 substrings = list(self.processor.split_string(string))
60 60 desired = { 20 : (255, 255, 255),
61 61 25 : (255, 255, 255) }
62 62 self.assertEqual(self.processor.color_map, desired)
63 63
64 64 string = '\x1b[38;5;20m\x1b[48;5;25m'
65 65 substrings = list(self.processor.split_string(string))
66 66 self.assertEqual(self.processor.foreground_color, 20)
67 67 self.assertEqual(self.processor.background_color, 25)
68 68
69 69 def test_scroll(self):
70 70 """ Do control sequences for scrolling the buffer work?
71 71 """
72 72 string = '\x1b[5S\x1b[T'
73 73 i = -1
74 74 for i, substring in enumerate(self.processor.split_string(string)):
75 75 if i == 0:
76 76 self.assertEqual(len(self.processor.actions), 1)
77 77 action = self.processor.actions[0]
78 78 self.assertEqual(action.action, 'scroll')
79 79 self.assertEqual(action.dir, 'up')
80 80 self.assertEqual(action.unit, 'line')
81 81 self.assertEqual(action.count, 5)
82 82 elif i == 1:
83 83 self.assertEqual(len(self.processor.actions), 1)
84 84 action = self.processor.actions[0]
85 85 self.assertEqual(action.action, 'scroll')
86 86 self.assertEqual(action.dir, 'down')
87 87 self.assertEqual(action.unit, 'line')
88 88 self.assertEqual(action.count, 1)
89 89 else:
90 90 self.fail('Too many substrings.')
91 91 self.assertEqual(i, 1, 'Too few substrings.')
92 92
93 93 def test_formfeed(self):
94 94 """ Are formfeed characters processed correctly?
95 95 """
96 96 string = '\f' # form feed
97 97 self.assertEqual(list(self.processor.split_string(string)), [''])
98 98 self.assertEqual(len(self.processor.actions), 1)
99 99 action = self.processor.actions[0]
100 100 self.assertEqual(action.action, 'scroll')
101 101 self.assertEqual(action.dir, 'down')
102 102 self.assertEqual(action.unit, 'page')
103 103 self.assertEqual(action.count, 1)
104 104
105 105 def test_carriage_return(self):
106 106 """ Are carriage return characters processed correctly?
107 107 """
108 108 string = 'foo\rbar' # carriage return
109 109 splits = []
110 110 actions = []
111 111 for split in self.processor.split_string(string):
112 112 splits.append(split)
113 113 actions.append([action.action for action in self.processor.actions])
114 114 self.assertEqual(splits, ['foo', None, 'bar'])
115 115 self.assertEqual(actions, [[], ['carriage-return'], []])
116 116
117 117 def test_carriage_return_newline(self):
118 118 """transform CRLF to LF"""
119 119 string = 'foo\rbar\r\ncat\r\n\n' # carriage return and newline
120 120 # only one CR action should occur, and '\r\n' should transform to '\n'
121 121 splits = []
122 122 actions = []
123 123 for split in self.processor.split_string(string):
124 124 splits.append(split)
125 125 actions.append([action.action for action in self.processor.actions])
126 126 self.assertEqual(splits, ['foo', None, 'bar', '\r\n', 'cat', '\r\n', '\n'])
127 127 self.assertEqual(actions, [[], ['carriage-return'], [], ['newline'], [], ['newline'], ['newline']])
128 128
129 129 def test_beep(self):
130 130 """ Are beep characters processed correctly?
131 131 """
132 132 string = 'foo\abar' # bell
133 133 splits = []
134 134 actions = []
135 135 for split in self.processor.split_string(string):
136 136 splits.append(split)
137 137 actions.append([action.action for action in self.processor.actions])
138 138 self.assertEqual(splits, ['foo', None, 'bar'])
139 139 self.assertEqual(actions, [[], ['beep'], []])
140 140
141 141 def test_backspace(self):
142 142 """ Are backspace characters processed correctly?
143 143 """
144 144 string = 'foo\bbar' # backspace
145 145 splits = []
146 146 actions = []
147 147 for split in self.processor.split_string(string):
148 148 splits.append(split)
149 149 actions.append([action.action for action in self.processor.actions])
150 150 self.assertEqual(splits, ['foo', None, 'bar'])
151 151 self.assertEqual(actions, [[], ['backspace'], []])
152 152
153 153 def test_combined(self):
154 154 """ Are CR and BS characters processed correctly in combination?
155 155
156 156 BS is treated as a change in print position, rather than a
157 157 backwards character deletion. Therefore a BS at EOL is
158 158 effectively ignored.
159 159 """
160 160 string = 'abc\rdef\b' # CR and backspace
161 161 splits = []
162 162 actions = []
163 163 for split in self.processor.split_string(string):
164 164 splits.append(split)
165 165 actions.append([action.action for action in self.processor.actions])
166 166 self.assertEqual(splits, ['abc', None, 'def', None])
167 167 self.assertEqual(actions, [[], ['carriage-return'], [], ['backspace']])
168 168
169 169
170 170 if __name__ == '__main__':
171 171 unittest.main()
@@ -1,47 +1,47 b''
1 1 # Standard library imports
2 2 import unittest
3 3
4 4 # System library imports
5 5 from pygments.lexers import CLexer, CppLexer, PythonLexer
6 6
7 7 # Local imports
8 from IPython.frontend.qt.console.completion_lexer import CompletionLexer
8 from IPython.qt.console.completion_lexer import CompletionLexer
9 9
10 10
11 11 class TestCompletionLexer(unittest.TestCase):
12 12
13 13 def testPython(self):
14 14 """ Does the CompletionLexer work for Python?
15 15 """
16 16 lexer = CompletionLexer(PythonLexer())
17 17
18 18 # Test simplest case.
19 19 self.assertEqual(lexer.get_context("foo.bar.baz"),
20 20 [ "foo", "bar", "baz" ])
21 21
22 22 # Test trailing period.
23 23 self.assertEqual(lexer.get_context("foo.bar."), [ "foo", "bar", "" ])
24 24
25 25 # Test with prompt present.
26 26 self.assertEqual(lexer.get_context(">>> foo.bar.baz"),
27 27 [ "foo", "bar", "baz" ])
28 28
29 29 # Test spacing in name.
30 30 self.assertEqual(lexer.get_context("foo.bar. baz"), [ "baz" ])
31 31
32 32 # Test parenthesis.
33 33 self.assertEqual(lexer.get_context("foo("), [])
34 34
35 35 def testC(self):
36 36 """ Does the CompletionLexer work for C/C++?
37 37 """
38 38 lexer = CompletionLexer(CLexer())
39 39 self.assertEqual(lexer.get_context("foo.bar"), [ "foo", "bar" ])
40 40 self.assertEqual(lexer.get_context("foo->bar"), [ "foo", "bar" ])
41 41
42 42 lexer = CompletionLexer(CppLexer())
43 43 self.assertEqual(lexer.get_context("Foo::Bar"), [ "Foo", "Bar" ])
44 44
45 45
46 46 if __name__ == '__main__':
47 47 unittest.main()
@@ -1,80 +1,80 b''
1 1 # Standard library imports
2 2 import unittest
3 3
4 4 # System library imports
5 5 from IPython.external.qt import QtCore, QtGui
6 6
7 7 # Local imports
8 from IPython.frontend.qt.console.console_widget import ConsoleWidget
8 from IPython.qt.console.console_widget import ConsoleWidget
9 9
10 10
11 11 class TestConsoleWidget(unittest.TestCase):
12 12
13 13 @classmethod
14 14 def setUpClass(cls):
15 15 """ Create the application for the test case.
16 16 """
17 17 cls._app = QtGui.QApplication.instance()
18 18 if cls._app is None:
19 19 cls._app = QtGui.QApplication([])
20 20 cls._app.setQuitOnLastWindowClosed(False)
21 21
22 22 @classmethod
23 23 def tearDownClass(cls):
24 24 """ Exit the application.
25 25 """
26 26 QtGui.QApplication.quit()
27 27
28 28 def test_special_characters(self):
29 29 """ Are special characters displayed correctly?
30 30 """
31 31 w = ConsoleWidget()
32 32 cursor = w._get_prompt_cursor()
33 33
34 34 test_inputs = ['xyz\b\b=\n', 'foo\b\nbar\n', 'foo\b\nbar\r\n', 'abc\rxyz\b\b=']
35 35 expected_outputs = [u'x=z\u2029', u'foo\u2029bar\u2029', u'foo\u2029bar\u2029', 'x=z']
36 36 for i, text in enumerate(test_inputs):
37 37 w._insert_plain_text(cursor, text)
38 38 cursor.select(cursor.Document)
39 39 selection = cursor.selectedText()
40 40 self.assertEqual(expected_outputs[i], selection)
41 41 # clear all the text
42 42 cursor.insertText('')
43 43
44 44 def test_link_handling(self):
45 45 noKeys = QtCore.Qt
46 46 noButton = QtCore.Qt.MouseButton(0)
47 47 noButtons = QtCore.Qt.MouseButtons(0)
48 48 noModifiers = QtCore.Qt.KeyboardModifiers(0)
49 49 MouseMove = QtCore.QEvent.MouseMove
50 50 QMouseEvent = QtGui.QMouseEvent
51 51
52 52 w = ConsoleWidget()
53 53 cursor = w._get_prompt_cursor()
54 54 w._insert_html(cursor, '<a href="http://python.org">written in</a>')
55 55 obj = w._control
56 56 tip = QtGui.QToolTip
57 57 self.assertEqual(tip.text(), u'')
58 58
59 59 # should be somewhere else
60 60 elsewhereEvent = QMouseEvent(MouseMove, QtCore.QPoint(50,50),
61 61 noButton, noButtons, noModifiers)
62 62 w.eventFilter(obj, elsewhereEvent)
63 63 self.assertEqual(tip.isVisible(), False)
64 64 self.assertEqual(tip.text(), u'')
65 65
66 66 #self.assertEqual(tip.text(), u'')
67 67 # should be over text
68 68 overTextEvent = QMouseEvent(MouseMove, QtCore.QPoint(1,5),
69 69 noButton, noButtons, noModifiers)
70 70 w.eventFilter(obj, overTextEvent)
71 71 self.assertEqual(tip.isVisible(), True)
72 72 self.assertEqual(tip.text(), "http://python.org")
73 73
74 74 # should still be over text
75 75 stillOverTextEvent = QMouseEvent(MouseMove, QtCore.QPoint(1,5),
76 76 noButton, noButtons, noModifiers)
77 77 w.eventFilter(obj, stillOverTextEvent)
78 78 self.assertEqual(tip.isVisible(), True)
79 79 self.assertEqual(tip.text(), "http://python.org")
80 80
@@ -1,85 +1,85 b''
1 1 # Standard library imports
2 2 import unittest
3 3
4 4 # System library imports
5 5 from IPython.external.qt import QtCore, QtGui
6 6
7 7 # Local imports
8 from IPython.frontend.qt.console.kill_ring import KillRing, QtKillRing
8 from IPython.qt.console.kill_ring import KillRing, QtKillRing
9 9
10 10
11 11 class TestKillRing(unittest.TestCase):
12 12
13 13 @classmethod
14 14 def setUpClass(cls):
15 15 """ Create the application for the test case.
16 16 """
17 17 cls._app = QtGui.QApplication.instance()
18 18 if cls._app is None:
19 19 cls._app = QtGui.QApplication([])
20 20 cls._app.setQuitOnLastWindowClosed(False)
21 21
22 22 @classmethod
23 23 def tearDownClass(cls):
24 24 """ Exit the application.
25 25 """
26 26 QtGui.QApplication.quit()
27 27
28 28 def test_generic(self):
29 29 """ Does the generic kill ring work?
30 30 """
31 31 ring = KillRing()
32 32 self.assertTrue(ring.yank() is None)
33 33 self.assertTrue(ring.rotate() is None)
34 34
35 35 ring.kill('foo')
36 36 self.assertEqual(ring.yank(), 'foo')
37 37 self.assertTrue(ring.rotate() is None)
38 38 self.assertEqual(ring.yank(), 'foo')
39 39
40 40 ring.kill('bar')
41 41 self.assertEqual(ring.yank(), 'bar')
42 42 self.assertEqual(ring.rotate(), 'foo')
43 43
44 44 ring.clear()
45 45 self.assertTrue(ring.yank() is None)
46 46 self.assertTrue(ring.rotate() is None)
47 47
48 48 def test_qt_basic(self):
49 49 """ Does the Qt kill ring work?
50 50 """
51 51 text_edit = QtGui.QPlainTextEdit()
52 52 ring = QtKillRing(text_edit)
53 53
54 54 ring.kill('foo')
55 55 ring.kill('bar')
56 56 ring.yank()
57 57 ring.rotate()
58 58 ring.yank()
59 59 self.assertEqual(text_edit.toPlainText(), 'foobar')
60 60
61 61 text_edit.clear()
62 62 ring.kill('baz')
63 63 ring.yank()
64 64 ring.rotate()
65 65 ring.rotate()
66 66 ring.rotate()
67 67 self.assertEqual(text_edit.toPlainText(), 'foo')
68 68
69 69 def test_qt_cursor(self):
70 70 """ Does the Qt kill ring maintain state with cursor movement?
71 71 """
72 72 text_edit = QtGui.QPlainTextEdit()
73 73 ring = QtKillRing(text_edit)
74 74
75 75 ring.kill('foo')
76 76 ring.kill('bar')
77 77 ring.yank()
78 78 text_edit.moveCursor(QtGui.QTextCursor.Left)
79 79 ring.rotate()
80 80 self.assertEqual(text_edit.toPlainText(), 'bar')
81 81
82 82
83 83 if __name__ == '__main__':
84 84 import nose
85 85 nose.main()
1 NO CONTENT: file renamed from IPython/frontend/qt/inprocess.py to IPython/qt/inprocess.py
1 NO CONTENT: file renamed from IPython/frontend/qt/kernel_mixins.py to IPython/qt/kernel_mixins.py
1 NO CONTENT: file renamed from IPython/frontend/qt/manager.py to IPython/qt/manager.py
1 NO CONTENT: file renamed from IPython/frontend/qt/rich_text.py to IPython/qt/rich_text.py
1 NO CONTENT: file renamed from IPython/frontend/qt/svg.py to IPython/qt/svg.py
1 NO CONTENT: file renamed from IPython/frontend/qt/util.py to IPython/qt/util.py
@@ -1,7 +1,7 b''
1 1 #!/usr/bin/env python
2 2 """Terminal-based IPython entry point.
3 3 """
4 4
5 from IPython.frontend.terminal.ipapp import launch_new_instance
5 from IPython.terminal.ipapp import launch_new_instance
6 6
7 7 launch_new_instance()
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/services/__init__.py to IPython/terminal/__init__.py
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/services/clusters/__init__.py to IPython/terminal/console/__init__.py
@@ -1,157 +1,157 b''
1 1 """ A minimal application using the ZMQ-based terminal IPython frontend.
2 2
3 3 This is not a complete console app, as subprocess will not be able to receive
4 4 input, there is no real readline support, among other limitations.
5 5
6 6 Authors:
7 7
8 8 * Min RK
9 9 * Paul Ivanov
10 10
11 11 """
12 12
13 13 #-----------------------------------------------------------------------------
14 14 # Imports
15 15 #-----------------------------------------------------------------------------
16 16 import signal
17 17 import sys
18 18 import time
19 19
20 from IPython.frontend.terminal.ipapp import TerminalIPythonApp, frontend_flags as term_flags
20 from IPython.terminal.ipapp import TerminalIPythonApp, frontend_flags as term_flags
21 21
22 22 from IPython.utils.traitlets import (
23 23 Dict, List, Unicode, Int, CaselessStrEnum, CBool, Any
24 24 )
25 25 from IPython.utils.warn import warn,error
26 26
27 27 from IPython.kernel.zmq.kernelapp import IPKernelApp
28 28 from IPython.kernel.zmq.session import Session, default_secure
29 29 from IPython.kernel.zmq.zmqshell import ZMQInteractiveShell
30 from IPython.frontend.consoleapp import (
30 from IPython.consoleapp import (
31 31 IPythonConsoleApp, app_aliases, app_flags, aliases, app_aliases, flags
32 32 )
33 33
34 from IPython.frontend.terminal.console.interactiveshell import ZMQTerminalInteractiveShell
34 from IPython.terminal.console.interactiveshell import ZMQTerminalInteractiveShell
35 35
36 36 #-----------------------------------------------------------------------------
37 37 # Globals
38 38 #-----------------------------------------------------------------------------
39 39
40 40 _examples = """
41 41 ipython console # start the ZMQ-based console
42 42 ipython console --existing # connect to an existing ipython session
43 43 """
44 44
45 45 #-----------------------------------------------------------------------------
46 46 # Flags and Aliases
47 47 #-----------------------------------------------------------------------------
48 48
49 49 # copy flags from mixin:
50 50 flags = dict(flags)
51 51 # start with mixin frontend flags:
52 52 frontend_flags = dict(app_flags)
53 53 # add TerminalIPApp flags:
54 54 frontend_flags.update(term_flags)
55 55 # disable quick startup, as it won't propagate to the kernel anyway
56 56 frontend_flags.pop('quick')
57 57 # update full dict with frontend flags:
58 58 flags.update(frontend_flags)
59 59
60 60 # copy flags from mixin
61 61 aliases = dict(aliases)
62 62 # start with mixin frontend flags
63 63 frontend_aliases = dict(app_aliases)
64 64 # load updated frontend flags into full dict
65 65 aliases.update(frontend_aliases)
66 66
67 67 # get flags&aliases into sets, and remove a couple that
68 68 # shouldn't be scrubbed from backend flags:
69 69 frontend_aliases = set(frontend_aliases.keys())
70 70 frontend_flags = set(frontend_flags.keys())
71 71
72 72
73 73 #-----------------------------------------------------------------------------
74 74 # Classes
75 75 #-----------------------------------------------------------------------------
76 76
77 77
78 78 class ZMQTerminalIPythonApp(TerminalIPythonApp, IPythonConsoleApp):
79 79 name = "ipython-console"
80 80 """Start a terminal frontend to the IPython zmq kernel."""
81 81
82 82 description = """
83 83 The IPython terminal-based Console.
84 84
85 85 This launches a Console application inside a terminal.
86 86
87 87 The Console supports various extra features beyond the traditional
88 88 single-process Terminal IPython shell, such as connecting to an
89 89 existing ipython session, via:
90 90
91 91 ipython console --existing
92 92
93 93 where the previous session could have been created by another ipython
94 94 console, an ipython qtconsole, or by opening an ipython notebook.
95 95
96 96 """
97 97 examples = _examples
98 98
99 99 classes = [ZMQTerminalInteractiveShell] + IPythonConsoleApp.classes
100 100 flags = Dict(flags)
101 101 aliases = Dict(aliases)
102 102 frontend_aliases = Any(frontend_aliases)
103 103 frontend_flags = Any(frontend_flags)
104 104
105 105 subcommands = Dict()
106 106
107 107 def parse_command_line(self, argv=None):
108 108 super(ZMQTerminalIPythonApp, self).parse_command_line(argv)
109 109 self.build_kernel_argv(argv)
110 110
111 111 def init_shell(self):
112 112 IPythonConsoleApp.initialize(self)
113 113 # relay sigint to kernel
114 114 signal.signal(signal.SIGINT, self.handle_sigint)
115 115 self.shell = ZMQTerminalInteractiveShell.instance(config=self.config,
116 116 display_banner=False, profile_dir=self.profile_dir,
117 117 ipython_dir=self.ipython_dir,
118 118 manager=self.kernel_manager,
119 119 client=self.kernel_client,
120 120 )
121 121
122 122 def init_gui_pylab(self):
123 123 # no-op, because we don't want to import matplotlib in the frontend.
124 124 pass
125 125
126 126 def handle_sigint(self, *args):
127 127 if self.shell._executing:
128 128 if self.kernel_manager:
129 129 # interrupt already gets passed to subprocess by signal handler.
130 130 # Only if we prevent that should we need to explicitly call
131 131 # interrupt_kernel, until which time, this would result in a
132 132 # double-interrupt:
133 133 # self.kernel_manager.interrupt_kernel()
134 134 pass
135 135 else:
136 136 self.shell.write_err('\n')
137 137 error("Cannot interrupt kernels we didn't start.\n")
138 138 else:
139 139 # raise the KeyboardInterrupt if we aren't waiting for execution,
140 140 # so that the interact loop advances, and prompt is redrawn, etc.
141 141 raise KeyboardInterrupt
142 142
143 143
144 144 def init_code(self):
145 145 # no-op in the frontend, code gets run in the backend
146 146 pass
147 147
148 148 def launch_new_instance():
149 149 """Create and run a full blown IPython instance"""
150 150 app = ZMQTerminalIPythonApp.instance()
151 151 app.initialize()
152 152 app.start()
153 153
154 154
155 155 if __name__ == '__main__':
156 156 launch_new_instance()
157 157
1 NO CONTENT: file renamed from IPython/frontend/terminal/console/completer.py to IPython/terminal/console/completer.py
@@ -1,465 +1,465 b''
1 1 # -*- coding: utf-8 -*-
2 2 """terminal client to the IPython kernel
3 3
4 4 """
5 5 #-----------------------------------------------------------------------------
6 6 # Copyright (C) 2013 The IPython Development Team
7 7 #
8 8 # Distributed under the terms of the BSD License. The full license is in
9 9 # the file COPYING, distributed as part of this software.
10 10 #-----------------------------------------------------------------------------
11 11
12 12 #-----------------------------------------------------------------------------
13 13 # Imports
14 14 #-----------------------------------------------------------------------------
15 15 from __future__ import print_function
16 16
17 17 import bdb
18 18 import signal
19 19 import os
20 20 import sys
21 21 import time
22 22 import subprocess
23 23 from io import BytesIO
24 24 import base64
25 25
26 26 from Queue import Empty
27 27
28 28 try:
29 29 from contextlib import nested
30 30 except:
31 31 from IPython.utils.nested_context import nested
32 32
33 33 from IPython.core.alias import AliasManager, AliasError
34 34 from IPython.core import page
35 35 from IPython.utils.warn import warn, error, fatal
36 36 from IPython.utils import io
37 37 from IPython.utils.traitlets import List, Enum, Any, Instance, Unicode
38 38 from IPython.utils.tempdir import NamedFileInTemporaryDirectory
39 39
40 from IPython.frontend.terminal.interactiveshell import TerminalInteractiveShell
41 from IPython.frontend.terminal.console.completer import ZMQCompleter
40 from IPython.terminal.interactiveshell import TerminalInteractiveShell
41 from IPython.terminal.console.completer import ZMQCompleter
42 42
43 43
44 44 class ZMQTerminalInteractiveShell(TerminalInteractiveShell):
45 45 """A subclass of TerminalInteractiveShell that uses the 0MQ kernel"""
46 46 _executing = False
47 47
48 48 image_handler = Enum(('PIL', 'stream', 'tempfile', 'callable'),
49 49 config=True, help=
50 50 """
51 51 Handler for image type output. This is useful, for example,
52 52 when connecting to the kernel in which pylab inline backend is
53 53 activated. There are four handlers defined. 'PIL': Use
54 54 Python Imaging Library to popup image; 'stream': Use an
55 55 external program to show the image. Image will be fed into
56 56 the STDIN of the program. You will need to configure
57 57 `stream_image_handler`; 'tempfile': Use an external program to
58 58 show the image. Image will be saved in a temporally file and
59 59 the program is called with the temporally file. You will need
60 60 to configure `tempfile_image_handler`; 'callable': You can set
61 61 any Python callable which is called with the image data. You
62 62 will need to configure `callable_image_handler`.
63 63 """
64 64 )
65 65
66 66 stream_image_handler = List(config=True, help=
67 67 """
68 68 Command to invoke an image viewer program when you are using
69 69 'stream' image handler. This option is a list of string where
70 70 the first element is the command itself and reminders are the
71 71 options for the command. Raw image data is given as STDIN to
72 72 the program.
73 73 """
74 74 )
75 75
76 76 tempfile_image_handler = List(config=True, help=
77 77 """
78 78 Command to invoke an image viewer program when you are using
79 79 'tempfile' image handler. This option is a list of string
80 80 where the first element is the command itself and reminders
81 81 are the options for the command. You can use {file} and
82 82 {format} in the string to represent the location of the
83 83 generated image file and image format.
84 84 """
85 85 )
86 86
87 87 callable_image_handler = Any(config=True, help=
88 88 """
89 89 Callable object called via 'callable' image handler with one
90 90 argument, `data`, which is `msg["content"]["data"]` where
91 91 `msg` is the message from iopub channel. For exmaple, you can
92 92 find base64 encoded PNG data as `data['image/png']`.
93 93 """
94 94 )
95 95
96 96 mime_preference = List(
97 97 default_value=['image/png', 'image/jpeg', 'image/svg+xml'],
98 98 config=True, allow_none=False, help=
99 99 """
100 100 Preferred object representation MIME type in order. First
101 101 matched MIME type will be used.
102 102 """
103 103 )
104 104
105 105 manager = Instance('IPython.kernel.KernelManager')
106 106 client = Instance('IPython.kernel.KernelClient')
107 107 def _client_changed(self, name, old, new):
108 108 self.session_id = new.session.session
109 109 session_id = Unicode()
110 110
111 111 def init_completer(self):
112 112 """Initialize the completion machinery.
113 113
114 114 This creates completion machinery that can be used by client code,
115 115 either interactively in-process (typically triggered by the readline
116 116 library), programatically (such as in test suites) or out-of-prcess
117 117 (typically over the network by remote frontends).
118 118 """
119 119 from IPython.core.completerlib import (module_completer,
120 120 magic_run_completer, cd_completer)
121 121
122 122 self.Completer = ZMQCompleter(self, self.client)
123 123
124 124
125 125 self.set_hook('complete_command', module_completer, str_key = 'import')
126 126 self.set_hook('complete_command', module_completer, str_key = 'from')
127 127 self.set_hook('complete_command', magic_run_completer, str_key = '%run')
128 128 self.set_hook('complete_command', cd_completer, str_key = '%cd')
129 129
130 130 # Only configure readline if we truly are using readline. IPython can
131 131 # do tab-completion over the network, in GUIs, etc, where readline
132 132 # itself may be absent
133 133 if self.has_readline:
134 134 self.set_readline_completer()
135 135
136 136 def run_cell(self, cell, store_history=True):
137 137 """Run a complete IPython cell.
138 138
139 139 Parameters
140 140 ----------
141 141 cell : str
142 142 The code (including IPython code such as %magic functions) to run.
143 143 store_history : bool
144 144 If True, the raw and translated cell will be stored in IPython's
145 145 history. For user code calling back into IPython's machinery, this
146 146 should be set to False.
147 147 """
148 148 if (not cell) or cell.isspace():
149 149 return
150 150
151 151 if cell.strip() == 'exit':
152 152 # explicitly handle 'exit' command
153 153 return self.ask_exit()
154 154
155 155 self._executing = True
156 156 # flush stale replies, which could have been ignored, due to missed heartbeats
157 157 while self.client.shell_channel.msg_ready():
158 158 self.client.shell_channel.get_msg()
159 159 # shell_channel.execute takes 'hidden', which is the inverse of store_hist
160 160 msg_id = self.client.shell_channel.execute(cell, not store_history)
161 161 while not self.client.shell_channel.msg_ready() and self.client.is_alive():
162 162 try:
163 163 self.handle_stdin_request(timeout=0.05)
164 164 except Empty:
165 165 # display intermediate print statements, etc.
166 166 self.handle_iopub()
167 167 pass
168 168 if self.client.shell_channel.msg_ready():
169 169 self.handle_execute_reply(msg_id)
170 170 self._executing = False
171 171
172 172 #-----------------
173 173 # message handlers
174 174 #-----------------
175 175
176 176 def handle_execute_reply(self, msg_id):
177 177 msg = self.client.shell_channel.get_msg()
178 178 if msg["parent_header"].get("msg_id", None) == msg_id:
179 179
180 180 self.handle_iopub()
181 181
182 182 content = msg["content"]
183 183 status = content['status']
184 184
185 185 if status == 'aborted':
186 186 self.write('Aborted\n')
187 187 return
188 188 elif status == 'ok':
189 189 # print execution payloads as well:
190 190 for item in content["payload"]:
191 191 text = item.get('text', None)
192 192 if text:
193 193 page.page(text)
194 194
195 195 elif status == 'error':
196 196 for frame in content["traceback"]:
197 197 print(frame, file=io.stderr)
198 198
199 199 self.execution_count = int(content["execution_count"] + 1)
200 200
201 201
202 202 def handle_iopub(self):
203 203 """ Method to procces subscribe channel's messages
204 204
205 205 This method reads a message and processes the content in different
206 206 outputs like stdout, stderr, pyout and status
207 207
208 208 Arguments:
209 209 sub_msg: message receive from kernel in the sub socket channel
210 210 capture by kernel manager.
211 211 """
212 212 while self.client.iopub_channel.msg_ready():
213 213 sub_msg = self.client.iopub_channel.get_msg()
214 214 msg_type = sub_msg['header']['msg_type']
215 215 parent = sub_msg["parent_header"]
216 216 if (not parent) or self.session_id == parent['session']:
217 217 if msg_type == 'status' :
218 218 if sub_msg["content"]["execution_state"] == "busy" :
219 219 pass
220 220
221 221 elif msg_type == 'stream' :
222 222 if sub_msg["content"]["name"] == "stdout":
223 223 print(sub_msg["content"]["data"], file=io.stdout, end="")
224 224 io.stdout.flush()
225 225 elif sub_msg["content"]["name"] == "stderr" :
226 226 print(sub_msg["content"]["data"], file=io.stderr, end="")
227 227 io.stderr.flush()
228 228
229 229 elif msg_type == 'pyout':
230 230 self.execution_count = int(sub_msg["content"]["execution_count"])
231 231 format_dict = sub_msg["content"]["data"]
232 232 self.handle_rich_data(format_dict)
233 233 # taken from DisplayHook.__call__:
234 234 hook = self.displayhook
235 235 hook.start_displayhook()
236 236 hook.write_output_prompt()
237 237 hook.write_format_data(format_dict)
238 238 hook.log_output(format_dict)
239 239 hook.finish_displayhook()
240 240
241 241 elif msg_type == 'display_data':
242 242 self.handle_rich_data(sub_msg["content"]["data"])
243 243
244 244 _imagemime = {
245 245 'image/png': 'png',
246 246 'image/jpeg': 'jpeg',
247 247 'image/svg+xml': 'svg',
248 248 }
249 249
250 250 def handle_rich_data(self, data):
251 251 for mime in self.mime_preference:
252 252 if mime in data and mime in self._imagemime:
253 253 self.handle_image(data, mime)
254 254 return
255 255
256 256 def handle_image(self, data, mime):
257 257 handler = getattr(
258 258 self, 'handle_image_{0}'.format(self.image_handler), None)
259 259 if handler:
260 260 handler(data, mime)
261 261
262 262 def handle_image_PIL(self, data, mime):
263 263 if mime not in ('image/png', 'image/jpeg'):
264 264 return
265 265 import PIL.Image
266 266 raw = base64.decodestring(data[mime].encode('ascii'))
267 267 img = PIL.Image.open(BytesIO(raw))
268 268 img.show()
269 269
270 270 def handle_image_stream(self, data, mime):
271 271 raw = base64.decodestring(data[mime].encode('ascii'))
272 272 imageformat = self._imagemime[mime]
273 273 fmt = dict(format=imageformat)
274 274 args = [s.format(**fmt) for s in self.stream_image_handler]
275 275 with open(os.devnull, 'w') as devnull:
276 276 proc = subprocess.Popen(
277 277 args, stdin=subprocess.PIPE,
278 278 stdout=devnull, stderr=devnull)
279 279 proc.communicate(raw)
280 280
281 281 def handle_image_tempfile(self, data, mime):
282 282 raw = base64.decodestring(data[mime].encode('ascii'))
283 283 imageformat = self._imagemime[mime]
284 284 filename = 'tmp.{0}'.format(imageformat)
285 285 with nested(NamedFileInTemporaryDirectory(filename),
286 286 open(os.devnull, 'w')) as (f, devnull):
287 287 f.write(raw)
288 288 f.flush()
289 289 fmt = dict(file=f.name, format=imageformat)
290 290 args = [s.format(**fmt) for s in self.tempfile_image_handler]
291 291 subprocess.call(args, stdout=devnull, stderr=devnull)
292 292
293 293 def handle_image_callable(self, data, mime):
294 294 self.callable_image_handler(data)
295 295
296 296 def handle_stdin_request(self, timeout=0.1):
297 297 """ Method to capture raw_input
298 298 """
299 299 msg_rep = self.client.stdin_channel.get_msg(timeout=timeout)
300 300 # in case any iopub came while we were waiting:
301 301 self.handle_iopub()
302 302 if self.session_id == msg_rep["parent_header"].get("session"):
303 303 # wrap SIGINT handler
304 304 real_handler = signal.getsignal(signal.SIGINT)
305 305 def double_int(sig,frame):
306 306 # call real handler (forwards sigint to kernel),
307 307 # then raise local interrupt, stopping local raw_input
308 308 real_handler(sig,frame)
309 309 raise KeyboardInterrupt
310 310 signal.signal(signal.SIGINT, double_int)
311 311
312 312 try:
313 313 raw_data = raw_input(msg_rep["content"]["prompt"])
314 314 except EOFError:
315 315 # turn EOFError into EOF character
316 316 raw_data = '\x04'
317 317 except KeyboardInterrupt:
318 318 sys.stdout.write('\n')
319 319 return
320 320 finally:
321 321 # restore SIGINT handler
322 322 signal.signal(signal.SIGINT, real_handler)
323 323
324 324 # only send stdin reply if there *was not* another request
325 325 # or execution finished while we were reading.
326 326 if not (self.client.stdin_channel.msg_ready() or self.client.shell_channel.msg_ready()):
327 327 self.client.stdin_channel.input(raw_data)
328 328
329 329 def mainloop(self, display_banner=False):
330 330 while True:
331 331 try:
332 332 self.interact(display_banner=display_banner)
333 333 #self.interact_with_readline()
334 334 # XXX for testing of a readline-decoupled repl loop, call
335 335 # interact_with_readline above
336 336 break
337 337 except KeyboardInterrupt:
338 338 # this should not be necessary, but KeyboardInterrupt
339 339 # handling seems rather unpredictable...
340 340 self.write("\nKeyboardInterrupt in interact()\n")
341 341
342 342 def wait_for_kernel(self, timeout=None):
343 343 """method to wait for a kernel to be ready"""
344 344 tic = time.time()
345 345 self.client.hb_channel.unpause()
346 346 while True:
347 347 self.run_cell('1', False)
348 348 if self.client.hb_channel.is_beating():
349 349 # heart failure was not the reason this returned
350 350 break
351 351 else:
352 352 # heart failed
353 353 if timeout is not None and (time.time() - tic) > timeout:
354 354 return False
355 355 return True
356 356
357 357 def interact(self, display_banner=None):
358 358 """Closely emulate the interactive Python console."""
359 359
360 360 # batch run -> do not interact
361 361 if self.exit_now:
362 362 return
363 363
364 364 if display_banner is None:
365 365 display_banner = self.display_banner
366 366
367 367 if isinstance(display_banner, basestring):
368 368 self.show_banner(display_banner)
369 369 elif display_banner:
370 370 self.show_banner()
371 371
372 372 more = False
373 373
374 374 # run a non-empty no-op, so that we don't get a prompt until
375 375 # we know the kernel is ready. This keeps the connection
376 376 # message above the first prompt.
377 377 if not self.wait_for_kernel(3):
378 378 error("Kernel did not respond\n")
379 379 return
380 380
381 381 if self.has_readline:
382 382 self.readline_startup_hook(self.pre_readline)
383 383 hlen_b4_cell = self.readline.get_current_history_length()
384 384 else:
385 385 hlen_b4_cell = 0
386 386 # exit_now is set by a call to %Exit or %Quit, through the
387 387 # ask_exit callback.
388 388
389 389 while not self.exit_now:
390 390 if not self.client.is_alive():
391 391 # kernel died, prompt for action or exit
392 392
393 393 action = "restart" if self.manager else "wait for restart"
394 394 ans = self.ask_yes_no("kernel died, %s ([y]/n)?" % action, default='y')
395 395 if ans:
396 396 if self.manager:
397 397 self.manager.restart_kernel(True)
398 398 self.wait_for_kernel(3)
399 399 else:
400 400 self.exit_now = True
401 401 continue
402 402 try:
403 403 # protect prompt block from KeyboardInterrupt
404 404 # when sitting on ctrl-C
405 405 self.hooks.pre_prompt_hook()
406 406 if more:
407 407 try:
408 408 prompt = self.prompt_manager.render('in2')
409 409 except Exception:
410 410 self.showtraceback()
411 411 if self.autoindent:
412 412 self.rl_do_indent = True
413 413
414 414 else:
415 415 try:
416 416 prompt = self.separate_in + self.prompt_manager.render('in')
417 417 except Exception:
418 418 self.showtraceback()
419 419
420 420 line = self.raw_input(prompt)
421 421 if self.exit_now:
422 422 # quick exit on sys.std[in|out] close
423 423 break
424 424 if self.autoindent:
425 425 self.rl_do_indent = False
426 426
427 427 except KeyboardInterrupt:
428 428 #double-guard against keyboardinterrupts during kbdint handling
429 429 try:
430 430 self.write('\nKeyboardInterrupt\n')
431 431 source_raw = self.input_splitter.source_raw_reset()[1]
432 432 hlen_b4_cell = self._replace_rlhist_multiline(source_raw, hlen_b4_cell)
433 433 more = False
434 434 except KeyboardInterrupt:
435 435 pass
436 436 except EOFError:
437 437 if self.autoindent:
438 438 self.rl_do_indent = False
439 439 if self.has_readline:
440 440 self.readline_startup_hook(None)
441 441 self.write('\n')
442 442 self.exit()
443 443 except bdb.BdbQuit:
444 444 warn('The Python debugger has exited with a BdbQuit exception.\n'
445 445 'Because of how pdb handles the stack, it is impossible\n'
446 446 'for IPython to properly format this particular exception.\n'
447 447 'IPython will resume normal operation.')
448 448 except:
449 449 # exceptions here are VERY RARE, but they can be triggered
450 450 # asynchronously by signal handlers, for example.
451 451 self.showtraceback()
452 452 else:
453 453 self.input_splitter.push(line)
454 454 more = self.input_splitter.push_accepts_more()
455 455 if (self.SyntaxTB.last_syntax_error and
456 456 self.autoedit_syntax):
457 457 self.edit_syntax_error()
458 458 if not more:
459 459 source_raw = self.input_splitter.source_raw_reset()[1]
460 460 hlen_b4_cell = self._replace_rlhist_multiline(source_raw, hlen_b4_cell)
461 461 self.run_cell(source_raw)
462 462
463 463
464 464 # Turn off the exit flag, so the mainloop can be restarted if desired
465 465 self.exit_now = False
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/services/kernels/__init__.py to IPython/terminal/console/tests/__init__.py
1 NO CONTENT: file renamed from IPython/frontend/terminal/console/tests/test_console.py to IPython/terminal/console/tests/test_console.py
@@ -1,95 +1,94 b''
1 1 #-----------------------------------------------------------------------------
2 2 # Copyright (C) 2012 The IPython Development Team
3 3 #
4 4 # Distributed under the terms of the BSD License. The full license is in
5 5 # the file COPYING, distributed as part of this software.
6 6 #-----------------------------------------------------------------------------
7 7
8 8 import os
9 9 import sys
10 10 import unittest
11 11 import base64
12 12
13 13 from IPython.kernel import KernelClient
14 from IPython.frontend.terminal.console.interactiveshell \
15 import ZMQTerminalInteractiveShell
14 from IPython.terminal.console.interactiveshell import ZMQTerminalInteractiveShell
16 15 from IPython.utils.tempdir import TemporaryDirectory
17 16 from IPython.testing.tools import monkeypatch
18 17 from IPython.testing.decorators import skip_without
19 18 from IPython.utils.ipstruct import Struct
20 19
21 20
22 21 SCRIPT_PATH = os.path.join(
23 22 os.path.abspath(os.path.dirname(__file__)), 'writetofile.py')
24 23
25 24
26 25 class ZMQTerminalInteractiveShellTestCase(unittest.TestCase):
27 26
28 27 def setUp(self):
29 28 client = KernelClient()
30 29 self.shell = ZMQTerminalInteractiveShell(kernel_client=client)
31 30 self.raw = b'dummy data'
32 31 self.mime = 'image/png'
33 32 self.data = {self.mime: base64.encodestring(self.raw).decode('ascii')}
34 33
35 34 def test_no_call_by_default(self):
36 35 def raise_if_called(*args, **kwds):
37 36 assert False
38 37
39 38 shell = self.shell
40 39 shell.handle_image_PIL = raise_if_called
41 40 shell.handle_image_stream = raise_if_called
42 41 shell.handle_image_tempfile = raise_if_called
43 42 shell.handle_image_callable = raise_if_called
44 43
45 44 shell.handle_image(None, None) # arguments are dummy
46 45
47 46 @skip_without('PIL')
48 47 def test_handle_image_PIL(self):
49 48 import PIL.Image
50 49
51 50 open_called_with = []
52 51 show_called_with = []
53 52
54 53 def fake_open(arg):
55 54 open_called_with.append(arg)
56 55 return Struct(show=lambda: show_called_with.append(None))
57 56
58 57 with monkeypatch(PIL.Image, 'open', fake_open):
59 58 self.shell.handle_image_PIL(self.data, self.mime)
60 59
61 60 self.assertEqual(len(open_called_with), 1)
62 61 self.assertEqual(len(show_called_with), 1)
63 62 self.assertEqual(open_called_with[0].getvalue(), self.raw)
64 63
65 64 def check_handler_with_file(self, inpath, handler):
66 65 shell = self.shell
67 66 configname = '{0}_image_handler'.format(handler)
68 67 funcname = 'handle_image_{0}'.format(handler)
69 68
70 69 assert hasattr(shell, configname)
71 70 assert hasattr(shell, funcname)
72 71
73 72 with TemporaryDirectory() as tmpdir:
74 73 outpath = os.path.join(tmpdir, 'data')
75 74 cmd = [sys.executable, SCRIPT_PATH, inpath, outpath]
76 75 setattr(shell, configname, cmd)
77 76 getattr(shell, funcname)(self.data, self.mime)
78 77 # cmd is called and file is closed. So it's safe to open now.
79 78 with open(outpath, 'rb') as file:
80 79 transferred = file.read()
81 80
82 81 self.assertEqual(transferred, self.raw)
83 82
84 83 def test_handle_image_stream(self):
85 84 self.check_handler_with_file('-', 'stream')
86 85
87 86 def test_handle_image_tempfile(self):
88 87 self.check_handler_with_file('{file}', 'tempfile')
89 88
90 89 def test_handle_image_callable(self):
91 90 called_with = []
92 91 self.shell.callable_image_handler = called_with.append
93 92 self.shell.handle_image_callable(self.data, self.mime)
94 93 self.assertEqual(len(called_with), 1)
95 94 assert called_with[0] is self.data
1 NO CONTENT: file renamed from IPython/frontend/terminal/console/tests/writetofile.py to IPython/terminal/console/tests/writetofile.py
@@ -1,296 +1,296 b''
1 1 # encoding: utf-8
2 2 """
3 3 An embedded IPython shell.
4 4
5 5 Authors:
6 6
7 7 * Brian Granger
8 8 * Fernando Perez
9 9
10 10 Notes
11 11 -----
12 12 """
13 13
14 14 #-----------------------------------------------------------------------------
15 15 # Copyright (C) 2008-2011 The IPython Development Team
16 16 #
17 17 # Distributed under the terms of the BSD License. The full license is in
18 18 # the file COPYING, distributed as part of this software.
19 19 #-----------------------------------------------------------------------------
20 20
21 21 #-----------------------------------------------------------------------------
22 22 # Imports
23 23 #-----------------------------------------------------------------------------
24 24
25 25 from __future__ import with_statement
26 26
27 27 import sys
28 28 import warnings
29 29
30 30 # We need to use nested to support python 2.6, once we move to >=2.7, we can
31 31 # use the with keyword's new builtin support for nested managers
32 32 try:
33 33 from contextlib import nested
34 34 except:
35 35 from IPython.utils.nested_context import nested
36 36
37 37 from IPython.core import ultratb, compilerop
38 38 from IPython.core.magic import Magics, magics_class, line_magic
39 from IPython.frontend.terminal.interactiveshell import TerminalInteractiveShell
40 from IPython.frontend.terminal.ipapp import load_default_config
39 from IPython.terminal.interactiveshell import TerminalInteractiveShell
40 from IPython.terminal.ipapp import load_default_config
41 41
42 42 from IPython.utils.traitlets import Bool, CBool, Unicode
43 43 from IPython.utils.io import ask_yes_no
44 44
45 45
46 46 #-----------------------------------------------------------------------------
47 47 # Classes and functions
48 48 #-----------------------------------------------------------------------------
49 49
50 50 # This is an additional magic that is exposed in embedded shells.
51 51 @magics_class
52 52 class EmbeddedMagics(Magics):
53 53
54 54 @line_magic
55 55 def kill_embedded(self, parameter_s=''):
56 56 """%kill_embedded : deactivate for good the current embedded IPython.
57 57
58 58 This function (after asking for confirmation) sets an internal flag so
59 59 that an embedded IPython will never activate again. This is useful to
60 60 permanently disable a shell that is being called inside a loop: once
61 61 you've figured out what you needed from it, you may then kill it and
62 62 the program will then continue to run without the interactive shell
63 63 interfering again.
64 64 """
65 65
66 66 kill = ask_yes_no("Are you sure you want to kill this embedded instance "
67 67 "(y/n)? [y/N] ",'n')
68 68 if kill:
69 69 self.shell.embedded_active = False
70 70 print ("This embedded IPython will not reactivate anymore "
71 71 "once you exit.")
72 72
73 73
74 74 class InteractiveShellEmbed(TerminalInteractiveShell):
75 75
76 76 dummy_mode = Bool(False)
77 77 exit_msg = Unicode('')
78 78 embedded = CBool(True)
79 79 embedded_active = CBool(True)
80 80 # Like the base class display_banner is not configurable, but here it
81 81 # is True by default.
82 82 display_banner = CBool(True)
83 83
84 84 def __init__(self, config=None, ipython_dir=None, user_ns=None,
85 85 user_module=None, custom_exceptions=((),None),
86 86 usage=None, banner1=None, banner2=None,
87 87 display_banner=None, exit_msg=u'', user_global_ns=None):
88 88
89 89 if user_global_ns is not None:
90 90 warnings.warn("user_global_ns has been replaced by user_module. The\
91 91 parameter will be ignored.", DeprecationWarning)
92 92
93 93 super(InteractiveShellEmbed,self).__init__(
94 94 config=config, ipython_dir=ipython_dir, user_ns=user_ns,
95 95 user_module=user_module, custom_exceptions=custom_exceptions,
96 96 usage=usage, banner1=banner1, banner2=banner2,
97 97 display_banner=display_banner
98 98 )
99 99
100 100 self.exit_msg = exit_msg
101 101
102 102 # don't use the ipython crash handler so that user exceptions aren't
103 103 # trapped
104 104 sys.excepthook = ultratb.FormattedTB(color_scheme=self.colors,
105 105 mode=self.xmode,
106 106 call_pdb=self.pdb)
107 107
108 108 def init_sys_modules(self):
109 109 pass
110 110
111 111 def init_magics(self):
112 112 super(InteractiveShellEmbed, self).init_magics()
113 113 self.register_magics(EmbeddedMagics)
114 114
115 115 def __call__(self, header='', local_ns=None, module=None, dummy=None,
116 116 stack_depth=1, global_ns=None, compile_flags=None):
117 117 """Activate the interactive interpreter.
118 118
119 119 __call__(self,header='',local_ns=None,module=None,dummy=None) -> Start
120 120 the interpreter shell with the given local and global namespaces, and
121 121 optionally print a header string at startup.
122 122
123 123 The shell can be globally activated/deactivated using the
124 124 dummy_mode attribute. This allows you to turn off a shell used
125 125 for debugging globally.
126 126
127 127 However, *each* time you call the shell you can override the current
128 128 state of dummy_mode with the optional keyword parameter 'dummy'. For
129 129 example, if you set dummy mode on with IPShell.dummy_mode = True, you
130 130 can still have a specific call work by making it as IPShell(dummy=False).
131 131 """
132 132
133 133 # If the user has turned it off, go away
134 134 if not self.embedded_active:
135 135 return
136 136
137 137 # Normal exits from interactive mode set this flag, so the shell can't
138 138 # re-enter (it checks this variable at the start of interactive mode).
139 139 self.exit_now = False
140 140
141 141 # Allow the dummy parameter to override the global __dummy_mode
142 142 if dummy or (dummy != 0 and self.dummy_mode):
143 143 return
144 144
145 145 if self.has_readline:
146 146 self.set_readline_completer()
147 147
148 148 # self.banner is auto computed
149 149 if header:
150 150 self.old_banner2 = self.banner2
151 151 self.banner2 = self.banner2 + '\n' + header + '\n'
152 152 else:
153 153 self.old_banner2 = ''
154 154
155 155 # Call the embedding code with a stack depth of 1 so it can skip over
156 156 # our call and get the original caller's namespaces.
157 157 self.mainloop(local_ns, module, stack_depth=stack_depth,
158 158 global_ns=global_ns, compile_flags=compile_flags)
159 159
160 160 self.banner2 = self.old_banner2
161 161
162 162 if self.exit_msg is not None:
163 163 print self.exit_msg
164 164
165 165 def mainloop(self, local_ns=None, module=None, stack_depth=0,
166 166 display_banner=None, global_ns=None, compile_flags=None):
167 167 """Embeds IPython into a running python program.
168 168
169 169 Input:
170 170
171 171 - header: An optional header message can be specified.
172 172
173 173 - local_ns, module: working local namespace (a dict) and module (a
174 174 module or similar object). If given as None, they are automatically
175 175 taken from the scope where the shell was called, so that
176 176 program variables become visible.
177 177
178 178 - stack_depth: specifies how many levels in the stack to go to
179 179 looking for namespaces (when local_ns or module is None). This
180 180 allows an intermediate caller to make sure that this function gets
181 181 the namespace from the intended level in the stack. By default (0)
182 182 it will get its locals and globals from the immediate caller.
183 183
184 184 - compile_flags: A bit field identifying the __future__ features
185 185 that are enabled, as passed to the builtin `compile` function. If
186 186 given as None, they are automatically taken from the scope where the
187 187 shell was called.
188 188
189 189 Warning: it's possible to use this in a program which is being run by
190 190 IPython itself (via %run), but some funny things will happen (a few
191 191 globals get overwritten). In the future this will be cleaned up, as
192 192 there is no fundamental reason why it can't work perfectly."""
193 193
194 194 if (global_ns is not None) and (module is None):
195 195 class DummyMod(object):
196 196 """A dummy module object for embedded IPython."""
197 197 pass
198 198 warnings.warn("global_ns is deprecated, use module instead.", DeprecationWarning)
199 199 module = DummyMod()
200 200 module.__dict__ = global_ns
201 201
202 202 # Get locals and globals from caller
203 203 if ((local_ns is None or module is None or compile_flags is None)
204 204 and self.default_user_namespaces):
205 205 call_frame = sys._getframe(stack_depth).f_back
206 206
207 207 if local_ns is None:
208 208 local_ns = call_frame.f_locals
209 209 if module is None:
210 210 global_ns = call_frame.f_globals
211 211 module = sys.modules[global_ns['__name__']]
212 212 if compile_flags is None:
213 213 compile_flags = (call_frame.f_code.co_flags &
214 214 compilerop.PyCF_MASK)
215 215
216 216 # Save original namespace and module so we can restore them after
217 217 # embedding; otherwise the shell doesn't shut down correctly.
218 218 orig_user_module = self.user_module
219 219 orig_user_ns = self.user_ns
220 220 orig_compile_flags = self.compile.flags
221 221
222 222 # Update namespaces and fire up interpreter
223 223
224 224 # The global one is easy, we can just throw it in
225 225 if module is not None:
226 226 self.user_module = module
227 227
228 228 # But the user/local one is tricky: ipython needs it to store internal
229 229 # data, but we also need the locals. We'll throw our hidden variables
230 230 # like _ih and get_ipython() into the local namespace, but delete them
231 231 # later.
232 232 if local_ns is not None:
233 233 self.user_ns = local_ns
234 234 self.init_user_ns()
235 235
236 236 # Compiler flags
237 237 if compile_flags is not None:
238 238 self.compile.flags = compile_flags
239 239
240 240 # Patch for global embedding to make sure that things don't overwrite
241 241 # user globals accidentally. Thanks to Richard <rxe@renre-europe.com>
242 242 # FIXME. Test this a bit more carefully (the if.. is new)
243 243 # N.B. This can't now ever be called. Not sure what it was for.
244 244 # And now, since it wasn't called in the previous version, I'm
245 245 # commenting out these lines so they can't be called with my new changes
246 246 # --TK, 2011-12-10
247 247 #if local_ns is None and module is None:
248 248 # self.user_global_ns.update(__main__.__dict__)
249 249
250 250 # make sure the tab-completer has the correct frame information, so it
251 251 # actually completes using the frame's locals/globals
252 252 self.set_completer_frame()
253 253
254 254 with nested(self.builtin_trap, self.display_trap):
255 255 self.interact(display_banner=display_banner)
256 256
257 257 # now, purge out the local namespace of IPython's hidden variables.
258 258 if local_ns is not None:
259 259 for name in self.user_ns_hidden:
260 260 local_ns.pop(name, None)
261 261
262 262 # Restore original namespace so shell can shut down when we exit.
263 263 self.user_module = orig_user_module
264 264 self.user_ns = orig_user_ns
265 265 self.compile.flags = orig_compile_flags
266 266
267 267
268 268 def embed(**kwargs):
269 269 """Call this to embed IPython at the current point in your program.
270 270
271 271 The first invocation of this will create an :class:`InteractiveShellEmbed`
272 272 instance and then call it. Consecutive calls just call the already
273 273 created instance.
274 274
275 275 Here is a simple example::
276 276
277 277 from IPython import embed
278 278 a = 10
279 279 b = 20
280 280 embed('First time')
281 281 c = 30
282 282 d = 40
283 283 embed
284 284
285 285 Full customization can be done by passing a :class:`Config` in as the
286 286 config argument.
287 287 """
288 288 config = kwargs.get('config')
289 289 header = kwargs.pop('header', u'')
290 290 compile_flags = kwargs.pop('compile_flags', None)
291 291 if config is None:
292 292 config = load_default_config()
293 293 config.InteractiveShellEmbed = config.TerminalInteractiveShell
294 294 kwargs['config'] = config
295 295 shell = InteractiveShellEmbed.instance(**kwargs)
296 296 shell(header=header, stack_depth=2, compile_flags=compile_flags)
1 NO CONTENT: file renamed from IPython/frontend/terminal/interactiveshell.py to IPython/terminal/interactiveshell.py
@@ -1,395 +1,395 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 """
4 4 The :class:`~IPython.core.application.Application` object for the command
5 5 line :command:`ipython` program.
6 6
7 7 Authors
8 8 -------
9 9
10 10 * Brian Granger
11 11 * Fernando Perez
12 12 * Min Ragan-Kelley
13 13 """
14 14
15 15 #-----------------------------------------------------------------------------
16 16 # Copyright (C) 2008-2011 The IPython Development Team
17 17 #
18 18 # Distributed under the terms of the BSD License. The full license is in
19 19 # the file COPYING, distributed as part of this software.
20 20 #-----------------------------------------------------------------------------
21 21
22 22 #-----------------------------------------------------------------------------
23 23 # Imports
24 24 #-----------------------------------------------------------------------------
25 25
26 26 from __future__ import absolute_import
27 27
28 28 import logging
29 29 import os
30 30 import sys
31 31
32 32 from IPython.config.loader import (
33 33 Config, PyFileConfigLoader, ConfigFileNotFound
34 34 )
35 35 from IPython.config.application import boolean_flag, catch_config_error
36 36 from IPython.core import release
37 37 from IPython.core import usage
38 38 from IPython.core.completer import IPCompleter
39 39 from IPython.core.crashhandler import CrashHandler
40 40 from IPython.core.formatters import PlainTextFormatter
41 41 from IPython.core.history import HistoryManager
42 42 from IPython.core.prompts import PromptManager
43 43 from IPython.core.application import (
44 44 ProfileDir, BaseIPythonApplication, base_flags, base_aliases
45 45 )
46 46 from IPython.core.magics import ScriptMagics
47 47 from IPython.core.shellapp import (
48 48 InteractiveShellApp, shell_flags, shell_aliases
49 49 )
50 from IPython.frontend.terminal.interactiveshell import TerminalInteractiveShell
50 from IPython.terminal.interactiveshell import TerminalInteractiveShell
51 51 from IPython.utils import warn
52 52 from IPython.utils.path import get_ipython_dir, check_for_old_config
53 53 from IPython.utils.traitlets import (
54 54 Bool, List, Dict, CaselessStrEnum
55 55 )
56 56
57 57 #-----------------------------------------------------------------------------
58 58 # Globals, utilities and helpers
59 59 #-----------------------------------------------------------------------------
60 60
61 61 #: The default config file name for this application.
62 62 default_config_file_name = u'ipython_config.py'
63 63
64 64 _examples = """
65 65 ipython --pylab # start in pylab mode
66 66 ipython --pylab=qt # start in pylab mode with the qt4 backend
67 67 ipython --log-level=DEBUG # set logging to DEBUG
68 68 ipython --profile=foo # start with profile foo
69 69
70 70 ipython qtconsole # start the qtconsole GUI application
71 71 ipython help qtconsole # show the help for the qtconsole subcmd
72 72
73 73 ipython console # start the terminal-based console application
74 74 ipython help console # show the help for the console subcmd
75 75
76 76 ipython notebook # start the IPython notebook
77 77 ipython help notebook # show the help for the notebook subcmd
78 78
79 79 ipython profile create foo # create profile foo w/ default config files
80 80 ipython help profile # show the help for the profile subcmd
81 81
82 82 ipython locate # print the path to the IPython directory
83 83 ipython locate profile foo # print the path to the directory for profile `foo`
84 84 """
85 85
86 86 #-----------------------------------------------------------------------------
87 87 # Crash handler for this application
88 88 #-----------------------------------------------------------------------------
89 89
90 90 class IPAppCrashHandler(CrashHandler):
91 91 """sys.excepthook for IPython itself, leaves a detailed report on disk."""
92 92
93 93 def __init__(self, app):
94 94 contact_name = release.author
95 95 contact_email = release.author_email
96 96 bug_tracker = 'https://github.com/ipython/ipython/issues'
97 97 super(IPAppCrashHandler,self).__init__(
98 98 app, contact_name, contact_email, bug_tracker
99 99 )
100 100
101 101 def make_report(self,traceback):
102 102 """Return a string containing a crash report."""
103 103
104 104 sec_sep = self.section_sep
105 105 # Start with parent report
106 106 report = [super(IPAppCrashHandler, self).make_report(traceback)]
107 107 # Add interactive-specific info we may have
108 108 rpt_add = report.append
109 109 try:
110 110 rpt_add(sec_sep+"History of session input:")
111 111 for line in self.app.shell.user_ns['_ih']:
112 112 rpt_add(line)
113 113 rpt_add('\n*** Last line of input (may not be in above history):\n')
114 114 rpt_add(self.app.shell._last_input_line+'\n')
115 115 except:
116 116 pass
117 117
118 118 return ''.join(report)
119 119
120 120 #-----------------------------------------------------------------------------
121 121 # Aliases and Flags
122 122 #-----------------------------------------------------------------------------
123 123 flags = dict(base_flags)
124 124 flags.update(shell_flags)
125 125 frontend_flags = {}
126 126 addflag = lambda *args: frontend_flags.update(boolean_flag(*args))
127 127 addflag('autoedit-syntax', 'TerminalInteractiveShell.autoedit_syntax',
128 128 'Turn on auto editing of files with syntax errors.',
129 129 'Turn off auto editing of files with syntax errors.'
130 130 )
131 131 addflag('banner', 'TerminalIPythonApp.display_banner',
132 132 "Display a banner upon starting IPython.",
133 133 "Don't display a banner upon starting IPython."
134 134 )
135 135 addflag('confirm-exit', 'TerminalInteractiveShell.confirm_exit',
136 136 """Set to confirm when you try to exit IPython with an EOF (Control-D
137 137 in Unix, Control-Z/Enter in Windows). By typing 'exit' or 'quit',
138 138 you can force a direct exit without any confirmation.""",
139 139 "Don't prompt the user when exiting."
140 140 )
141 141 addflag('term-title', 'TerminalInteractiveShell.term_title',
142 142 "Enable auto setting the terminal title.",
143 143 "Disable auto setting the terminal title."
144 144 )
145 145 classic_config = Config()
146 146 classic_config.InteractiveShell.cache_size = 0
147 147 classic_config.PlainTextFormatter.pprint = False
148 148 classic_config.PromptManager.in_template = '>>> '
149 149 classic_config.PromptManager.in2_template = '... '
150 150 classic_config.PromptManager.out_template = ''
151 151 classic_config.InteractiveShell.separate_in = ''
152 152 classic_config.InteractiveShell.separate_out = ''
153 153 classic_config.InteractiveShell.separate_out2 = ''
154 154 classic_config.InteractiveShell.colors = 'NoColor'
155 155 classic_config.InteractiveShell.xmode = 'Plain'
156 156
157 157 frontend_flags['classic']=(
158 158 classic_config,
159 159 "Gives IPython a similar feel to the classic Python prompt."
160 160 )
161 161 # # log doesn't make so much sense this way anymore
162 162 # paa('--log','-l',
163 163 # action='store_true', dest='InteractiveShell.logstart',
164 164 # help="Start logging to the default log file (./ipython_log.py).")
165 165 #
166 166 # # quick is harder to implement
167 167 frontend_flags['quick']=(
168 168 {'TerminalIPythonApp' : {'quick' : True}},
169 169 "Enable quick startup with no config files."
170 170 )
171 171
172 172 frontend_flags['i'] = (
173 173 {'TerminalIPythonApp' : {'force_interact' : True}},
174 174 """If running code from the command line, become interactive afterwards.
175 175 Note: can also be given simply as '-i.'"""
176 176 )
177 177 flags.update(frontend_flags)
178 178
179 179 aliases = dict(base_aliases)
180 180 aliases.update(shell_aliases)
181 181
182 182 #-----------------------------------------------------------------------------
183 183 # Main classes and functions
184 184 #-----------------------------------------------------------------------------
185 185
186 186
187 187 class LocateIPythonApp(BaseIPythonApplication):
188 188 description = """print the path to the IPython dir"""
189 189 subcommands = Dict(dict(
190 190 profile=('IPython.core.profileapp.ProfileLocate',
191 191 "print the path to an IPython profile directory",
192 192 ),
193 193 ))
194 194 def start(self):
195 195 if self.subapp is not None:
196 196 return self.subapp.start()
197 197 else:
198 198 print self.ipython_dir
199 199
200 200
201 201 class TerminalIPythonApp(BaseIPythonApplication, InteractiveShellApp):
202 202 name = u'ipython'
203 203 description = usage.cl_usage
204 204 default_config_file_name = default_config_file_name
205 205 crash_handler_class = IPAppCrashHandler
206 206 examples = _examples
207 207
208 208 flags = Dict(flags)
209 209 aliases = Dict(aliases)
210 210 classes = List()
211 211 def _classes_default(self):
212 212 """This has to be in a method, for TerminalIPythonApp to be available."""
213 213 return [
214 214 InteractiveShellApp, # ShellApp comes before TerminalApp, because
215 215 self.__class__, # it will also affect subclasses (e.g. QtConsole)
216 216 TerminalInteractiveShell,
217 217 PromptManager,
218 218 HistoryManager,
219 219 ProfileDir,
220 220 PlainTextFormatter,
221 221 IPCompleter,
222 222 ScriptMagics,
223 223 ]
224 224
225 225 subcommands = Dict(dict(
226 qtconsole=('IPython.frontend.qt.console.qtconsoleapp.IPythonQtConsoleApp',
226 qtconsole=('IPython.qt.console.qtconsoleapp.IPythonQtConsoleApp',
227 227 """Launch the IPython Qt Console."""
228 228 ),
229 notebook=('IPython.frontend.html.notebook.notebookapp.NotebookApp',
229 notebook=('IPython.html.notebookapp.NotebookApp',
230 230 """Launch the IPython HTML Notebook Server."""
231 231 ),
232 232 profile = ("IPython.core.profileapp.ProfileApp",
233 233 "Create and manage IPython profiles."
234 234 ),
235 235 kernel = ("IPython.kernel.zmq.kernelapp.IPKernelApp",
236 236 "Start a kernel without an attached frontend."
237 237 ),
238 console=('IPython.frontend.terminal.console.app.ZMQTerminalIPythonApp',
238 console=('IPython.terminal.console.app.ZMQTerminalIPythonApp',
239 239 """Launch the IPython terminal-based Console."""
240 240 ),
241 locate=('IPython.frontend.terminal.ipapp.LocateIPythonApp',
241 locate=('IPython.terminal.ipapp.LocateIPythonApp',
242 242 LocateIPythonApp.description
243 243 ),
244 244 history=('IPython.core.historyapp.HistoryApp',
245 245 "Manage the IPython history database."
246 246 ),
247 247 ))
248 248
249 249 # *do* autocreate requested profile, but don't create the config file.
250 250 auto_create=Bool(True)
251 251 # configurables
252 252 ignore_old_config=Bool(False, config=True,
253 253 help="Suppress warning messages about legacy config files"
254 254 )
255 255 quick = Bool(False, config=True,
256 256 help="""Start IPython quickly by skipping the loading of config files."""
257 257 )
258 258 def _quick_changed(self, name, old, new):
259 259 if new:
260 260 self.load_config_file = lambda *a, **kw: None
261 261 self.ignore_old_config=True
262 262
263 263 display_banner = Bool(True, config=True,
264 264 help="Whether to display a banner upon starting IPython."
265 265 )
266 266
267 267 # if there is code of files to run from the cmd line, don't interact
268 268 # unless the --i flag (App.force_interact) is true.
269 269 force_interact = Bool(False, config=True,
270 270 help="""If a command or file is given via the command-line,
271 271 e.g. 'ipython foo.py"""
272 272 )
273 273 def _force_interact_changed(self, name, old, new):
274 274 if new:
275 275 self.interact = True
276 276
277 277 def _file_to_run_changed(self, name, old, new):
278 278 if new:
279 279 self.something_to_run = True
280 280 if new and not self.force_interact:
281 281 self.interact = False
282 282 _code_to_run_changed = _file_to_run_changed
283 283 _module_to_run_changed = _file_to_run_changed
284 284
285 285 # internal, not-configurable
286 286 interact=Bool(True)
287 287 something_to_run=Bool(False)
288 288
289 289 def parse_command_line(self, argv=None):
290 290 """override to allow old '-pylab' flag with deprecation warning"""
291 291
292 292 argv = sys.argv[1:] if argv is None else argv
293 293
294 294 if '-pylab' in argv:
295 295 # deprecated `-pylab` given,
296 296 # warn and transform into current syntax
297 297 argv = argv[:] # copy, don't clobber
298 298 idx = argv.index('-pylab')
299 299 warn.warn("`-pylab` flag has been deprecated.\n"
300 300 " Use `--pylab` instead, or `--pylab=foo` to specify a backend.")
301 301 sub = '--pylab'
302 302 if len(argv) > idx+1:
303 303 # check for gui arg, as in '-pylab qt'
304 304 gui = argv[idx+1]
305 305 if gui in ('wx', 'qt', 'qt4', 'gtk', 'auto'):
306 306 sub = '--pylab='+gui
307 307 argv.pop(idx+1)
308 308 argv[idx] = sub
309 309
310 310 return super(TerminalIPythonApp, self).parse_command_line(argv)
311 311
312 312 @catch_config_error
313 313 def initialize(self, argv=None):
314 314 """Do actions after construct, but before starting the app."""
315 315 super(TerminalIPythonApp, self).initialize(argv)
316 316 if self.subapp is not None:
317 317 # don't bother initializing further, starting subapp
318 318 return
319 319 if not self.ignore_old_config:
320 320 check_for_old_config(self.ipython_dir)
321 321 # print self.extra_args
322 322 if self.extra_args and not self.something_to_run:
323 323 self.file_to_run = self.extra_args[0]
324 324 self.init_path()
325 325 # create the shell
326 326 self.init_shell()
327 327 # and draw the banner
328 328 self.init_banner()
329 329 # Now a variety of things that happen after the banner is printed.
330 330 self.init_gui_pylab()
331 331 self.init_extensions()
332 332 self.init_code()
333 333
334 334 def init_shell(self):
335 335 """initialize the InteractiveShell instance"""
336 336 # Create an InteractiveShell instance.
337 337 # shell.display_banner should always be False for the terminal
338 338 # based app, because we call shell.show_banner() by hand below
339 339 # so the banner shows *before* all extension loading stuff.
340 340 self.shell = TerminalInteractiveShell.instance(config=self.config,
341 341 display_banner=False, profile_dir=self.profile_dir,
342 342 ipython_dir=self.ipython_dir)
343 343 self.shell.configurables.append(self)
344 344
345 345 def init_banner(self):
346 346 """optionally display the banner"""
347 347 if self.display_banner and self.interact:
348 348 self.shell.show_banner()
349 349 # Make sure there is a space below the banner.
350 350 if self.log_level <= logging.INFO: print
351 351
352 352 def _pylab_changed(self, name, old, new):
353 353 """Replace --pylab='inline' with --pylab='auto'"""
354 354 if new == 'inline':
355 355 warn.warn("'inline' not available as pylab backend, "
356 356 "using 'auto' instead.")
357 357 self.pylab = 'auto'
358 358
359 359 def start(self):
360 360 if self.subapp is not None:
361 361 return self.subapp.start()
362 362 # perform any prexec steps:
363 363 if self.interact:
364 364 self.log.debug("Starting IPython's mainloop...")
365 365 self.shell.mainloop()
366 366 else:
367 367 self.log.debug("IPython not interactive...")
368 368
369 369
370 370 def load_default_config(ipython_dir=None):
371 371 """Load the default config file from the default ipython_dir.
372 372
373 373 This is useful for embedded shells.
374 374 """
375 375 if ipython_dir is None:
376 376 ipython_dir = get_ipython_dir()
377 377 profile_dir = os.path.join(ipython_dir, 'profile_default')
378 378 cl = PyFileConfigLoader(default_config_file_name, profile_dir)
379 379 try:
380 380 config = cl.load_config()
381 381 except ConfigFileNotFound:
382 382 # no config found
383 383 config = Config()
384 384 return config
385 385
386 386
387 387 def launch_new_instance():
388 388 """Create and run a full blown IPython instance"""
389 389 app = TerminalIPythonApp.instance()
390 390 app.initialize()
391 391 app.start()
392 392
393 393
394 394 if __name__ == '__main__':
395 395 launch_new_instance()
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/services/notebooks/__init__.py to IPython/terminal/tests/__init__.py
1 NO CONTENT: file renamed from IPython/frontend/terminal/tests/test_interactivshell.py to IPython/terminal/tests/test_interactivshell.py
@@ -1,358 +1,360 b''
1 1 # -*- coding: utf-8 -*-
2 2 """Decorators for labeling test objects.
3 3
4 4 Decorators that merely return a modified version of the original function
5 5 object are straightforward. Decorators that return a new function object need
6 6 to use nose.tools.make_decorator(original_function)(decorator) in returning the
7 7 decorator, in order to preserve metadata such as function name, setup and
8 8 teardown functions and so on - see nose.tools for more information.
9 9
10 10 This module provides a set of useful decorators meant to be ready to use in
11 11 your own tests. See the bottom of the file for the ready-made ones, and if you
12 12 find yourself writing a new one that may be of generic use, add it here.
13 13
14 14 Included decorators:
15 15
16 16
17 17 Lightweight testing that remains unittest-compatible.
18 18
19 19 - @parametric, for parametric test support that is vastly easier to use than
20 20 nose's for debugging. With ours, if a test fails, the stack under inspection
21 21 is that of the test and not that of the test framework.
22 22
23 23 - An @as_unittest decorator can be used to tag any normal parameter-less
24 24 function as a unittest TestCase. Then, both nose and normal unittest will
25 25 recognize it as such. This will make it easier to migrate away from Nose if
26 26 we ever need/want to while maintaining very lightweight tests.
27 27
28 28 NOTE: This file contains IPython-specific decorators. Using the machinery in
29 29 IPython.external.decorators, we import either numpy.testing.decorators if numpy is
30 30 available, OR use equivalent code in IPython.external._decorators, which
31 31 we've copied verbatim from numpy.
32 32
33 33 Authors
34 34 -------
35 35
36 36 - Fernando Perez <Fernando.Perez@berkeley.edu>
37 37 """
38 38
39 39 #-----------------------------------------------------------------------------
40 40 # Copyright (C) 2009-2011 The IPython Development Team
41 41 #
42 42 # Distributed under the terms of the BSD License. The full license is in
43 43 # the file COPYING, distributed as part of this software.
44 44 #-----------------------------------------------------------------------------
45 45
46 46 #-----------------------------------------------------------------------------
47 47 # Imports
48 48 #-----------------------------------------------------------------------------
49 49
50 50 # Stdlib imports
51 51 import inspect
52 52 import sys
53 53 import tempfile
54 54 import unittest
55 55
56 56 # Third-party imports
57 57
58 58 # This is Michele Simionato's decorator module, kept verbatim.
59 59 from IPython.external.decorator import decorator
60 60
61 61 # We already have python3-compliant code for parametric tests
62 62 if sys.version[0]=='2':
63 63 from _paramtestpy2 import parametric, ParametricTestCase
64 64 else:
65 65 from _paramtestpy3 import parametric, ParametricTestCase
66 66
67 67 # Expose the unittest-driven decorators
68 68 from ipunittest import ipdoctest, ipdocstring
69 69
70 70 # Grab the numpy-specific decorators which we keep in a file that we
71 71 # occasionally update from upstream: decorators.py is a copy of
72 72 # numpy.testing.decorators, we expose all of it here.
73 73 from IPython.external.decorators import *
74 74
75 75 # For onlyif_cmd_exists decorator
76 76 from IPython.utils.process import is_cmd_found
77 77
78 78 #-----------------------------------------------------------------------------
79 79 # Classes and functions
80 80 #-----------------------------------------------------------------------------
81 81
82 82 # Simple example of the basic idea
83 83 def as_unittest(func):
84 84 """Decorator to make a simple function into a normal test via unittest."""
85 85 class Tester(unittest.TestCase):
86 86 def test(self):
87 87 func()
88 88
89 89 Tester.__name__ = func.__name__
90 90
91 91 return Tester
92 92
93 93 # Utility functions
94 94
95 95 def apply_wrapper(wrapper,func):
96 96 """Apply a wrapper to a function for decoration.
97 97
98 98 This mixes Michele Simionato's decorator tool with nose's make_decorator,
99 99 to apply a wrapper in a decorator so that all nose attributes, as well as
100 100 function signature and other properties, survive the decoration cleanly.
101 101 This will ensure that wrapped functions can still be well introspected via
102 102 IPython, for example.
103 103 """
104 104 import nose.tools
105 105
106 106 return decorator(wrapper,nose.tools.make_decorator(func)(wrapper))
107 107
108 108
109 109 def make_label_dec(label,ds=None):
110 110 """Factory function to create a decorator that applies one or more labels.
111 111
112 112 Parameters
113 113 ----------
114 114 label : string or sequence
115 115 One or more labels that will be applied by the decorator to the functions
116 116 it decorates. Labels are attributes of the decorated function with their
117 117 value set to True.
118 118
119 119 ds : string
120 120 An optional docstring for the resulting decorator. If not given, a
121 121 default docstring is auto-generated.
122 122
123 123 Returns
124 124 -------
125 125 A decorator.
126 126
127 127 Examples
128 128 --------
129 129
130 130 A simple labeling decorator:
131 >>> slow = make_label_dec('slow')
132 >>> print slow.__doc__
133 Labels a test as 'slow'.
134 131
132 >>> slow = make_label_dec('slow')
133 >>> slow.__doc__
134 "Labels a test as 'slow'."
135
135 136 And one that uses multiple labels and a custom docstring:
137
136 138 >>> rare = make_label_dec(['slow','hard'],
137 139 ... "Mix labels 'slow' and 'hard' for rare tests.")
138 >>> print rare.__doc__
139 Mix labels 'slow' and 'hard' for rare tests.
140 >>> rare.__doc__
141 "Mix labels 'slow' and 'hard' for rare tests."
140 142
141 143 Now, let's test using this one:
142 144 >>> @rare
143 145 ... def f(): pass
144 146 ...
145 147 >>>
146 148 >>> f.slow
147 149 True
148 150 >>> f.hard
149 151 True
150 152 """
151 153
152 154 if isinstance(label,basestring):
153 155 labels = [label]
154 156 else:
155 157 labels = label
156 158
157 159 # Validate that the given label(s) are OK for use in setattr() by doing a
158 160 # dry run on a dummy function.
159 161 tmp = lambda : None
160 162 for label in labels:
161 163 setattr(tmp,label,True)
162 164
163 165 # This is the actual decorator we'll return
164 166 def decor(f):
165 167 for label in labels:
166 168 setattr(f,label,True)
167 169 return f
168 170
169 171 # Apply the user's docstring, or autogenerate a basic one
170 172 if ds is None:
171 173 ds = "Labels a test as %r." % label
172 174 decor.__doc__ = ds
173 175
174 176 return decor
175 177
176 178
177 179 # Inspired by numpy's skipif, but uses the full apply_wrapper utility to
178 180 # preserve function metadata better and allows the skip condition to be a
179 181 # callable.
180 182 def skipif(skip_condition, msg=None):
181 183 ''' Make function raise SkipTest exception if skip_condition is true
182 184
183 185 Parameters
184 186 ----------
185 187 skip_condition : bool or callable.
186 188 Flag to determine whether to skip test. If the condition is a
187 189 callable, it is used at runtime to dynamically make the decision. This
188 190 is useful for tests that may require costly imports, to delay the cost
189 191 until the test suite is actually executed.
190 192 msg : string
191 193 Message to give on raising a SkipTest exception
192 194
193 195 Returns
194 196 -------
195 197 decorator : function
196 198 Decorator, which, when applied to a function, causes SkipTest
197 199 to be raised when the skip_condition was True, and the function
198 200 to be called normally otherwise.
199 201
200 202 Notes
201 203 -----
202 204 You will see from the code that we had to further decorate the
203 205 decorator with the nose.tools.make_decorator function in order to
204 206 transmit function name, and various other metadata.
205 207 '''
206 208
207 209 def skip_decorator(f):
208 210 # Local import to avoid a hard nose dependency and only incur the
209 211 # import time overhead at actual test-time.
210 212 import nose
211 213
212 214 # Allow for both boolean or callable skip conditions.
213 215 if callable(skip_condition):
214 216 skip_val = skip_condition
215 217 else:
216 218 skip_val = lambda : skip_condition
217 219
218 220 def get_msg(func,msg=None):
219 221 """Skip message with information about function being skipped."""
220 222 if msg is None: out = 'Test skipped due to test condition.'
221 223 else: out = msg
222 224 return "Skipping test: %s. %s" % (func.__name__,out)
223 225
224 226 # We need to define *two* skippers because Python doesn't allow both
225 227 # return with value and yield inside the same function.
226 228 def skipper_func(*args, **kwargs):
227 229 """Skipper for normal test functions."""
228 230 if skip_val():
229 231 raise nose.SkipTest(get_msg(f,msg))
230 232 else:
231 233 return f(*args, **kwargs)
232 234
233 235 def skipper_gen(*args, **kwargs):
234 236 """Skipper for test generators."""
235 237 if skip_val():
236 238 raise nose.SkipTest(get_msg(f,msg))
237 239 else:
238 240 for x in f(*args, **kwargs):
239 241 yield x
240 242
241 243 # Choose the right skipper to use when building the actual generator.
242 244 if nose.util.isgenerator(f):
243 245 skipper = skipper_gen
244 246 else:
245 247 skipper = skipper_func
246 248
247 249 return nose.tools.make_decorator(f)(skipper)
248 250
249 251 return skip_decorator
250 252
251 253 # A version with the condition set to true, common case just to attach a message
252 254 # to a skip decorator
253 255 def skip(msg=None):
254 256 """Decorator factory - mark a test function for skipping from test suite.
255 257
256 258 Parameters
257 259 ----------
258 260 msg : string
259 261 Optional message to be added.
260 262
261 263 Returns
262 264 -------
263 265 decorator : function
264 266 Decorator, which, when applied to a function, causes SkipTest
265 267 to be raised, with the optional message added.
266 268 """
267 269
268 270 return skipif(True,msg)
269 271
270 272
271 273 def onlyif(condition, msg):
272 274 """The reverse from skipif, see skipif for details."""
273 275
274 276 if callable(condition):
275 277 skip_condition = lambda : not condition()
276 278 else:
277 279 skip_condition = lambda : not condition
278 280
279 281 return skipif(skip_condition, msg)
280 282
281 283 #-----------------------------------------------------------------------------
282 284 # Utility functions for decorators
283 285 def module_not_available(module):
284 286 """Can module be imported? Returns true if module does NOT import.
285 287
286 288 This is used to make a decorator to skip tests that require module to be
287 289 available, but delay the 'import numpy' to test execution time.
288 290 """
289 291 try:
290 292 mod = __import__(module)
291 293 mod_not_avail = False
292 294 except ImportError:
293 295 mod_not_avail = True
294 296
295 297 return mod_not_avail
296 298
297 299 #-----------------------------------------------------------------------------
298 300 # Decorators for public use
299 301
300 302 # Decorators to skip certain tests on specific platforms.
301 303 skip_win32 = skipif(sys.platform == 'win32',
302 304 "This test does not run under Windows")
303 305 skip_linux = skipif(sys.platform.startswith('linux'),
304 306 "This test does not run under Linux")
305 307 skip_osx = skipif(sys.platform == 'darwin',"This test does not run under OS X")
306 308
307 309
308 310 # Decorators to skip tests if not on specific platforms.
309 311 skip_if_not_win32 = skipif(sys.platform != 'win32',
310 312 "This test only runs under Windows")
311 313 skip_if_not_linux = skipif(not sys.platform.startswith('linux'),
312 314 "This test only runs under Linux")
313 315 skip_if_not_osx = skipif(sys.platform != 'darwin',
314 316 "This test only runs under OSX")
315 317
316 318 # Other skip decorators
317 319
318 320 # generic skip without module
319 321 skip_without = lambda mod: skipif(module_not_available(mod), "This test requires %s" % mod)
320 322
321 323 skipif_not_numpy = skip_without('numpy')
322 324
323 325 skipif_not_matplotlib = skip_without('matplotlib')
324 326
325 327 skipif_not_sympy = skip_without('sympy')
326 328
327 329 skip_known_failure = knownfailureif(True,'This test is known to fail')
328 330
329 331 known_failure_py3 = knownfailureif(sys.version_info[0] >= 3,
330 332 'This test is known to fail on Python 3.')
331 333
332 334 # A null 'decorator', useful to make more readable code that needs to pick
333 335 # between different decorators based on OS or other conditions
334 336 null_deco = lambda f: f
335 337
336 338 # Some tests only run where we can use unicode paths. Note that we can't just
337 339 # check os.path.supports_unicode_filenames, which is always False on Linux.
338 340 try:
339 341 f = tempfile.NamedTemporaryFile(prefix=u"tmp€")
340 342 except UnicodeEncodeError:
341 343 unicode_paths = False
342 344 else:
343 345 unicode_paths = True
344 346 f.close()
345 347
346 348 onlyif_unicode_paths = onlyif(unicode_paths, ("This test is only applicable "
347 349 "where we can use unicode in filenames."))
348 350
349 351
350 352 def onlyif_cmds_exist(*commands):
351 353 """
352 354 Decorator to skip test when at least one of `commands` is not found.
353 355 """
354 356 for cmd in commands:
355 357 if not is_cmd_found(cmd):
356 358 return skip("This test runs only if command '{0}' "
357 359 "is installed".format(cmd))
358 360 return null_deco
@@ -1,176 +1,176 b''
1 1 """Global IPython app to support test running.
2 2
3 3 We must start our own ipython object and heavily muck with it so that all the
4 4 modifications IPython makes to system behavior don't send the doctest machinery
5 5 into a fit. This code should be considered a gross hack, but it gets the job
6 6 done.
7 7 """
8 8 from __future__ import absolute_import
9 9 from __future__ import print_function
10 10
11 11 #-----------------------------------------------------------------------------
12 12 # Copyright (C) 2009-2011 The IPython Development Team
13 13 #
14 14 # Distributed under the terms of the BSD License. The full license is in
15 15 # the file COPYING, distributed as part of this software.
16 16 #-----------------------------------------------------------------------------
17 17
18 18 #-----------------------------------------------------------------------------
19 19 # Imports
20 20 #-----------------------------------------------------------------------------
21 21
22 22 # stdlib
23 23 import __builtin__ as builtin_mod
24 24 import os
25 25 import sys
26 26
27 27 # our own
28 28 from . import tools
29 29
30 30 from IPython.core import page
31 31 from IPython.utils import io
32 32 from IPython.utils import py3compat
33 from IPython.frontend.terminal.interactiveshell import TerminalInteractiveShell
33 from IPython.terminal.interactiveshell import TerminalInteractiveShell
34 34
35 35 #-----------------------------------------------------------------------------
36 36 # Functions
37 37 #-----------------------------------------------------------------------------
38 38
39 39 class StreamProxy(io.IOStream):
40 40 """Proxy for sys.stdout/err. This will request the stream *at call time*
41 41 allowing for nose's Capture plugin's redirection of sys.stdout/err.
42 42
43 43 Parameters
44 44 ----------
45 45 name : str
46 46 The name of the stream. This will be requested anew at every call
47 47 """
48 48
49 49 def __init__(self, name):
50 50 self.name=name
51 51
52 52 @property
53 53 def stream(self):
54 54 return getattr(sys, self.name)
55 55
56 56 def flush(self):
57 57 self.stream.flush()
58 58
59 59 # Hack to modify the %run command so we can sync the user's namespace with the
60 60 # test globals. Once we move over to a clean magic system, this will be done
61 61 # with much less ugliness.
62 62
63 63 class py_file_finder(object):
64 64 def __init__(self,test_filename):
65 65 self.test_filename = test_filename
66 66
67 67 def __call__(self,name,win32=False):
68 68 from IPython.utils.path import get_py_filename
69 69 try:
70 70 return get_py_filename(name,win32=win32)
71 71 except IOError:
72 72 test_dir = os.path.dirname(self.test_filename)
73 73 new_path = os.path.join(test_dir,name)
74 74 return get_py_filename(new_path,win32=win32)
75 75
76 76
77 77 def _run_ns_sync(self,arg_s,runner=None):
78 78 """Modified version of %run that syncs testing namespaces.
79 79
80 80 This is strictly needed for running doctests that call %run.
81 81 """
82 82 #print('in run_ns_sync', arg_s, file=sys.stderr) # dbg
83 83 finder = py_file_finder(arg_s)
84 84 return get_ipython().magic_run_ori(arg_s, runner, finder)
85 85
86 86
87 87 def get_ipython():
88 88 # This will get replaced by the real thing once we start IPython below
89 89 return start_ipython()
90 90
91 91
92 92 # A couple of methods to override those in the running IPython to interact
93 93 # better with doctest (doctest captures on raw stdout, so we need to direct
94 94 # various types of output there otherwise it will miss them).
95 95
96 96 def xsys(self, cmd):
97 97 """Replace the default system call with a capturing one for doctest.
98 98 """
99 99 # We use getoutput, but we need to strip it because pexpect captures
100 100 # the trailing newline differently from commands.getoutput
101 101 print(self.getoutput(cmd, split=False, depth=1).rstrip(), end='', file=sys.stdout)
102 102 sys.stdout.flush()
103 103
104 104
105 105 def _showtraceback(self, etype, evalue, stb):
106 106 """Print the traceback purely on stdout for doctest to capture it.
107 107 """
108 108 print(self.InteractiveTB.stb2text(stb), file=sys.stdout)
109 109
110 110
111 111 def start_ipython():
112 112 """Start a global IPython shell, which we need for IPython-specific syntax.
113 113 """
114 114 global get_ipython
115 115
116 116 # This function should only ever run once!
117 117 if hasattr(start_ipython, 'already_called'):
118 118 return
119 119 start_ipython.already_called = True
120 120
121 121 # Store certain global objects that IPython modifies
122 122 _displayhook = sys.displayhook
123 123 _excepthook = sys.excepthook
124 124 _main = sys.modules.get('__main__')
125 125
126 126 # Create custom argv and namespaces for our IPython to be test-friendly
127 127 config = tools.default_config()
128 128
129 129 # Create and initialize our test-friendly IPython instance.
130 130 shell = TerminalInteractiveShell.instance(config=config,
131 131 )
132 132
133 133 # A few more tweaks needed for playing nicely with doctests...
134 134
135 135 # remove history file
136 136 shell.tempfiles.append(config.HistoryManager.hist_file)
137 137
138 138 # These traps are normally only active for interactive use, set them
139 139 # permanently since we'll be mocking interactive sessions.
140 140 shell.builtin_trap.activate()
141 141
142 142 # Modify the IPython system call with one that uses getoutput, so that we
143 143 # can capture subcommands and print them to Python's stdout, otherwise the
144 144 # doctest machinery would miss them.
145 145 shell.system = py3compat.MethodType(xsys, shell)
146 146
147 147 shell._showtraceback = py3compat.MethodType(_showtraceback, shell)
148 148
149 149 # IPython is ready, now clean up some global state...
150 150
151 151 # Deactivate the various python system hooks added by ipython for
152 152 # interactive convenience so we don't confuse the doctest system
153 153 sys.modules['__main__'] = _main
154 154 sys.displayhook = _displayhook
155 155 sys.excepthook = _excepthook
156 156
157 157 # So that ipython magics and aliases can be doctested (they work by making
158 158 # a call into a global _ip object). Also make the top-level get_ipython
159 159 # now return this without recursively calling here again.
160 160 _ip = shell
161 161 get_ipython = _ip.get_ipython
162 162 builtin_mod._ip = _ip
163 163 builtin_mod.get_ipython = get_ipython
164 164
165 165 # To avoid extra IPython messages during testing, suppress io.stdout/stderr
166 166 io.stdout = StreamProxy('stdout')
167 167 io.stderr = StreamProxy('stderr')
168 168
169 169 # Override paging, so we don't require user interaction during the tests.
170 170 def nopage(strng, start=0, screen_lines=0, pager_cmd=None):
171 171 print(strng)
172 172
173 173 page.orig_page = page.page
174 174 page.page = nopage
175 175
176 176 return _ip
@@ -1,603 +1,610 b''
1 1 # -*- coding: utf-8 -*-
2 2 """IPython Test Suite Runner.
3 3
4 4 This module provides a main entry point to a user script to test IPython
5 5 itself from the command line. There are two ways of running this script:
6 6
7 7 1. With the syntax `iptest all`. This runs our entire test suite by
8 8 calling this script (with different arguments) recursively. This
9 9 causes modules and package to be tested in different processes, using nose
10 10 or trial where appropriate.
11 11 2. With the regular nose syntax, like `iptest -vvs IPython`. In this form
12 12 the script simply calls nose, but with special command line flags and
13 13 plugins loaded.
14 14
15 15 """
16 16
17 17 #-----------------------------------------------------------------------------
18 18 # Copyright (C) 2009-2011 The IPython Development Team
19 19 #
20 20 # Distributed under the terms of the BSD License. The full license is in
21 21 # the file COPYING, distributed as part of this software.
22 22 #-----------------------------------------------------------------------------
23 23
24 24 #-----------------------------------------------------------------------------
25 25 # Imports
26 26 #-----------------------------------------------------------------------------
27 27 from __future__ import print_function
28 28
29 29 # Stdlib
30 30 import glob
31 31 import os
32 32 import os.path as path
33 33 import signal
34 34 import sys
35 35 import subprocess
36 36 import tempfile
37 37 import time
38 38 import warnings
39 39
40 40 # Note: monkeypatch!
41 41 # We need to monkeypatch a small problem in nose itself first, before importing
42 42 # it for actual use. This should get into nose upstream, but its release cycle
43 43 # is slow and we need it for our parametric tests to work correctly.
44 44 from IPython.testing import nosepatch
45 45
46 46 # Monkeypatch extra assert methods into nose.tools if they're not already there.
47 47 # This can be dropped once we no longer test on Python 2.6
48 48 from IPython.testing import nose_assert_methods
49 49
50 50 # Now, proceed to import nose itself
51 51 import nose.plugins.builtin
52 52 from nose.plugins.xunit import Xunit
53 53 from nose import SkipTest
54 54 from nose.core import TestProgram
55 55
56 56 # Our own imports
57 57 from IPython.utils import py3compat
58 58 from IPython.utils.importstring import import_item
59 59 from IPython.utils.path import get_ipython_module_path, get_ipython_package_dir
60 60 from IPython.utils.process import find_cmd, pycmd2argv
61 61 from IPython.utils.sysinfo import sys_info
62 62 from IPython.utils.tempdir import TemporaryDirectory
63 63 from IPython.utils.warn import warn
64 64
65 65 from IPython.testing import globalipapp
66 66 from IPython.testing.plugin.ipdoctest import IPythonDoctest
67 67 from IPython.external.decorators import KnownFailure, knownfailureif
68 68
69 69 pjoin = path.join
70 70
71 71
72 72 #-----------------------------------------------------------------------------
73 73 # Globals
74 74 #-----------------------------------------------------------------------------
75 75
76 76
77 77 #-----------------------------------------------------------------------------
78 78 # Warnings control
79 79 #-----------------------------------------------------------------------------
80 80
81 81 # Twisted generates annoying warnings with Python 2.6, as will do other code
82 82 # that imports 'sets' as of today
83 83 warnings.filterwarnings('ignore', 'the sets module is deprecated',
84 84 DeprecationWarning )
85 85
86 86 # This one also comes from Twisted
87 87 warnings.filterwarnings('ignore', 'the sha module is deprecated',
88 88 DeprecationWarning)
89 89
90 90 # Wx on Fedora11 spits these out
91 91 warnings.filterwarnings('ignore', 'wxPython/wxWidgets release number mismatch',
92 92 UserWarning)
93 93
94 94 # ------------------------------------------------------------------------------
95 95 # Monkeypatch Xunit to count known failures as skipped.
96 96 # ------------------------------------------------------------------------------
97 97 def monkeypatch_xunit():
98 98 try:
99 99 knownfailureif(True)(lambda: None)()
100 100 except Exception as e:
101 101 KnownFailureTest = type(e)
102 102
103 103 def addError(self, test, err, capt=None):
104 104 if issubclass(err[0], KnownFailureTest):
105 105 err = (SkipTest,) + err[1:]
106 106 return self.orig_addError(test, err, capt)
107 107
108 108 Xunit.orig_addError = Xunit.addError
109 109 Xunit.addError = addError
110 110
111 111 #-----------------------------------------------------------------------------
112 112 # Logic for skipping doctests
113 113 #-----------------------------------------------------------------------------
114 114 def extract_version(mod):
115 115 return mod.__version__
116 116
117 117 def test_for(item, min_version=None, callback=extract_version):
118 118 """Test to see if item is importable, and optionally check against a minimum
119 119 version.
120 120
121 121 If min_version is given, the default behavior is to check against the
122 122 `__version__` attribute of the item, but specifying `callback` allows you to
123 123 extract the value you are interested in. e.g::
124 124
125 125 In [1]: import sys
126 126
127 127 In [2]: from IPython.testing.iptest import test_for
128 128
129 129 In [3]: test_for('sys', (2,6), callback=lambda sys: sys.version_info)
130 130 Out[3]: True
131 131
132 132 """
133 133 try:
134 134 check = import_item(item)
135 135 except (ImportError, RuntimeError):
136 136 # GTK reports Runtime error if it can't be initialized even if it's
137 137 # importable.
138 138 return False
139 139 else:
140 140 if min_version:
141 141 if callback:
142 142 # extra processing step to get version to compare
143 143 check = callback(check)
144 144
145 145 return check >= min_version
146 146 else:
147 147 return True
148 148
149 149 # Global dict where we can store information on what we have and what we don't
150 150 # have available at test run time
151 151 have = {}
152 152
153 153 have['curses'] = test_for('_curses')
154 154 have['matplotlib'] = test_for('matplotlib')
155 155 have['numpy'] = test_for('numpy')
156 156 have['pexpect'] = test_for('IPython.external.pexpect')
157 157 have['pymongo'] = test_for('pymongo')
158 158 have['pygments'] = test_for('pygments')
159 159 have['qt'] = test_for('IPython.external.qt')
160 160 have['rpy2'] = test_for('rpy2')
161 161 have['sqlite3'] = test_for('sqlite3')
162 162 have['cython'] = test_for('Cython')
163 163 have['oct2py'] = test_for('oct2py')
164 164 have['tornado'] = test_for('tornado.version_info', (2,1,0), callback=None)
165 165 have['jinja2'] = test_for('jinja2')
166 166 have['wx'] = test_for('wx')
167 167 have['wx.aui'] = test_for('wx.aui')
168 168 have['azure'] = test_for('azure')
169 169
170 170 min_zmq = (2,1,11)
171 171
172 172 have['zmq'] = test_for('zmq.pyzmq_version_info', min_zmq, callback=lambda x: x())
173 173
174 174 #-----------------------------------------------------------------------------
175 175 # Functions and classes
176 176 #-----------------------------------------------------------------------------
177 177
178 178 def report():
179 179 """Return a string with a summary report of test-related variables."""
180 180
181 181 out = [ sys_info(), '\n']
182 182
183 183 avail = []
184 184 not_avail = []
185 185
186 186 for k, is_avail in have.items():
187 187 if is_avail:
188 188 avail.append(k)
189 189 else:
190 190 not_avail.append(k)
191 191
192 192 if avail:
193 193 out.append('\nTools and libraries available at test time:\n')
194 194 avail.sort()
195 195 out.append(' ' + ' '.join(avail)+'\n')
196 196
197 197 if not_avail:
198 198 out.append('\nTools and libraries NOT available at test time:\n')
199 199 not_avail.sort()
200 200 out.append(' ' + ' '.join(not_avail)+'\n')
201 201
202 202 return ''.join(out)
203 203
204 204
205 205 def make_exclude():
206 206 """Make patterns of modules and packages to exclude from testing.
207 207
208 208 For the IPythonDoctest plugin, we need to exclude certain patterns that
209 209 cause testing problems. We should strive to minimize the number of
210 210 skipped modules, since this means untested code.
211 211
212 212 These modules and packages will NOT get scanned by nose at all for tests.
213 213 """
214 214 # Simple utility to make IPython paths more readably, we need a lot of
215 215 # these below
216 216 ipjoin = lambda *paths: pjoin('IPython', *paths)
217 217
218 218 exclusions = [ipjoin('external'),
219 219 ipjoin('quarantine'),
220 220 ipjoin('deathrow'),
221 221 # This guy is probably attic material
222 222 ipjoin('testing', 'mkdoctests'),
223 223 # Testing inputhook will need a lot of thought, to figure out
224 224 # how to have tests that don't lock up with the gui event
225 225 # loops in the picture
226 226 ipjoin('lib', 'inputhook'),
227 227 # Config files aren't really importable stand-alone
228 228 ipjoin('config', 'profile'),
229 229 # The notebook 'static' directory contains JS, css and other
230 230 # files for web serving. Occasionally projects may put a .py
231 231 # file in there (MathJax ships a conf.py), so we might as
232 232 # well play it safe and skip the whole thing.
233 ipjoin('frontend', 'html', 'notebook', 'static'),
234 ipjoin('frontend', 'html', 'notebook', 'fabfile'),
233 ipjoin('html', 'static'),
234 ipjoin('html', 'fabfile'),
235 235 ]
236 236 if not have['sqlite3']:
237 237 exclusions.append(ipjoin('core', 'tests', 'test_history'))
238 238 exclusions.append(ipjoin('core', 'history'))
239 239 if not have['wx']:
240 240 exclusions.append(ipjoin('lib', 'inputhookwx'))
241 241
242 242 if 'IPython.kernel.inprocess' not in sys.argv:
243 243 exclusions.append(ipjoin('kernel', 'inprocess'))
244 244
245 245 # FIXME: temporarily disable autoreload tests, as they can produce
246 246 # spurious failures in subsequent tests (cythonmagic).
247 247 exclusions.append(ipjoin('extensions', 'autoreload'))
248 248 exclusions.append(ipjoin('extensions', 'tests', 'test_autoreload'))
249 249
250 250 # We do this unconditionally, so that the test suite doesn't import
251 251 # gtk, changing the default encoding and masking some unicode bugs.
252 252 exclusions.append(ipjoin('lib', 'inputhookgtk'))
253 253 exclusions.append(ipjoin('kernel', 'zmq', 'gui', 'gtkembed'))
254 254
255 255 # These have to be skipped on win32 because the use echo, rm, cd, etc.
256 256 # See ticket https://github.com/ipython/ipython/issues/87
257 257 if sys.platform == 'win32':
258 258 exclusions.append(ipjoin('testing', 'plugin', 'test_exampleip'))
259 259 exclusions.append(ipjoin('testing', 'plugin', 'dtexample'))
260 260
261 261 if not have['pexpect']:
262 262 exclusions.extend([ipjoin('lib', 'irunner'),
263 263 ipjoin('lib', 'tests', 'test_irunner'),
264 ipjoin('frontend', 'terminal', 'console'),
264 ipjoin('terminal', 'console'),
265 265 ])
266 266
267 267 if not have['zmq']:
268 268 exclusions.append(ipjoin('kernel'))
269 exclusions.append(ipjoin('frontend', 'qt'))
270 exclusions.append(ipjoin('frontend', 'html'))
271 exclusions.append(ipjoin('frontend', 'consoleapp.py'))
272 exclusions.append(ipjoin('frontend', 'terminal', 'console'))
269 exclusions.append(ipjoin('qt'))
270 exclusions.append(ipjoin('html'))
271 exclusions.append(ipjoin('consoleapp.py'))
272 exclusions.append(ipjoin('terminal', 'console'))
273 273 exclusions.append(ipjoin('parallel'))
274 274 elif not have['qt'] or not have['pygments']:
275 exclusions.append(ipjoin('frontend', 'qt'))
275 exclusions.append(ipjoin('qt'))
276 276
277 277 if not have['pymongo']:
278 278 exclusions.append(ipjoin('parallel', 'controller', 'mongodb'))
279 279 exclusions.append(ipjoin('parallel', 'tests', 'test_mongodb'))
280 280
281 281 if not have['matplotlib']:
282 282 exclusions.extend([ipjoin('core', 'pylabtools'),
283 283 ipjoin('core', 'tests', 'test_pylabtools'),
284 284 ipjoin('kernel', 'zmq', 'pylab'),
285 285 ])
286 286
287 287 if not have['cython']:
288 288 exclusions.extend([ipjoin('extensions', 'cythonmagic')])
289 289 exclusions.extend([ipjoin('extensions', 'tests', 'test_cythonmagic')])
290 290
291 291 if not have['oct2py']:
292 292 exclusions.extend([ipjoin('extensions', 'octavemagic')])
293 293 exclusions.extend([ipjoin('extensions', 'tests', 'test_octavemagic')])
294 294
295 295 if not have['tornado']:
296 exclusions.append(ipjoin('frontend', 'html'))
296 exclusions.append(ipjoin('html'))
297 297
298 298 if not have['jinja2']:
299 exclusions.append(ipjoin('frontend', 'html', 'notebook', 'notebookapp'))
299 exclusions.append(ipjoin('html', 'notebookapp'))
300 300
301 301 if not have['rpy2'] or not have['numpy']:
302 302 exclusions.append(ipjoin('extensions', 'rmagic'))
303 303 exclusions.append(ipjoin('extensions', 'tests', 'test_rmagic'))
304 304
305 305 if not have['azure']:
306 exclusions.append(ipjoin('frontend', 'html', 'notebook', 'services', 'notebooks', 'azurenbmanager'))
306 exclusions.append(ipjoin('html', 'services', 'notebooks', 'azurenbmanager'))
307 307
308 308 # This is needed for the reg-exp to match on win32 in the ipdoctest plugin.
309 309 if sys.platform == 'win32':
310 310 exclusions = [s.replace('\\','\\\\') for s in exclusions]
311 311
312 312 # check for any exclusions that don't seem to exist:
313 313 parent, _ = os.path.split(get_ipython_package_dir())
314 314 for exclusion in exclusions:
315 315 if exclusion.endswith(('deathrow', 'quarantine')):
316 316 # ignore deathrow/quarantine, which exist in dev, but not install
317 317 continue
318 318 fullpath = pjoin(parent, exclusion)
319 319 if not os.path.exists(fullpath) and not glob.glob(fullpath + '.*'):
320 320 warn("Excluding nonexistent file: %r" % exclusion)
321 321
322 322 return exclusions
323 323
324 324
325 325 class IPTester(object):
326 326 """Call that calls iptest or trial in a subprocess.
327 327 """
328 328 #: string, name of test runner that will be called
329 329 runner = None
330 330 #: list, parameters for test runner
331 331 params = None
332 332 #: list, arguments of system call to be made to call test runner
333 333 call_args = None
334 334 #: list, subprocesses we start (for cleanup)
335 335 processes = None
336 336 #: str, coverage xml output file
337 337 coverage_xml = None
338 338
339 339 def __init__(self, runner='iptest', params=None):
340 340 """Create new test runner."""
341 341 p = os.path
342 342 if runner == 'iptest':
343 343 iptest_app = get_ipython_module_path('IPython.testing.iptest')
344 344 self.runner = pycmd2argv(iptest_app) + sys.argv[1:]
345 345 else:
346 346 raise Exception('Not a valid test runner: %s' % repr(runner))
347 347 if params is None:
348 348 params = []
349 349 if isinstance(params, str):
350 350 params = [params]
351 351 self.params = params
352 352
353 353 # Assemble call
354 354 self.call_args = self.runner+self.params
355 355
356 356 # Find the section we're testing (IPython.foo)
357 357 for sect in self.params:
358 358 if sect.startswith('IPython'): break
359 359 else:
360 360 raise ValueError("Section not found", self.params)
361 361
362 362 if '--with-xunit' in self.call_args:
363 363
364 364 self.call_args.append('--xunit-file')
365 365 # FIXME: when Windows uses subprocess.call, these extra quotes are unnecessary:
366 366 xunit_file = path.abspath(sect+'.xunit.xml')
367 367 if sys.platform == 'win32':
368 368 xunit_file = '"%s"' % xunit_file
369 369 self.call_args.append(xunit_file)
370 370
371 371 if '--with-xml-coverage' in self.call_args:
372 372 self.coverage_xml = path.abspath(sect+".coverage.xml")
373 373 self.call_args.remove('--with-xml-coverage')
374 374 self.call_args = ["coverage", "run", "--source="+sect] + self.call_args[1:]
375 375
376 376 # Store anything we start to clean up on deletion
377 377 self.processes = []
378 378
379 379 def _run_cmd(self):
380 380 with TemporaryDirectory() as IPYTHONDIR:
381 381 env = os.environ.copy()
382 382 env['IPYTHONDIR'] = IPYTHONDIR
383 383 # print >> sys.stderr, '*** CMD:', ' '.join(self.call_args) # dbg
384 384 subp = subprocess.Popen(self.call_args, env=env)
385 385 self.processes.append(subp)
386 386 # If this fails, the process will be left in self.processes and
387 387 # cleaned up later, but if the wait call succeeds, then we can
388 388 # clear the stored process.
389 389 retcode = subp.wait()
390 390 self.processes.pop()
391 391 return retcode
392 392
393 393 def run(self):
394 394 """Run the stored commands"""
395 395 try:
396 396 retcode = self._run_cmd()
397 397 except KeyboardInterrupt:
398 398 return -signal.SIGINT
399 399 except:
400 400 import traceback
401 401 traceback.print_exc()
402 402 return 1 # signal failure
403 403
404 404 if self.coverage_xml:
405 405 subprocess.call(["coverage", "xml", "-o", self.coverage_xml])
406 406 return retcode
407 407
408 408 def __del__(self):
409 409 """Cleanup on exit by killing any leftover processes."""
410 410 for subp in self.processes:
411 411 if subp.poll() is not None:
412 412 continue # process is already dead
413 413
414 414 try:
415 415 print('Cleaning up stale PID: %d' % subp.pid)
416 416 subp.kill()
417 417 except: # (OSError, WindowsError) ?
418 418 # This is just a best effort, if we fail or the process was
419 419 # really gone, ignore it.
420 420 pass
421 421 else:
422 422 for i in range(10):
423 423 if subp.poll() is None:
424 424 time.sleep(0.1)
425 425 else:
426 426 break
427 427
428 428 if subp.poll() is None:
429 429 # The process did not die...
430 430 print('... failed. Manual cleanup may be required.')
431 431
432
432 433 def make_runners(inc_slow=False):
433 434 """Define the top-level packages that need to be tested.
434 435 """
435 436
436 437 # Packages to be tested via nose, that only depend on the stdlib
437 nose_pkg_names = ['config', 'core', 'extensions', 'frontend', 'lib',
438 nose_pkg_names = ['config', 'core', 'extensions', 'lib', 'terminal',
438 439 'testing', 'utils', 'nbformat' ]
439 440
441 if have['qt']:
442 nose_pkg_names.append('qt')
443
444 if have['tornado']:
445 nose_pkg_names.append('html')
446
440 447 if have['zmq']:
441 448 nose_pkg_names.append('kernel')
442 449 nose_pkg_names.append('kernel.inprocess')
443 450 if inc_slow:
444 451 nose_pkg_names.append('parallel')
445 452
446 453 # For debugging this code, only load quick stuff
447 454 #nose_pkg_names = ['core', 'extensions'] # dbg
448 455
449 456 # Make fully qualified package names prepending 'IPython.' to our name lists
450 457 nose_packages = ['IPython.%s' % m for m in nose_pkg_names ]
451 458
452 459 # Make runners
453 460 runners = [ (v, IPTester('iptest', params=v)) for v in nose_packages ]
454 461
455 462 return runners
456 463
457 464
458 465 def run_iptest():
459 466 """Run the IPython test suite using nose.
460 467
461 468 This function is called when this script is **not** called with the form
462 469 `iptest all`. It simply calls nose with appropriate command line flags
463 470 and accepts all of the standard nose arguments.
464 471 """
465 472 # Apply our monkeypatch to Xunit
466 473 if '--with-xunit' in sys.argv and not hasattr(Xunit, 'orig_addError'):
467 474 monkeypatch_xunit()
468 475
469 476 warnings.filterwarnings('ignore',
470 477 'This will be removed soon. Use IPython.testing.util instead')
471 478
472 479 argv = sys.argv + [ '--detailed-errors', # extra info in tracebacks
473 480
474 481 '--with-ipdoctest',
475 482 '--ipdoctest-tests','--ipdoctest-extension=txt',
476 483
477 484 # We add --exe because of setuptools' imbecility (it
478 485 # blindly does chmod +x on ALL files). Nose does the
479 486 # right thing and it tries to avoid executables,
480 487 # setuptools unfortunately forces our hand here. This
481 488 # has been discussed on the distutils list and the
482 489 # setuptools devs refuse to fix this problem!
483 490 '--exe',
484 491 ]
485 492 if '-a' not in argv and '-A' not in argv:
486 493 argv = argv + ['-a', '!crash']
487 494
488 495 if nose.__version__ >= '0.11':
489 496 # I don't fully understand why we need this one, but depending on what
490 497 # directory the test suite is run from, if we don't give it, 0 tests
491 498 # get run. Specifically, if the test suite is run from the source dir
492 499 # with an argument (like 'iptest.py IPython.core', 0 tests are run,
493 500 # even if the same call done in this directory works fine). It appears
494 501 # that if the requested package is in the current dir, nose bails early
495 502 # by default. Since it's otherwise harmless, leave it in by default
496 503 # for nose >= 0.11, though unfortunately nose 0.10 doesn't support it.
497 504 argv.append('--traverse-namespace')
498 505
499 506 # use our plugin for doctesting. It will remove the standard doctest plugin
500 507 # if it finds it enabled
501 508 plugins = [IPythonDoctest(make_exclude()), KnownFailure()]
502 509
503 510 # We need a global ipython running in this process, but the special
504 511 # in-process group spawns its own IPython kernels, so for *that* group we
505 512 # must avoid also opening the global one (otherwise there's a conflict of
506 513 # singletons). Ultimately the solution to this problem is to refactor our
507 514 # assumptions about what needs to be a singleton and what doesn't (app
508 515 # objects should, individual shells shouldn't). But for now, this
509 516 # workaround allows the test suite for the inprocess module to complete.
510 517 if not 'IPython.kernel.inprocess' in sys.argv:
511 518 globalipapp.start_ipython()
512 519
513 520 # Now nose can run
514 521 TestProgram(argv=argv, addplugins=plugins)
515 522
516 523
517 524 def run_iptestall(inc_slow=False):
518 525 """Run the entire IPython test suite by calling nose and trial.
519 526
520 527 This function constructs :class:`IPTester` instances for all IPython
521 528 modules and package and then runs each of them. This causes the modules
522 529 and packages of IPython to be tested each in their own subprocess using
523 530 nose.
524 531
525 532 Parameters
526 533 ----------
527 534
528 535 inc_slow : bool, optional
529 536 Include slow tests, like IPython.parallel. By default, these tests aren't
530 537 run.
531 538 """
532 539
533 540 runners = make_runners(inc_slow=inc_slow)
534 541
535 542 # Run the test runners in a temporary dir so we can nuke it when finished
536 543 # to clean up any junk files left over by accident. This also makes it
537 544 # robust against being run in non-writeable directories by mistake, as the
538 545 # temp dir will always be user-writeable.
539 546 curdir = os.getcwdu()
540 547 testdir = tempfile.gettempdir()
541 548 os.chdir(testdir)
542 549
543 550 # Run all test runners, tracking execution time
544 551 failed = []
545 552 t_start = time.time()
546 553 try:
547 554 for (name, runner) in runners:
548 555 print('*'*70)
549 556 print('IPython test group:',name)
550 557 res = runner.run()
551 558 if res:
552 559 failed.append( (name, runner) )
553 560 if res == -signal.SIGINT:
554 561 print("Interrupted")
555 562 break
556 563 finally:
557 564 os.chdir(curdir)
558 565 t_end = time.time()
559 566 t_tests = t_end - t_start
560 567 nrunners = len(runners)
561 568 nfail = len(failed)
562 569 # summarize results
563 570 print()
564 571 print('*'*70)
565 572 print('Test suite completed for system with the following information:')
566 573 print(report())
567 574 print('Ran %s test groups in %.3fs' % (nrunners, t_tests))
568 575 print()
569 576 print('Status:')
570 577 if not failed:
571 578 print('OK')
572 579 else:
573 580 # If anything went wrong, point out what command to rerun manually to
574 581 # see the actual errors and individual summary
575 582 print('ERROR - %s out of %s test groups failed.' % (nfail, nrunners))
576 583 for name, failed_runner in failed:
577 584 print('-'*40)
578 585 print('Runner failed:',name)
579 586 print('You may wish to rerun this one individually, with:')
580 587 failed_call_args = [py3compat.cast_unicode(x) for x in failed_runner.call_args]
581 588 print(u' '.join(failed_call_args))
582 589 print()
583 590 # Ensure that our exit code indicates failure
584 591 sys.exit(1)
585 592
586 593
587 594 def main():
588 595 for arg in sys.argv[1:]:
589 596 if arg.startswith('IPython'):
590 597 # This is in-process
591 598 run_iptest()
592 599 else:
593 600 if "--all" in sys.argv:
594 601 sys.argv.remove("--all")
595 602 inc_slow = True
596 603 else:
597 604 inc_slow = False
598 605 # This starts subprocesses
599 606 run_iptestall(inc_slow=inc_slow)
600 607
601 608
602 609 if __name__ == '__main__':
603 610 main()
@@ -1,47 +1,50 b''
1 1 # encoding: utf-8
2 2 """
3 3 A simple utility to import something by its string name.
4 4
5 5 Authors:
6 6
7 7 * Brian Granger
8 8 """
9 9
10 10 #-----------------------------------------------------------------------------
11 11 # Copyright (C) 2008-2011 The IPython Development Team
12 12 #
13 13 # Distributed under the terms of the BSD License. The full license is in
14 14 # the file COPYING, distributed as part of this software.
15 15 #-----------------------------------------------------------------------------
16 16
17 17 #-----------------------------------------------------------------------------
18 18 # Functions and classes
19 19 #-----------------------------------------------------------------------------
20 20
21 21 def import_item(name):
22 """Import and return bar given the string foo.bar."""
23 package = '.'.join(name.split('.')[0:-1])
24 obj = name.split('.')[-1]
25
26 # Note: the original code for this was the following. We've left it
27 # visible for now in case the new implementation shows any problems down
28 # the road, to make it easier on anyone looking for a problem. This code
29 # should be removed once we're comfortable we didn't break anything.
22 """Import and return ``bar`` given the string ``foo.bar``.
23
24 Calling ``bar = import_item("foo.bar")`` is the functional equivalent of
25 executing the code ``from foo import bar``.
26
27 Parameters
28 ----------
29 name : string
30 The fully qualified name of the module/package being imported.
31
32 Returns
33 -------
34 mod : module object
35 The module that was imported.
36 """
30 37
31 ## execString = 'from %s import %s' % (package, obj)
32 ## try:
33 ## exec execString
34 ## except SyntaxError:
35 ## raise ImportError("Invalid class specification: %s" % name)
36 ## exec 'temp = %s' % obj
37 ## return temp
38
39 if package:
40 module = __import__(package,fromlist=[obj])
38 parts = name.rsplit('.', 1)
39 if len(parts) == 2:
40 # called with 'foo.bar....'
41 package, obj = parts
42 module = __import__(package, fromlist=[obj])
41 43 try:
42 44 pak = module.__dict__[obj]
43 45 except KeyError:
44 46 raise ImportError('No module named %s' % obj)
45 47 return pak
46 48 else:
47 return __import__(obj)
49 # called with un-dotted string
50 return __import__(parts[0])
@@ -1,122 +1,122 b''
1 1 # encoding: utf-8
2 2 """
3 3 Utilities for working with external processes.
4 4 """
5 5
6 6 #-----------------------------------------------------------------------------
7 7 # Copyright (C) 2008-2011 The IPython Development Team
8 8 #
9 9 # Distributed under the terms of the BSD License. The full license is in
10 10 # the file COPYING, distributed as part of this software.
11 11 #-----------------------------------------------------------------------------
12 12
13 13 #-----------------------------------------------------------------------------
14 14 # Imports
15 15 #-----------------------------------------------------------------------------
16 16 from __future__ import print_function
17 17
18 18 # Stdlib
19 19 import os
20 20 import sys
21 21 import shlex
22 22
23 23 # Our own
24 24 if sys.platform == 'win32':
25 25 from ._process_win32 import _find_cmd, system, getoutput, AvoidUNCPath, arg_split
26 26 else:
27 27 from ._process_posix import _find_cmd, system, getoutput, arg_split
28 28
29 29
30 30 from ._process_common import getoutputerror
31 31
32 32 #-----------------------------------------------------------------------------
33 33 # Code
34 34 #-----------------------------------------------------------------------------
35 35
36 36
37 37 class FindCmdError(Exception):
38 38 pass
39 39
40 40
41 41 def find_cmd(cmd):
42 42 """Find absolute path to executable cmd in a cross platform manner.
43 43
44 44 This function tries to determine the full path to a command line program
45 45 using `which` on Unix/Linux/OS X and `win32api` on Windows. Most of the
46 46 time it will use the version that is first on the users `PATH`.
47 47
48 48 Warning, don't use this to find IPython command line programs as there
49 49 is a risk you will find the wrong one. Instead find those using the
50 50 following code and looking for the application itself::
51 51
52 52 from IPython.utils.path import get_ipython_module_path
53 53 from IPython.utils.process import pycmd2argv
54 argv = pycmd2argv(get_ipython_module_path('IPython.frontend.terminal.ipapp'))
54 argv = pycmd2argv(get_ipython_module_path('IPython.terminal.ipapp'))
55 55
56 56 Parameters
57 57 ----------
58 58 cmd : str
59 59 The command line program to look for.
60 60 """
61 61 try:
62 62 path = _find_cmd(cmd).rstrip()
63 63 except OSError:
64 64 raise FindCmdError('command could not be found: %s' % cmd)
65 65 # which returns empty if not found
66 66 if path == '':
67 67 raise FindCmdError('command could not be found: %s' % cmd)
68 68 return os.path.abspath(path)
69 69
70 70
71 71 def is_cmd_found(cmd):
72 72 """Check whether executable `cmd` exists or not and return a bool."""
73 73 try:
74 74 find_cmd(cmd)
75 75 return True
76 76 except FindCmdError:
77 77 return False
78 78
79 79
80 80 def pycmd2argv(cmd):
81 81 r"""Take the path of a python command and return a list (argv-style).
82 82
83 83 This only works on Python based command line programs and will find the
84 84 location of the ``python`` executable using ``sys.executable`` to make
85 85 sure the right version is used.
86 86
87 87 For a given path ``cmd``, this returns [cmd] if cmd's extension is .exe,
88 88 .com or .bat, and [, cmd] otherwise.
89 89
90 90 Parameters
91 91 ----------
92 92 cmd : string
93 93 The path of the command.
94 94
95 95 Returns
96 96 -------
97 97 argv-style list.
98 98 """
99 99 ext = os.path.splitext(cmd)[1]
100 100 if ext in ['.exe', '.com', '.bat']:
101 101 return [cmd]
102 102 else:
103 103 return [sys.executable, cmd]
104 104
105 105
106 106 def abbrev_cwd():
107 107 """ Return abbreviated version of cwd, e.g. d:mydir """
108 108 cwd = os.getcwdu().replace('\\','/')
109 109 drivepart = ''
110 110 tail = cwd
111 111 if sys.platform == 'win32':
112 112 if len(cwd) < 4:
113 113 return cwd
114 114 drivepart,tail = os.path.splitdrive(cwd)
115 115
116 116
117 117 parts = tail.split('/')
118 118 if len(parts) > 2:
119 119 tail = '/'.join(parts[-2:])
120 120
121 121 return (drivepart + (
122 122 cwd == '/' and '/' or tail))
@@ -1,92 +1,92 b''
1 1 """utilities for checking submodule status"""
2 2
3 3 #-----------------------------------------------------------------------------
4 4 # Copyright (C) 2013 The IPython Development Team
5 5 #
6 6 # Distributed under the terms of the BSD License. The full license is in
7 7 # the file COPYING, distributed as part of this software.
8 8 #-----------------------------------------------------------------------------
9 9
10 10 #-----------------------------------------------------------------------------
11 11 # Imports
12 12 #-----------------------------------------------------------------------------
13 13
14 14 import os
15 15 import subprocess
16 16 import sys
17 17
18 18 #-----------------------------------------------------------------------------
19 19 # Globals
20 20 #-----------------------------------------------------------------------------
21 21
22 22 pjoin = os.path.join
23 23
24 24 #-----------------------------------------------------------------------------
25 25 # Code
26 26 #-----------------------------------------------------------------------------
27 27
28 28 def ipython_parent():
29 29 """return IPython's parent (i.e. root if run from git)"""
30 30 from IPython.utils.path import get_ipython_package_dir
31 31 return os.path.abspath(os.path.dirname(get_ipython_package_dir()))
32 32
33 33 def ipython_submodules(root):
34 34 """return IPython submodules relative to root"""
35 35 return [
36 pjoin(root, 'IPython', 'frontend', 'html', 'notebook', 'static', 'components'),
36 pjoin(root, 'IPython', 'html', 'static', 'components'),
37 37 ]
38 38
39 39 def is_repo(d):
40 40 """is d a git repo?"""
41 41 return os.path.exists(pjoin(d, '.git'))
42 42
43 43 def check_submodule_status(root=None):
44 44 """check submodule status
45 45
46 46 Has three return values:
47 47
48 48 'missing' - submodules are absent
49 49 'unclean' - submodules have unstaged changes
50 50 'clean' - all submodules are up to date
51 51 """
52 52
53 53 if hasattr(sys, "frozen"):
54 54 # frozen via py2exe or similar, don't bother
55 55 return 'clean'
56 56
57 57 if not root:
58 58 root = ipython_parent()
59 59
60 60 if not is_repo(root):
61 61 # not in git, assume clean
62 62 return 'clean'
63 63
64 64 submodules = ipython_submodules(root)
65 65
66 66 for submodule in submodules:
67 67 if not os.path.exists(submodule):
68 68 return 'missing'
69 69
70 70 # check with git submodule status
71 71 proc = subprocess.Popen('git submodule status',
72 72 stdout=subprocess.PIPE,
73 73 stderr=subprocess.PIPE,
74 74 shell=True,
75 75 cwd=root,
76 76 )
77 77 status, _ = proc.communicate()
78 78 status = status.decode("ascii")
79 79
80 80 for line in status.splitlines():
81 81 if status.startswith('-'):
82 82 return 'missing'
83 83 elif status.startswith('+'):
84 84 return 'unclean'
85 85
86 86 return 'clean'
87 87
88 88 def update_submodules(repo_dir):
89 89 """update submodules in a repo"""
90 90 subprocess.check_call("git submodule init", cwd=repo_dir, shell=True)
91 91 subprocess.check_call("git submodule update --recursive", cwd=repo_dir, shell=True)
92 92
@@ -1,560 +1,560 b''
1 1 # encoding: utf-8
2 2 """Tests for IPython.utils.path.py"""
3 3
4 4 #-----------------------------------------------------------------------------
5 5 # Copyright (C) 2008-2011 The IPython Development Team
6 6 #
7 7 # Distributed under the terms of the BSD License. The full license is in
8 8 # the file COPYING, distributed as part of this software.
9 9 #-----------------------------------------------------------------------------
10 10
11 11 #-----------------------------------------------------------------------------
12 12 # Imports
13 13 #-----------------------------------------------------------------------------
14 14
15 15 from __future__ import with_statement
16 16
17 17 import os
18 18 import shutil
19 19 import sys
20 20 import tempfile
21 21 from io import StringIO
22 22 from contextlib import contextmanager
23 23
24 24 from os.path import join, abspath, split
25 25
26 26 import nose.tools as nt
27 27
28 28 from nose import with_setup
29 29
30 30 import IPython
31 31 from IPython.testing import decorators as dec
32 32 from IPython.testing.decorators import skip_if_not_win32, skip_win32
33 33 from IPython.testing.tools import make_tempfile, AssertPrints
34 34 from IPython.utils import path, io
35 35 from IPython.utils import py3compat
36 36 from IPython.utils.tempdir import TemporaryDirectory
37 37
38 38 # Platform-dependent imports
39 39 try:
40 40 import _winreg as wreg
41 41 except ImportError:
42 42 #Fake _winreg module on none windows platforms
43 43 import types
44 44 wr_name = "winreg" if py3compat.PY3 else "_winreg"
45 45 sys.modules[wr_name] = types.ModuleType(wr_name)
46 46 import _winreg as wreg
47 47 #Add entries that needs to be stubbed by the testing code
48 48 (wreg.OpenKey, wreg.QueryValueEx,) = (None, None)
49 49
50 50 try:
51 51 reload
52 52 except NameError: # Python 3
53 53 from imp import reload
54 54
55 55 #-----------------------------------------------------------------------------
56 56 # Globals
57 57 #-----------------------------------------------------------------------------
58 58 env = os.environ
59 59 TEST_FILE_PATH = split(abspath(__file__))[0]
60 60 TMP_TEST_DIR = tempfile.mkdtemp()
61 61 HOME_TEST_DIR = join(TMP_TEST_DIR, "home_test_dir")
62 62 XDG_TEST_DIR = join(HOME_TEST_DIR, "xdg_test_dir")
63 63 XDG_CACHE_DIR = join(HOME_TEST_DIR, "xdg_cache_dir")
64 64 IP_TEST_DIR = join(HOME_TEST_DIR,'.ipython')
65 65 #
66 66 # Setup/teardown functions/decorators
67 67 #
68 68
69 69 def setup():
70 70 """Setup testenvironment for the module:
71 71
72 72 - Adds dummy home dir tree
73 73 """
74 74 # Do not mask exceptions here. In particular, catching WindowsError is a
75 75 # problem because that exception is only defined on Windows...
76 76 os.makedirs(IP_TEST_DIR)
77 77 os.makedirs(os.path.join(XDG_TEST_DIR, 'ipython'))
78 78 os.makedirs(os.path.join(XDG_CACHE_DIR, 'ipython'))
79 79
80 80
81 81 def teardown():
82 82 """Teardown testenvironment for the module:
83 83
84 84 - Remove dummy home dir tree
85 85 """
86 86 # Note: we remove the parent test dir, which is the root of all test
87 87 # subdirs we may have created. Use shutil instead of os.removedirs, so
88 88 # that non-empty directories are all recursively removed.
89 89 shutil.rmtree(TMP_TEST_DIR)
90 90
91 91
92 92 def setup_environment():
93 93 """Setup testenvironment for some functions that are tested
94 94 in this module. In particular this functions stores attributes
95 95 and other things that we need to stub in some test functions.
96 96 This needs to be done on a function level and not module level because
97 97 each testfunction needs a pristine environment.
98 98 """
99 99 global oldstuff, platformstuff
100 100 oldstuff = (env.copy(), os.name, sys.platform, path.get_home_dir, IPython.__file__, os.getcwd())
101 101
102 102 if os.name == 'nt':
103 103 platformstuff = (wreg.OpenKey, wreg.QueryValueEx,)
104 104
105 105
106 106 def teardown_environment():
107 107 """Restore things that were remebered by the setup_environment function
108 108 """
109 109 (oldenv, os.name, sys.platform, path.get_home_dir, IPython.__file__, old_wd) = oldstuff
110 110 os.chdir(old_wd)
111 111 reload(path)
112 112
113 113 for key in env.keys():
114 114 if key not in oldenv:
115 115 del env[key]
116 116 env.update(oldenv)
117 117 if hasattr(sys, 'frozen'):
118 118 del sys.frozen
119 119 if os.name == 'nt':
120 120 (wreg.OpenKey, wreg.QueryValueEx,) = platformstuff
121 121
122 122 # Build decorator that uses the setup_environment/setup_environment
123 123 with_environment = with_setup(setup_environment, teardown_environment)
124 124
125 125 @skip_if_not_win32
126 126 @with_environment
127 127 def test_get_home_dir_1():
128 128 """Testcase for py2exe logic, un-compressed lib
129 129 """
130 130 sys.frozen = True
131 131
132 132 #fake filename for IPython.__init__
133 133 IPython.__file__ = abspath(join(HOME_TEST_DIR, "Lib/IPython/__init__.py"))
134 134
135 135 home_dir = path.get_home_dir()
136 136 nt.assert_equal(home_dir, abspath(HOME_TEST_DIR))
137 137
138 138
139 139 @skip_if_not_win32
140 140 @with_environment
141 141 def test_get_home_dir_2():
142 142 """Testcase for py2exe logic, compressed lib
143 143 """
144 144 sys.frozen = True
145 145 #fake filename for IPython.__init__
146 146 IPython.__file__ = abspath(join(HOME_TEST_DIR, "Library.zip/IPython/__init__.py")).lower()
147 147
148 148 home_dir = path.get_home_dir(True)
149 149 nt.assert_equal(home_dir, abspath(HOME_TEST_DIR).lower())
150 150
151 151
152 152 @with_environment
153 153 def test_get_home_dir_3():
154 154 """get_home_dir() uses $HOME if set"""
155 155 env["HOME"] = HOME_TEST_DIR
156 156 home_dir = path.get_home_dir(True)
157 157 # get_home_dir expands symlinks
158 158 nt.assert_equal(home_dir, os.path.realpath(env["HOME"]))
159 159
160 160
161 161 @with_environment
162 162 def test_get_home_dir_4():
163 163 """get_home_dir() still works if $HOME is not set"""
164 164
165 165 if 'HOME' in env: del env['HOME']
166 166 # this should still succeed, but we don't care what the answer is
167 167 home = path.get_home_dir(False)
168 168
169 169 @with_environment
170 170 def test_get_home_dir_5():
171 171 """raise HomeDirError if $HOME is specified, but not a writable dir"""
172 172 env['HOME'] = abspath(HOME_TEST_DIR+'garbage')
173 173 # set os.name = posix, to prevent My Documents fallback on Windows
174 174 os.name = 'posix'
175 175 nt.assert_raises(path.HomeDirError, path.get_home_dir, True)
176 176
177 177
178 178 # Should we stub wreg fully so we can run the test on all platforms?
179 179 @skip_if_not_win32
180 180 @with_environment
181 181 def test_get_home_dir_8():
182 182 """Using registry hack for 'My Documents', os=='nt'
183 183
184 184 HOMESHARE, HOMEDRIVE, HOMEPATH, USERPROFILE and others are missing.
185 185 """
186 186 os.name = 'nt'
187 187 # Remove from stub environment all keys that may be set
188 188 for key in ['HOME', 'HOMESHARE', 'HOMEDRIVE', 'HOMEPATH', 'USERPROFILE']:
189 189 env.pop(key, None)
190 190
191 191 #Stub windows registry functions
192 192 def OpenKey(x, y):
193 193 class key:
194 194 def Close(self):
195 195 pass
196 196 return key()
197 197 def QueryValueEx(x, y):
198 198 return [abspath(HOME_TEST_DIR)]
199 199
200 200 wreg.OpenKey = OpenKey
201 201 wreg.QueryValueEx = QueryValueEx
202 202
203 203 home_dir = path.get_home_dir()
204 204 nt.assert_equal(home_dir, abspath(HOME_TEST_DIR))
205 205
206 206
207 207 @with_environment
208 208 def test_get_ipython_dir_1():
209 209 """test_get_ipython_dir_1, Testcase to see if we can call get_ipython_dir without Exceptions."""
210 210 env_ipdir = os.path.join("someplace", ".ipython")
211 211 path._writable_dir = lambda path: True
212 212 env['IPYTHONDIR'] = env_ipdir
213 213 ipdir = path.get_ipython_dir()
214 214 nt.assert_equal(ipdir, env_ipdir)
215 215
216 216
217 217 @with_environment
218 218 def test_get_ipython_dir_2():
219 219 """test_get_ipython_dir_2, Testcase to see if we can call get_ipython_dir without Exceptions."""
220 220 path.get_home_dir = lambda : "someplace"
221 221 path.get_xdg_dir = lambda : None
222 222 path._writable_dir = lambda path: True
223 223 os.name = "posix"
224 224 env.pop('IPYTHON_DIR', None)
225 225 env.pop('IPYTHONDIR', None)
226 226 env.pop('XDG_CONFIG_HOME', None)
227 227 ipdir = path.get_ipython_dir()
228 228 nt.assert_equal(ipdir, os.path.join("someplace", ".ipython"))
229 229
230 230 @with_environment
231 231 def test_get_ipython_dir_3():
232 232 """test_get_ipython_dir_3, use XDG if defined, and .ipython doesn't exist."""
233 233 path.get_home_dir = lambda : "someplace"
234 234 path._writable_dir = lambda path: True
235 235 os.name = "posix"
236 236 env.pop('IPYTHON_DIR', None)
237 237 env.pop('IPYTHONDIR', None)
238 238 env['XDG_CONFIG_HOME'] = XDG_TEST_DIR
239 239 ipdir = path.get_ipython_dir()
240 240 if sys.platform == "darwin":
241 241 expected = os.path.join("someplace", ".ipython")
242 242 else:
243 243 expected = os.path.join(XDG_TEST_DIR, "ipython")
244 244 nt.assert_equal(ipdir, expected)
245 245
246 246 @with_environment
247 247 def test_get_ipython_dir_4():
248 248 """test_get_ipython_dir_4, use XDG if both exist."""
249 249 path.get_home_dir = lambda : HOME_TEST_DIR
250 250 os.name = "posix"
251 251 env.pop('IPYTHON_DIR', None)
252 252 env.pop('IPYTHONDIR', None)
253 253 env['XDG_CONFIG_HOME'] = XDG_TEST_DIR
254 254 ipdir = path.get_ipython_dir()
255 255 if sys.platform == "darwin":
256 256 expected = os.path.join(HOME_TEST_DIR, ".ipython")
257 257 else:
258 258 expected = os.path.join(XDG_TEST_DIR, "ipython")
259 259 nt.assert_equal(ipdir, expected)
260 260
261 261 @with_environment
262 262 def test_get_ipython_dir_5():
263 263 """test_get_ipython_dir_5, use .ipython if exists and XDG defined, but doesn't exist."""
264 264 path.get_home_dir = lambda : HOME_TEST_DIR
265 265 os.name = "posix"
266 266 env.pop('IPYTHON_DIR', None)
267 267 env.pop('IPYTHONDIR', None)
268 268 env['XDG_CONFIG_HOME'] = XDG_TEST_DIR
269 269 os.rmdir(os.path.join(XDG_TEST_DIR, 'ipython'))
270 270 ipdir = path.get_ipython_dir()
271 271 nt.assert_equal(ipdir, IP_TEST_DIR)
272 272
273 273 @with_environment
274 274 def test_get_ipython_dir_6():
275 275 """test_get_ipython_dir_6, use XDG if defined and neither exist."""
276 276 xdg = os.path.join(HOME_TEST_DIR, 'somexdg')
277 277 os.mkdir(xdg)
278 278 shutil.rmtree(os.path.join(HOME_TEST_DIR, '.ipython'))
279 279 path.get_home_dir = lambda : HOME_TEST_DIR
280 280 path.get_xdg_dir = lambda : xdg
281 281 os.name = "posix"
282 282 env.pop('IPYTHON_DIR', None)
283 283 env.pop('IPYTHONDIR', None)
284 284 env.pop('XDG_CONFIG_HOME', None)
285 285 xdg_ipdir = os.path.join(xdg, "ipython")
286 286 ipdir = path.get_ipython_dir()
287 287 nt.assert_equal(ipdir, xdg_ipdir)
288 288
289 289 @with_environment
290 290 def test_get_ipython_dir_7():
291 291 """test_get_ipython_dir_7, test home directory expansion on IPYTHONDIR"""
292 292 path._writable_dir = lambda path: True
293 293 home_dir = os.path.normpath(os.path.expanduser('~'))
294 294 env['IPYTHONDIR'] = os.path.join('~', 'somewhere')
295 295 ipdir = path.get_ipython_dir()
296 296 nt.assert_equal(ipdir, os.path.join(home_dir, 'somewhere'))
297 297
298 298 @skip_win32
299 299 @with_environment
300 300 def test_get_ipython_dir_8():
301 301 """test_get_ipython_dir_8, test / home directory"""
302 302 old = path._writable_dir, path.get_xdg_dir
303 303 try:
304 304 path._writable_dir = lambda path: bool(path)
305 305 path.get_xdg_dir = lambda: None
306 306 env.pop('IPYTHON_DIR', None)
307 307 env.pop('IPYTHONDIR', None)
308 308 env['HOME'] = '/'
309 309 nt.assert_equal(path.get_ipython_dir(), '/.ipython')
310 310 finally:
311 311 path._writable_dir, path.get_xdg_dir = old
312 312
313 313 @with_environment
314 314 def test_get_xdg_dir_0():
315 315 """test_get_xdg_dir_0, check xdg_dir"""
316 316 reload(path)
317 317 path._writable_dir = lambda path: True
318 318 path.get_home_dir = lambda : 'somewhere'
319 319 os.name = "posix"
320 320 sys.platform = "linux2"
321 321 env.pop('IPYTHON_DIR', None)
322 322 env.pop('IPYTHONDIR', None)
323 323 env.pop('XDG_CONFIG_HOME', None)
324 324
325 325 nt.assert_equal(path.get_xdg_dir(), os.path.join('somewhere', '.config'))
326 326
327 327
328 328 @with_environment
329 329 def test_get_xdg_dir_1():
330 330 """test_get_xdg_dir_1, check nonexistant xdg_dir"""
331 331 reload(path)
332 332 path.get_home_dir = lambda : HOME_TEST_DIR
333 333 os.name = "posix"
334 334 sys.platform = "linux2"
335 335 env.pop('IPYTHON_DIR', None)
336 336 env.pop('IPYTHONDIR', None)
337 337 env.pop('XDG_CONFIG_HOME', None)
338 338 nt.assert_equal(path.get_xdg_dir(), None)
339 339
340 340 @with_environment
341 341 def test_get_xdg_dir_2():
342 342 """test_get_xdg_dir_2, check xdg_dir default to ~/.config"""
343 343 reload(path)
344 344 path.get_home_dir = lambda : HOME_TEST_DIR
345 345 os.name = "posix"
346 346 sys.platform = "linux2"
347 347 env.pop('IPYTHON_DIR', None)
348 348 env.pop('IPYTHONDIR', None)
349 349 env.pop('XDG_CONFIG_HOME', None)
350 350 cfgdir=os.path.join(path.get_home_dir(), '.config')
351 351 if not os.path.exists(cfgdir):
352 352 os.makedirs(cfgdir)
353 353
354 354 nt.assert_equal(path.get_xdg_dir(), cfgdir)
355 355
356 356 @with_environment
357 357 def test_get_xdg_dir_3():
358 358 """test_get_xdg_dir_3, check xdg_dir not used on OS X"""
359 359 reload(path)
360 360 path.get_home_dir = lambda : HOME_TEST_DIR
361 361 os.name = "posix"
362 362 sys.platform = "darwin"
363 363 env.pop('IPYTHON_DIR', None)
364 364 env.pop('IPYTHONDIR', None)
365 365 env.pop('XDG_CONFIG_HOME', None)
366 366 cfgdir=os.path.join(path.get_home_dir(), '.config')
367 367 if not os.path.exists(cfgdir):
368 368 os.makedirs(cfgdir)
369 369
370 370 nt.assert_equal(path.get_xdg_dir(), None)
371 371
372 372 def test_filefind():
373 373 """Various tests for filefind"""
374 374 f = tempfile.NamedTemporaryFile()
375 375 # print 'fname:',f.name
376 376 alt_dirs = path.get_ipython_dir()
377 377 t = path.filefind(f.name, alt_dirs)
378 378 # print 'found:',t
379 379
380 380 @with_environment
381 381 def test_get_ipython_cache_dir():
382 382 os.environ["HOME"] = HOME_TEST_DIR
383 383 if os.name == 'posix' and sys.platform != 'darwin':
384 384 # test default
385 385 os.makedirs(os.path.join(HOME_TEST_DIR, ".cache"))
386 386 os.environ.pop("XDG_CACHE_HOME", None)
387 387 ipdir = path.get_ipython_cache_dir()
388 388 nt.assert_equal(os.path.join(HOME_TEST_DIR, ".cache", "ipython"),
389 389 ipdir)
390 390 nt.assert_true(os.path.isdir(ipdir))
391 391
392 392 # test env override
393 393 os.environ["XDG_CACHE_HOME"] = XDG_CACHE_DIR
394 394 ipdir = path.get_ipython_cache_dir()
395 395 nt.assert_true(os.path.isdir(ipdir))
396 396 nt.assert_equal(ipdir, os.path.join(XDG_CACHE_DIR, "ipython"))
397 397 else:
398 398 nt.assert_equal(path.get_ipython_cache_dir(),
399 399 path.get_ipython_dir())
400 400
401 401 def test_get_ipython_package_dir():
402 402 ipdir = path.get_ipython_package_dir()
403 403 nt.assert_true(os.path.isdir(ipdir))
404 404
405 405
406 406 def test_get_ipython_module_path():
407 ipapp_path = path.get_ipython_module_path('IPython.frontend.terminal.ipapp')
407 ipapp_path = path.get_ipython_module_path('IPython.terminal.ipapp')
408 408 nt.assert_true(os.path.isfile(ipapp_path))
409 409
410 410
411 411 @dec.skip_if_not_win32
412 412 def test_get_long_path_name_win32():
413 413 p = path.get_long_path_name('c:\\docume~1')
414 414 nt.assert_equal(p,u'c:\\Documents and Settings')
415 415
416 416
417 417 @dec.skip_win32
418 418 def test_get_long_path_name():
419 419 p = path.get_long_path_name('/usr/local')
420 420 nt.assert_equal(p,'/usr/local')
421 421
422 422 @dec.skip_win32 # can't create not-user-writable dir on win
423 423 @with_environment
424 424 def test_not_writable_ipdir():
425 425 tmpdir = tempfile.mkdtemp()
426 426 os.name = "posix"
427 427 env.pop('IPYTHON_DIR', None)
428 428 env.pop('IPYTHONDIR', None)
429 429 env.pop('XDG_CONFIG_HOME', None)
430 430 env['HOME'] = tmpdir
431 431 ipdir = os.path.join(tmpdir, '.ipython')
432 432 os.mkdir(ipdir)
433 433 os.chmod(ipdir, 600)
434 434 with AssertPrints('is not a writable location', channel='stderr'):
435 435 ipdir = path.get_ipython_dir()
436 436 env.pop('IPYTHON_DIR', None)
437 437
438 438 def test_unquote_filename():
439 439 for win32 in (True, False):
440 440 nt.assert_equal(path.unquote_filename('foo.py', win32=win32), 'foo.py')
441 441 nt.assert_equal(path.unquote_filename('foo bar.py', win32=win32), 'foo bar.py')
442 442 nt.assert_equal(path.unquote_filename('"foo.py"', win32=True), 'foo.py')
443 443 nt.assert_equal(path.unquote_filename('"foo bar.py"', win32=True), 'foo bar.py')
444 444 nt.assert_equal(path.unquote_filename("'foo.py'", win32=True), 'foo.py')
445 445 nt.assert_equal(path.unquote_filename("'foo bar.py'", win32=True), 'foo bar.py')
446 446 nt.assert_equal(path.unquote_filename('"foo.py"', win32=False), '"foo.py"')
447 447 nt.assert_equal(path.unquote_filename('"foo bar.py"', win32=False), '"foo bar.py"')
448 448 nt.assert_equal(path.unquote_filename("'foo.py'", win32=False), "'foo.py'")
449 449 nt.assert_equal(path.unquote_filename("'foo bar.py'", win32=False), "'foo bar.py'")
450 450
451 451 @with_environment
452 452 def test_get_py_filename():
453 453 os.chdir(TMP_TEST_DIR)
454 454 for win32 in (True, False):
455 455 with make_tempfile('foo.py'):
456 456 nt.assert_equal(path.get_py_filename('foo.py', force_win32=win32), 'foo.py')
457 457 nt.assert_equal(path.get_py_filename('foo', force_win32=win32), 'foo.py')
458 458 with make_tempfile('foo'):
459 459 nt.assert_equal(path.get_py_filename('foo', force_win32=win32), 'foo')
460 460 nt.assert_raises(IOError, path.get_py_filename, 'foo.py', force_win32=win32)
461 461 nt.assert_raises(IOError, path.get_py_filename, 'foo', force_win32=win32)
462 462 nt.assert_raises(IOError, path.get_py_filename, 'foo.py', force_win32=win32)
463 463 true_fn = 'foo with spaces.py'
464 464 with make_tempfile(true_fn):
465 465 nt.assert_equal(path.get_py_filename('foo with spaces', force_win32=win32), true_fn)
466 466 nt.assert_equal(path.get_py_filename('foo with spaces.py', force_win32=win32), true_fn)
467 467 if win32:
468 468 nt.assert_equal(path.get_py_filename('"foo with spaces.py"', force_win32=True), true_fn)
469 469 nt.assert_equal(path.get_py_filename("'foo with spaces.py'", force_win32=True), true_fn)
470 470 else:
471 471 nt.assert_raises(IOError, path.get_py_filename, '"foo with spaces.py"', force_win32=False)
472 472 nt.assert_raises(IOError, path.get_py_filename, "'foo with spaces.py'", force_win32=False)
473 473
474 474 def test_unicode_in_filename():
475 475 """When a file doesn't exist, the exception raised should be safe to call
476 476 str() on - i.e. in Python 2 it must only have ASCII characters.
477 477
478 478 https://github.com/ipython/ipython/issues/875
479 479 """
480 480 try:
481 481 # these calls should not throw unicode encode exceptions
482 482 path.get_py_filename(u'fooéè.py', force_win32=False)
483 483 except IOError as ex:
484 484 str(ex)
485 485
486 486
487 487 class TestShellGlob(object):
488 488
489 489 @classmethod
490 490 def setUpClass(cls):
491 491 cls.filenames_start_with_a = map('a{0}'.format, range(3))
492 492 cls.filenames_end_with_b = map('{0}b'.format, range(3))
493 493 cls.filenames = cls.filenames_start_with_a + cls.filenames_end_with_b
494 494 cls.tempdir = TemporaryDirectory()
495 495 td = cls.tempdir.name
496 496
497 497 with cls.in_tempdir():
498 498 # Create empty files
499 499 for fname in cls.filenames:
500 500 open(os.path.join(td, fname), 'w').close()
501 501
502 502 @classmethod
503 503 def tearDownClass(cls):
504 504 cls.tempdir.cleanup()
505 505
506 506 @classmethod
507 507 @contextmanager
508 508 def in_tempdir(cls):
509 509 save = os.getcwdu()
510 510 try:
511 511 os.chdir(cls.tempdir.name)
512 512 yield
513 513 finally:
514 514 os.chdir(save)
515 515
516 516 def check_match(self, patterns, matches):
517 517 with self.in_tempdir():
518 518 # glob returns unordered list. that's why sorted is required.
519 519 nt.assert_equals(sorted(path.shellglob(patterns)),
520 520 sorted(matches))
521 521
522 522 def common_cases(self):
523 523 return [
524 524 (['*'], self.filenames),
525 525 (['a*'], self.filenames_start_with_a),
526 526 (['*c'], ['*c']),
527 527 (['*', 'a*', '*b', '*c'], self.filenames
528 528 + self.filenames_start_with_a
529 529 + self.filenames_end_with_b
530 530 + ['*c']),
531 531 (['a[012]'], self.filenames_start_with_a),
532 532 ]
533 533
534 534 @skip_win32
535 535 def test_match_posix(self):
536 536 for (patterns, matches) in self.common_cases() + [
537 537 ([r'\*'], ['*']),
538 538 ([r'a\*', 'a*'], ['a*'] + self.filenames_start_with_a),
539 539 ([r'a\[012]'], ['a[012]']),
540 540 ]:
541 541 yield (self.check_match, patterns, matches)
542 542
543 543 @skip_if_not_win32
544 544 def test_match_windows(self):
545 545 for (patterns, matches) in self.common_cases() + [
546 546 # In windows, backslash is interpreted as path
547 547 # separator. Therefore, you can't escape glob
548 548 # using it.
549 549 ([r'a\*', 'a*'], [r'a\*'] + self.filenames_start_with_a),
550 550 ([r'a\[012]'], [r'a\[012]']),
551 551 ]:
552 552 yield (self.check_match, patterns, matches)
553 553
554 554
555 555 def test_unescape_glob():
556 556 nt.assert_equals(path.unescape_glob(r'\*\[\!\]\?'), '*[!]?')
557 557 nt.assert_equals(path.unescape_glob(r'\\*'), r'\*')
558 558 nt.assert_equals(path.unescape_glob(r'\\\*'), r'\*')
559 559 nt.assert_equals(path.unescape_glob(r'\\a'), r'\a')
560 560 nt.assert_equals(path.unescape_glob(r'\a'), r'\a')
@@ -1,39 +1,39 b''
1 1 include README.rst
2 2 include COPYING.txt
3 3 include setupbase.py
4 4 include setupegg.py
5 5
6 6 graft setupext
7 7
8 8 graft scripts
9 9
10 10 # Load main dir but exclude things we don't want in the distro
11 11 graft IPython
12 12 prune IPython/deathrow
13 13 prune IPython/external/js
14 prune IPython/frontend/html/notebook/static/mathjax
14 prune IPython/html/static/mathjax
15 15
16 16 # Include some specific files and data resources we need
17 17 include IPython/.git_commit_info.ini
18 include IPython/frontend/qt/console/resources/icon/IPythonConsole.svg
18 include IPython/qt/console/resources/icon/IPythonConsole.svg
19 19
20 20 # Documentation
21 21 graft docs
22 22 exclude docs/\#*
23 23 exclude docs/man/*.1.gz
24 24
25 25 # Examples
26 26 graft examples
27 27
28 28 # docs subdirs we want to skip
29 29 prune docs/attic
30 30 prune docs/build
31 31 prune docs/gh-pages
32 32 prune docs/dist
33 33
34 34 # Patterns to exclude from any directory
35 35 global-exclude *~
36 36 global-exclude *.flc
37 37 global-exclude *.pyc
38 38 global-exclude *.pyo
39 39 global-exclude .dircopy.log
@@ -1,482 +1,482 b''
1 1 .. _htmlnotebook:
2 2
3 3 =========================
4 4 An HTML Notebook IPython
5 5 =========================
6 6
7 7 .. seealso::
8 8
9 9 :ref:`Installation requirements <installnotebook>` for the Notebook.
10 10
11 11 The IPython Notebook consists of two related components:
12 12
13 13 * An JSON based Notebook document format for recording and distributing
14 14 Python code and rich text.
15 15 * A web-based user interface for authoring and running notebook documents.
16 16
17 17 The Notebook can be used by starting the Notebook server with the
18 18 command::
19 19
20 20 $ ipython notebook
21 21
22 22 Note that by default, the notebook doesn't load pylab, it's just a normal
23 23 IPython session like any other. If you want pylab support, you must use::
24 24
25 25 $ ipython notebook --pylab
26 26
27 27 which will behave similar to the terminal and Qt console versions, using your
28 28 default matplotlib backend and providing floating interactive plot windows. If
29 29 you want inline figures, you must manually select the ``inline`` backend::
30 30
31 31 $ ipython notebook --pylab inline
32 32
33 33 This server uses the same ZeroMQ-based two process kernel architecture as
34 34 the QT Console as well Tornado for serving HTTP/S requests. Some of the main
35 35 features of the Notebook include:
36 36
37 37 * Display rich data (png/html/latex/svg) in the browser as a result of
38 38 computations.
39 39 * Compose text cells using HTML and Markdown.
40 40 * Import and export notebook documents in range of formats (.ipynb, .py).
41 41 * In browser syntax highlighting, tab completion and autoindentation.
42 42 * Inline matplotlib plots that can be stored in Notebook documents and opened
43 43 later.
44 44
45 45 See :ref:`our installation documentation <install_index>` for directions on
46 46 how to install the notebook and its dependencies.
47 47
48 48 .. note::
49 49
50 50 You can start more than one notebook server at the same time, if you want to
51 51 work on notebooks in different directories. By default the first notebook
52 52 server starts in port 8888, later notebooks search for random ports near
53 53 that one. You can also manually specify the port with the ``--port``
54 54 option.
55 55
56 56
57 57 Basic Usage
58 58 ===========
59 59
60 60 The landing page of the notebook server application, which we call the IPython
61 61 Notebook *dashboard*, shows the notebooks currently available in the directory
62 62 in which the application was started, and allows you to create new notebooks.
63 63
64 64 A notebook is a combination of two things:
65 65
66 66 1. An interactive session connected to an IPython kernel, controlled by a web
67 67 application that can send input to the console and display many types of
68 68 output (text, graphics, mathematics and more). This is the same kernel used
69 69 by the :ref:`Qt console <qtconsole>`, but in this case the web console sends
70 70 input in persistent cells that you can edit in-place instead of the
71 71 vertically scrolling terminal style used by the Qt console.
72 72
73 73 2. A document that can save the inputs and outputs of the session as well as
74 74 additional text that accompanies the code but is not meant for execution.
75 75 In this way, notebook files serve as a complete computational record of a
76 76 session including explanatory text and mathematics, code and resulting
77 77 figures. These documents are internally JSON files and are saved with the
78 78 ``.ipynb`` extension.
79 79
80 80 If you have ever used the Mathematica or Sage notebooks (the latter is also
81 81 web-based__) you should feel right at home. If you have not, you should be
82 82 able to learn how to use it in just a few minutes.
83 83
84 84 .. __: http://sagenb.org
85 85
86 86
87 87 Creating and editing notebooks
88 88 ------------------------------
89 89
90 90 You can create new notebooks from the dashboard with the ``New Notebook``
91 91 button or open existing ones by clicking on their name. Once in a notebook,
92 92 your browser tab will reflect the name of that notebook (prefixed with "IPy:").
93 93 The URL for that notebook is not meant to be human-readable and is *not*
94 94 persistent across invocations of the notebook server.
95 95
96 96 You can also drag and drop into the area listing files any python file: it
97 97 will be imported into a notebook with the same name (but ``.ipynb`` extension)
98 98 located in the directory where the notebook server was started. This notebook
99 99 will consist of a single cell with all the code in the file, which you can
100 100 later manually partition into individual cells for gradual execution, add text
101 101 and graphics, etc.
102 102
103 103
104 104 Workflow and limitations
105 105 ------------------------
106 106
107 107 The normal workflow in a notebook is quite similar to a normal IPython session,
108 108 with the difference that you can edit a cell in-place multiple times until you
109 109 obtain the desired results rather than having to rerun separate scripts with
110 110 the ``%run`` magic (though magics also work in the notebook). Typically
111 111 you'll work on a problem in pieces, organizing related pieces into cells and
112 112 moving forward as previous parts work correctly. This is much more convenient
113 113 for interactive exploration than breaking up a computation into scripts that
114 114 must be executed together, especially if parts of them take a long time to run
115 115 (In the traditional terminal-based IPython, you can use tricks with namespaces
116 116 and ``%run -i`` to achieve this capability, but we think the notebook is a more
117 117 natural solution for that kind of problem).
118 118
119 119 The only significant limitation the notebook currently has, compared to the qt
120 120 console, is that it can not run any code that expects input from the kernel
121 121 (such as scripts that call :func:`raw_input`). Very importantly, this means
122 122 that the ``%debug`` magic does *not* work in the notebook! We intend to
123 123 correct this limitation, but in the meantime, there is a way to debug problems
124 124 in the notebook: you can attach a Qt console to your existing notebook kernel,
125 125 and run ``%debug`` from the Qt console. If your notebook is running on a local
126 126 computer (i.e. if you are accessing it via your localhost address at
127 127 127.0.0.1), you can just type ``%qtconsole`` in the notebook and a Qt console
128 128 will open up connected to that same kernel.
129 129
130 130 In general, the notebook server prints the full details of how to connect to
131 131 each kernel at the terminal, with lines like::
132 132
133 133 [IPKernelApp] To connect another client to this kernel, use:
134 134 [IPKernelApp] --existing kernel-3bb93edd-6b5a-455c-99c8-3b658f45dde5.json
135 135
136 136 This is the name of a JSON file that contains all the port and validation
137 137 information necessary to connect to the kernel. You can manually start a
138 138 qt console with::
139 139
140 140 ipython qtconsole --existing kernel-3bb93edd-6b5a-455c-99c8-3b658f45dde5.json
141 141
142 142 and if you only have a single kernel running, simply typing::
143 143
144 144 ipython qtconsole --existing
145 145
146 146 will automatically find it (it will always find the most recently started
147 147 kernel if there is more than one). You can also request this connection data
148 148 by typing ``%connect_info``; this will print the same file information as well
149 149 as the content of the JSON data structure it contains.
150 150
151 151
152 152 Text input
153 153 ----------
154 154
155 155 In addition to code cells and the output they produce (such as figures), you
156 156 can also type text not meant for execution. To type text, change the type of a
157 157 cell from ``Code`` to ``Markdown`` by using the button or the :kbd:`Ctrl-m m`
158 158 keybinding (see below). You can then type any text in Markdown_ syntax, as
159 159 well as mathematical expressions if you use ``$...$`` for inline math or
160 160 ``$$...$$`` for displayed math.
161 161
162 162
163 163 Exporting a notebook and importing existing scripts
164 164 ---------------------------------------------------
165 165
166 166 If you want to provide others with a static HTML or PDF view of your notebook,
167 167 use the ``Print`` button. This opens a static view of the document, which you
168 168 can print to PDF using your operating system's facilities, or save to a file
169 169 with your web browser's 'Save' option (note that typically, this will create
170 170 both an html file *and* a directory called `notebook_name_files` next to it
171 171 that contains all the necessary style information, so if you intend to share
172 172 this, you must send the directory along with the main html file).
173 173
174 174 The `Download` button lets you save a notebook file to the Download area
175 175 configured by your web browser (particularly useful if you are running the
176 176 notebook server on a remote host and need a file locally). The notebook is
177 177 saved by default with the ``.ipynb`` extension and the files contain JSON data
178 178 that is not meant for human editing or consumption. But you can always export
179 179 the input part of a notebook to a plain python script by choosing Python format
180 180 in the `Download` drop list. This removes all output and saves the text cells
181 181 in comment areas. See ref:`below <notebook_format>` for more details on the
182 182 notebook format.
183 183
184 184 The notebook can also *import* ``.py`` files as notebooks, by dragging and
185 185 dropping the file into the notebook dashboard file list area. By default, the
186 186 entire contents of the file will be loaded into a single code cell. But if
187 187 prior to import, you manually add the ``# <nbformat>2</nbformat>`` marker at
188 188 the start and then add separators for text/code cells, you can get a cleaner
189 189 import with the file broken into individual cells.
190 190
191 191 .. warning::
192 192
193 193 While in simple cases you can roundtrip a notebook to Python, edit the
194 194 python file and import it back without loss of main content, this is in
195 195 general *not guaranteed to work at all*. First, there is extra metadata
196 196 saved in the notebook that may not be saved to the ``.py`` format. And as
197 197 the notebook format evolves in complexity, there will be attributes of the
198 198 notebook that will not survive a roundtrip through the Python form. You
199 199 should think of the Python format as a way to output a script version of a
200 200 notebook and the import capabilities as a way to load existing code to get a
201 201 notebook started. But the Python version is *not* an alternate notebook
202 202 format.
203 203
204 204
205 205 Importing or executing a notebook as a normal Python file
206 206 ---------------------------------------------------------
207 207
208 208 The native format of the notebook, a file with a ``.ipynb`` extension, is a
209 209 JSON container of all the input and output of the notebook, and therefore not
210 210 valid Python by itself. This means that by default, you can not import a
211 211 notebook or execute it as a normal python script. But if you want use
212 212 notebooks as regular Python files, you can start the notebook server with::
213 213
214 214 ipython notebook --script
215 215
216 216 or you can set this option permanently in your configuration file with::
217 217
218 218 c.NotebookManager.save_script=True
219 219
220 220 This will instruct the notebook server to save the ``.py`` export of each
221 221 notebook adjacent to the ``.ipynb`` at every save. These files can be
222 222 ``%run``, imported from regular IPython sessions or other notebooks, or
223 223 executed at the command-line as normal Python files. Since we export the raw
224 224 code you have typed, for these files to be importable from other code you will
225 225 have to avoid using syntax such as ``%magics`` and other IPython-specific
226 226 extensions to the language.
227 227
228 228 In regular practice, the standard way to differentiate importable code from the
229 229 'executable' part of a script is to put at the bottom::
230 230
231 231 if __name__ == '__main__':
232 232 # rest of the code...
233 233
234 234 Since all cells in the notebook are run as top-level code, you'll need to
235 235 similarly protect *all* cells that you do not want executed when other scripts
236 236 try to import your notebook. A convenient shortand for this is to define early
237 237 on::
238 238
239 239 script = __name__ == '__main__'
240 240
241 241 and then on any cell that you need to protect, use::
242 242
243 243 if script:
244 244 # rest of the cell...
245 245
246 246 Configuration
247 247 -------------
248 248
249 249 The IPython notebook server can be run with a variety of command line arguments.
250 250 To see a list of available options enter:
251 251
252 252 $ ipython notebook --help
253 253
254 254 Defaults for these options can also be set by creating a file named
255 255 ipython_notebook_config.py in your IPython profile folder. The profile folder is
256 256 a subfolder of your IPython directory (`ipython locate` will show you where that
257 257 is). To create default configuration files (with lots of info on available
258 258 options) use:
259 259
260 260 $ ipython profile create
261 261
262 262 .. seealso:
263 263
264 264 :ref:`config_overview`, in particular :ref:`Profiles`.
265 265
266 266
267 267 Keyboard use
268 268 ------------
269 269
270 270 All actions in the notebook can be achieved with the mouse, but we have also
271 271 added keyboard shortcuts for the most common ones, so that productive use of
272 272 the notebook can be achieved with minimal mouse intervention. The main
273 273 key bindings you need to remember are:
274 274
275 275 * :kbd:`Shift-Enter`: execute the current cell (similar to the Qt console),
276 276 show output (if any) and jump to the next cell below. If :kbd:`Shift-Enter`
277 277 was invoked on the last input line, a new code cell will also be created. Note
278 278 that in the notebook, simply using :kbd:`Enter` *never* forces execution,
279 279 it simply inserts a new line in the current cell. Therefore, in the notebook
280 280 you must always use :kbd:`Shift-Enter` to get execution (or use the mouse and
281 281 click on the ``Run Selected`` button).
282 282
283 283 * :kbd:`Alt-Enter`: this combination is similar to the previous one, with the
284 284 exception that, if the next cell below is not empty, a new code cell will be
285 285 added to the notebook, even if the cell execution happens not in the last cell.
286 286 In this regard, :kbd:`Alt-Enter`: is simply a shortcut for the :kbd:`Shift-Enter`,
287 287 :kbd:`Ctrl-m a` sequence.
288 288
289 289 * :kbd:`Ctrl-Enter`: execute the current cell in "terminal mode", where any
290 290 output is shown but the cursor stays in the current cell, whose input
291 291 area is flushed empty. This is convenient to do quick in-place experiments
292 292 or query things like filesystem content without creating additional cells you
293 293 may not want saved in your notebook.
294 294
295 295 * :kbd:`Ctrl-m`: this is the prefix for all other keybindings, which consist
296 296 of an additional single letter. Type :kbd:`Ctrl-m h` (that is, the sole
297 297 letter :kbd:`h` after :kbd:`Ctrl-m`) and IPython will show you the remaining
298 298 available keybindings.
299 299
300 300
301 301 .. _notebook_security:
302 302
303 303 Security
304 304 ========
305 305
306 306 You can protect your notebook server with a simple single-password by
307 307 setting the :attr:`NotebookApp.password` configurable. You can prepare a
308 308 hashed password using the function :func:`IPython.lib.security.passwd`:
309 309
310 310 .. sourcecode:: ipython
311 311
312 312 In [1]: from IPython.lib import passwd
313 313 In [2]: passwd()
314 314 Enter password:
315 315 Verify password:
316 316 Out[2]: 'sha1:67c9e60bb8b6:9ffede0825894254b2e042ea597d771089e11aed'
317 317
318 318 .. note::
319 319
320 320 :func:`~IPython.lib.security.passwd` can also take the password as a string
321 321 argument. **Do not** pass it as an argument inside an IPython session, as it
322 322 will be saved in your input history.
323 323
324 324 You can then add this to your :file:`ipython_notebook_config.py`, e.g.::
325 325
326 326 # Password to use for web authentication
327 327 c.NotebookApp.password = u'sha1:67c9e60bb8b6:9ffede0825894254b2e042ea597d771089e11aed'
328 328
329 329 When using a password, it is a good idea to also use SSL, so that your password
330 330 is not sent unencrypted by your browser. You can start the notebook to
331 331 communicate via a secure protocol mode using a self-signed certificate by
332 332 typing::
333 333
334 334 $ ipython notebook --certfile=mycert.pem
335 335
336 336 .. note::
337 337
338 338 A self-signed certificate can be generated with openssl. For example, the
339 339 following command will create a certificate valid for 365 days with both
340 340 the key and certificate data written to the same file::
341 341
342 342 $ openssl req -x509 -nodes -days 365 -newkey rsa:1024 -keyout mycert.pem -out mycert.pem
343 343
344 344 Your browser will warn you of a dangerous certificate because it is
345 345 self-signed. If you want to have a fully compliant certificate that will not
346 346 raise warnings, it is possible (but rather involved) to obtain one for free,
347 347 `as explained in detailed in this tutorial`__.
348 348
349 349 .. __: http://arstechnica.com/security/news/2009/12/how-to-get-set-with-a-secure-sertificate-for-free.ars
350 350
351 351 Keep in mind that when you enable SSL support, you'll need to access the
352 352 notebook server over ``https://``, not over plain ``http://``. The startup
353 353 message from the server prints this, but it's easy to overlook and think the
354 354 server is for some reason non-responsive.
355 355
356 356 Quick how to's
357 357 ==============
358 358
359 359 Running a public notebook server
360 360 --------------------------------
361 361
362 362 If you want to access your notebook server remotely with just a web browser,
363 363 here is a quick set of instructions. Start by creating a certificate file and
364 364 a hashed password as explained above. Then, create a custom profile for the
365 365 notebook. At the command line, type::
366 366
367 367 ipython profile create nbserver
368 368
369 369 In the profile directory, edit the file ``ipython_notebook_config.py``. By
370 370 default the file has all fields commented, the minimum set you need to
371 371 uncomment and edit is here::
372 372
373 373 c = get_config()
374 374
375 375 # Kernel config
376 376 c.IPKernelApp.pylab = 'inline' # if you want plotting support always
377 377
378 378 # Notebook config
379 379 c.NotebookApp.certfile = u'/absolute/path/to/your/certificate/mycert.pem'
380 380 c.NotebookApp.ip = '*'
381 381 c.NotebookApp.open_browser = False
382 382 c.NotebookApp.password = u'sha1:bcd259ccf...your hashed password here'
383 383 # It's a good idea to put it on a known, fixed port
384 384 c.NotebookApp.port = 9999
385 385
386 386 You can then start the notebook and access it later by pointing your browser to
387 387 ``https://your.host.com:9999`` with ``ipython notebook --profile=nbserver``.
388 388
389 389 Running with a different URL prefix
390 390 -----------------------------------
391 391
392 392 The notebook dashboard (i.e. the default landing page with an overview
393 393 of all your notebooks) typically lives at a URL path of
394 394 "http://localhost:8888/". If you want to have it, and the rest of the
395 395 notebook, live under a sub-directory,
396 396 e.g. "http://localhost:8888/ipython/", you can do so with
397 397 configuration options like these (see above for instructions about
398 398 modifying ``ipython_notebook_config.py``)::
399 399
400 400 c.NotebookApp.base_project_url = '/ipython/'
401 401 c.NotebookApp.base_kernel_url = '/ipython/'
402 402 c.NotebookApp.webapp_settings = {'static_url_prefix':'/ipython/static/'}
403 403
404 404 Using a different notebook store
405 405 --------------------------------
406 406
407 407 By default the notebook server stores notebooks as files in the working
408 408 directory of the notebook server, also known as the ``notebook_dir``. This
409 409 logic is implemented in the :class:`FileNotebookManager` class. However, the
410 410 server can be configured to use a different notebook manager class, which can
411 411 store the notebooks in a different format. Currently, we ship a
412 412 :class:`AzureNotebookManager` class that stores notebooks in Azure blob
413 413 storage. This can be used by adding the following lines to your
414 414 ``ipython_notebook_config.py`` file::
415 415
416 c.NotebookApp.notebook_manager_class = 'IPython.frontend.html.notebook.azurenbmanager.AzureNotebookManager'
416 c.NotebookApp.notebook_manager_class = 'IPython.html.services.notebooks.azurenbmanager.AzureNotebookManager'
417 417 c.AzureNotebookManager.account_name = u'paste_your_account_name_here'
418 418 c.AzureNotebookManager.account_key = u'paste_your_account_key_here'
419 419 c.AzureNotebookManager.container = u'notebooks'
420 420
421 421 In addition to providing your Azure Blob Storage account name and key, you will
422 422 have to provide a container name; you can use multiple containers to organize
423 423 your Notebooks.
424 424
425 425 .. _notebook_format:
426 426
427 427 The notebook format
428 428 ===================
429 429
430 430 The notebooks themselves are JSON files with an ``ipynb`` extension, formatted
431 431 as legibly as possible with minimal extra indentation and cell content broken
432 432 across lines to make them reasonably friendly to use in version-control
433 433 workflows. You should be very careful if you ever edit manually this JSON
434 434 data, as it is extremely easy to corrupt its internal structure and make the
435 435 file impossible to load. In general, you should consider the notebook as a
436 436 file meant only to be edited by IPython itself, not for hand-editing.
437 437
438 438 .. note::
439 439
440 440 Binary data such as figures are directly saved in the JSON file. This
441 441 provides convenient single-file portability but means the files can be
442 442 large and diffs of binary data aren't very meaningful. Since the binary
443 443 blobs are encoded in a single line they only affect one line of the diff
444 444 output, but they are typically very long lines. You can use the
445 445 'ClearAll' button to remove all output from a notebook prior to
446 446 committing it to version control, if this is a concern.
447 447
448 448 The notebook server can also generate a pure-python version of your notebook,
449 449 by clicking on the 'Download' button and selecting ``py`` as the format. This
450 450 file will contain all the code cells from your notebook verbatim, and all text
451 451 cells prepended with a comment marker. The separation between code and text
452 452 cells is indicated with special comments and there is a header indicating the
453 453 format version. All output is stripped out when exporting to python.
454 454
455 455 Here is an example of a simple notebook with one text cell and one code input
456 456 cell, when exported to python format::
457 457
458 458 # <nbformat>2</nbformat>
459 459
460 460 # <markdowncell>
461 461
462 462 # A text cell
463 463
464 464 # <codecell>
465 465
466 466 print "hello IPython"
467 467
468 468
469 469 Known issues
470 470 ============
471 471
472 472 When behind a proxy, especially if your system or browser is set to autodetect
473 473 the proxy, the html notebook might fail to connect to the server's websockets,
474 474 and present you with a warning at startup. In this case, you need to configure
475 475 your system not to use the proxy for the server's address.
476 476
477 477 In Firefox, for example, go to the Preferences panel, Advanced section,
478 478 Network tab, click 'Settings...', and add the address of the notebook server
479 479 to the 'No proxy for' field.
480 480
481 481
482 482 .. _Markdown: http://daringfireball.net/projects/markdown/basics
@@ -1,468 +1,468 b''
1 1 # encoding: utf-8
2 2 """
3 3 This module defines the things that are used in setup.py for building IPython
4 4
5 5 This includes:
6 6
7 7 * The basic arguments to setup
8 8 * Functions for finding things like packages, package data, etc.
9 9 * A function for checking dependencies.
10 10 """
11 11 from __future__ import print_function
12 12
13 13 #-------------------------------------------------------------------------------
14 14 # Copyright (C) 2008 The IPython Development Team
15 15 #
16 16 # Distributed under the terms of the BSD License. The full license is in
17 17 # the file COPYING, distributed as part of this software.
18 18 #-------------------------------------------------------------------------------
19 19
20 20 #-------------------------------------------------------------------------------
21 21 # Imports
22 22 #-------------------------------------------------------------------------------
23 23 import os
24 24 import sys
25 25
26 26 try:
27 27 from configparser import ConfigParser
28 28 except:
29 29 from ConfigParser import ConfigParser
30 30 from distutils.command.build_py import build_py
31 31 from distutils.cmd import Command
32 32 from glob import glob
33 33
34 34 from setupext import install_data_ext
35 35
36 36 #-------------------------------------------------------------------------------
37 37 # Useful globals and utility functions
38 38 #-------------------------------------------------------------------------------
39 39
40 40 # A few handy globals
41 41 isfile = os.path.isfile
42 42 pjoin = os.path.join
43 43 repo_root = os.path.dirname(os.path.abspath(__file__))
44 44
45 45 def oscmd(s):
46 46 print(">", s)
47 47 os.system(s)
48 48
49 49 # Py3 compatibility hacks, without assuming IPython itself is installed with
50 50 # the full py3compat machinery.
51 51
52 52 try:
53 53 execfile
54 54 except NameError:
55 55 def execfile(fname, globs, locs=None):
56 56 locs = locs or globs
57 57 exec(compile(open(fname).read(), fname, "exec"), globs, locs)
58 58
59 59 # A little utility we'll need below, since glob() does NOT allow you to do
60 60 # exclusion on multiple endings!
61 61 def file_doesnt_endwith(test,endings):
62 62 """Return true if test is a file and its name does NOT end with any
63 63 of the strings listed in endings."""
64 64 if not isfile(test):
65 65 return False
66 66 for e in endings:
67 67 if test.endswith(e):
68 68 return False
69 69 return True
70 70
71 71 #---------------------------------------------------------------------------
72 72 # Basic project information
73 73 #---------------------------------------------------------------------------
74 74
75 75 # release.py contains version, authors, license, url, keywords, etc.
76 76 execfile(pjoin(repo_root, 'IPython','core','release.py'), globals())
77 77
78 78 # Create a dict with the basic information
79 79 # This dict is eventually passed to setup after additional keys are added.
80 80 setup_args = dict(
81 81 name = name,
82 82 version = version,
83 83 description = description,
84 84 long_description = long_description,
85 85 author = author,
86 86 author_email = author_email,
87 87 url = url,
88 88 download_url = download_url,
89 89 license = license,
90 90 platforms = platforms,
91 91 keywords = keywords,
92 92 classifiers = classifiers,
93 93 cmdclass = {'install_data': install_data_ext},
94 94 )
95 95
96 96
97 97 #---------------------------------------------------------------------------
98 98 # Find packages
99 99 #---------------------------------------------------------------------------
100 100
101 101 def find_packages():
102 102 """
103 103 Find all of IPython's packages.
104 104 """
105 105 excludes = ['deathrow', 'quarantine']
106 106 packages = []
107 107 for dir,subdirs,files in os.walk('IPython'):
108 108 package = dir.replace(os.path.sep, '.')
109 109 if any(package.startswith('IPython.'+exc) for exc in excludes):
110 110 # package is to be excluded (e.g. deathrow)
111 111 continue
112 112 if '__init__.py' not in files:
113 113 # not a package
114 114 continue
115 115 packages.append(package)
116 116 return packages
117 117
118 118 #---------------------------------------------------------------------------
119 119 # Find package data
120 120 #---------------------------------------------------------------------------
121 121
122 122 def find_package_data():
123 123 """
124 124 Find IPython's package_data.
125 125 """
126 126 # This is not enough for these things to appear in an sdist.
127 127 # We need to muck with the MANIFEST to get this to work
128 128
129 129 # exclude static things that we don't ship (e.g. mathjax)
130 130 excludes = ['mathjax']
131 131
132 132 # add 'static/' prefix to exclusions, and tuplify for use in startswith
133 133 excludes = tuple([os.path.join('static', ex) for ex in excludes])
134 134
135 135 # walk notebook resources:
136 136 cwd = os.getcwd()
137 os.chdir(os.path.join('IPython', 'frontend', 'html', 'notebook'))
137 os.chdir(os.path.join('IPython', 'html'))
138 138 static_walk = list(os.walk('static'))
139 139 os.chdir(cwd)
140 140 static_data = []
141 141 for parent, dirs, files in static_walk:
142 142 if parent.startswith(excludes):
143 143 continue
144 144 for f in files:
145 145 static_data.append(os.path.join(parent, f))
146 146
147 147 package_data = {
148 148 'IPython.config.profile' : ['README*', '*/*.py'],
149 149 'IPython.testing' : ['*.txt'],
150 150 'IPython.testing.plugin' : ['*.txt'],
151 'IPython.frontend.html.notebook' : ['templates/*'] + static_data,
152 'IPython.frontend.qt.console' : ['resources/icon/*.svg'],
151 'IPython.html' : ['templates/*'] + static_data,
152 'IPython.qt.console' : ['resources/icon/*.svg'],
153 153 }
154 154 return package_data
155 155
156 156
157 157 #---------------------------------------------------------------------------
158 158 # Find data files
159 159 #---------------------------------------------------------------------------
160 160
161 161 def make_dir_struct(tag,base,out_base):
162 162 """Make the directory structure of all files below a starting dir.
163 163
164 164 This is just a convenience routine to help build a nested directory
165 165 hierarchy because distutils is too stupid to do this by itself.
166 166
167 167 XXX - this needs a proper docstring!
168 168 """
169 169
170 170 # we'll use these a lot below
171 171 lbase = len(base)
172 172 pathsep = os.path.sep
173 173 lpathsep = len(pathsep)
174 174
175 175 out = []
176 176 for (dirpath,dirnames,filenames) in os.walk(base):
177 177 # we need to strip out the dirpath from the base to map it to the
178 178 # output (installation) path. This requires possibly stripping the
179 179 # path separator, because otherwise pjoin will not work correctly
180 180 # (pjoin('foo/','/bar') returns '/bar').
181 181
182 182 dp_eff = dirpath[lbase:]
183 183 if dp_eff.startswith(pathsep):
184 184 dp_eff = dp_eff[lpathsep:]
185 185 # The output path must be anchored at the out_base marker
186 186 out_path = pjoin(out_base,dp_eff)
187 187 # Now we can generate the final filenames. Since os.walk only produces
188 188 # filenames, we must join back with the dirpath to get full valid file
189 189 # paths:
190 190 pfiles = [pjoin(dirpath,f) for f in filenames]
191 191 # Finally, generate the entry we need, which is a pari of (output
192 192 # path, files) for use as a data_files parameter in install_data.
193 193 out.append((out_path, pfiles))
194 194
195 195 return out
196 196
197 197
198 198 def find_data_files():
199 199 """
200 200 Find IPython's data_files.
201 201
202 202 Most of these are docs.
203 203 """
204 204
205 205 docdirbase = pjoin('share', 'doc', 'ipython')
206 206 manpagebase = pjoin('share', 'man', 'man1')
207 207
208 208 # Simple file lists can be made by hand
209 209 manpages = [f for f in glob(pjoin('docs','man','*.1.gz')) if isfile(f)]
210 210 if not manpages:
211 211 # When running from a source tree, the manpages aren't gzipped
212 212 manpages = [f for f in glob(pjoin('docs','man','*.1')) if isfile(f)]
213 213
214 214 igridhelpfiles = [f for f in glob(pjoin('IPython','extensions','igrid_help.*')) if isfile(f)]
215 215
216 216 # For nested structures, use the utility above
217 217 example_files = make_dir_struct(
218 218 'data',
219 219 pjoin('docs','examples'),
220 220 pjoin(docdirbase,'examples')
221 221 )
222 222 manual_files = make_dir_struct(
223 223 'data',
224 224 pjoin('docs','html'),
225 225 pjoin(docdirbase,'manual')
226 226 )
227 227
228 228 # And assemble the entire output list
229 229 data_files = [ (manpagebase, manpages),
230 230 (pjoin(docdirbase, 'extensions'), igridhelpfiles),
231 231 ] + manual_files + example_files
232 232
233 233 return data_files
234 234
235 235
236 236 def make_man_update_target(manpage):
237 237 """Return a target_update-compliant tuple for the given manpage.
238 238
239 239 Parameters
240 240 ----------
241 241 manpage : string
242 242 Name of the manpage, must include the section number (trailing number).
243 243
244 244 Example
245 245 -------
246 246
247 247 >>> make_man_update_target('ipython.1') #doctest: +NORMALIZE_WHITESPACE
248 248 ('docs/man/ipython.1.gz',
249 249 ['docs/man/ipython.1'],
250 250 'cd docs/man && gzip -9c ipython.1 > ipython.1.gz')
251 251 """
252 252 man_dir = pjoin('docs', 'man')
253 253 manpage_gz = manpage + '.gz'
254 254 manpath = pjoin(man_dir, manpage)
255 255 manpath_gz = pjoin(man_dir, manpage_gz)
256 256 gz_cmd = ( "cd %(man_dir)s && gzip -9c %(manpage)s > %(manpage_gz)s" %
257 257 locals() )
258 258 return (manpath_gz, [manpath], gz_cmd)
259 259
260 260 # The two functions below are copied from IPython.utils.path, so we don't need
261 261 # to import IPython during setup, which fails on Python 3.
262 262
263 263 def target_outdated(target,deps):
264 264 """Determine whether a target is out of date.
265 265
266 266 target_outdated(target,deps) -> 1/0
267 267
268 268 deps: list of filenames which MUST exist.
269 269 target: single filename which may or may not exist.
270 270
271 271 If target doesn't exist or is older than any file listed in deps, return
272 272 true, otherwise return false.
273 273 """
274 274 try:
275 275 target_time = os.path.getmtime(target)
276 276 except os.error:
277 277 return 1
278 278 for dep in deps:
279 279 dep_time = os.path.getmtime(dep)
280 280 if dep_time > target_time:
281 281 #print "For target",target,"Dep failed:",dep # dbg
282 282 #print "times (dep,tar):",dep_time,target_time # dbg
283 283 return 1
284 284 return 0
285 285
286 286
287 287 def target_update(target,deps,cmd):
288 288 """Update a target with a given command given a list of dependencies.
289 289
290 290 target_update(target,deps,cmd) -> runs cmd if target is outdated.
291 291
292 292 This is just a wrapper around target_outdated() which calls the given
293 293 command if target is outdated."""
294 294
295 295 if target_outdated(target,deps):
296 296 os.system(cmd)
297 297
298 298 #---------------------------------------------------------------------------
299 299 # Find scripts
300 300 #---------------------------------------------------------------------------
301 301
302 302 def find_scripts(entry_points=False, suffix=''):
303 303 """Find IPython's scripts.
304 304
305 305 if entry_points is True:
306 306 return setuptools entry_point-style definitions
307 307 else:
308 308 return file paths of plain scripts [default]
309 309
310 310 suffix is appended to script names if entry_points is True, so that the
311 311 Python 3 scripts get named "ipython3" etc.
312 312 """
313 313 if entry_points:
314 314 console_scripts = [s % suffix for s in [
315 'ipython%s = IPython.frontend.terminal.ipapp:launch_new_instance',
315 'ipython%s = IPython.terminal.ipapp:launch_new_instance',
316 316 'pycolor%s = IPython.utils.PyColorize:main',
317 317 'ipcontroller%s = IPython.parallel.apps.ipcontrollerapp:launch_new_instance',
318 318 'ipengine%s = IPython.parallel.apps.ipengineapp:launch_new_instance',
319 319 'iplogger%s = IPython.parallel.apps.iploggerapp:launch_new_instance',
320 320 'ipcluster%s = IPython.parallel.apps.ipclusterapp:launch_new_instance',
321 321 'iptest%s = IPython.testing.iptest:main',
322 322 'irunner%s = IPython.lib.irunner:main'
323 323 ]]
324 324 gui_scripts = []
325 325 scripts = dict(console_scripts=console_scripts, gui_scripts=gui_scripts)
326 326 else:
327 327 parallel_scripts = pjoin('IPython','parallel','scripts')
328 328 main_scripts = pjoin('IPython','scripts')
329 329 scripts = [
330 330 pjoin(parallel_scripts, 'ipengine'),
331 331 pjoin(parallel_scripts, 'ipcontroller'),
332 332 pjoin(parallel_scripts, 'ipcluster'),
333 333 pjoin(parallel_scripts, 'iplogger'),
334 334 pjoin(main_scripts, 'ipython'),
335 335 pjoin(main_scripts, 'pycolor'),
336 336 pjoin(main_scripts, 'irunner'),
337 337 pjoin(main_scripts, 'iptest')
338 338 ]
339 339 return scripts
340 340
341 341 #---------------------------------------------------------------------------
342 342 # Verify all dependencies
343 343 #---------------------------------------------------------------------------
344 344
345 345 def check_for_dependencies():
346 346 """Check for IPython's dependencies.
347 347
348 348 This function should NOT be called if running under setuptools!
349 349 """
350 350 from setupext.setupext import (
351 351 print_line, print_raw, print_status,
352 352 check_for_sphinx, check_for_pygments,
353 353 check_for_nose, check_for_pexpect,
354 354 check_for_pyzmq, check_for_readline
355 355 )
356 356 print_line()
357 357 print_raw("BUILDING IPYTHON")
358 358 print_status('python', sys.version)
359 359 print_status('platform', sys.platform)
360 360 if sys.platform == 'win32':
361 361 print_status('Windows version', sys.getwindowsversion())
362 362
363 363 print_raw("")
364 364 print_raw("OPTIONAL DEPENDENCIES")
365 365
366 366 check_for_sphinx()
367 367 check_for_pygments()
368 368 check_for_nose()
369 369 check_for_pexpect()
370 370 check_for_pyzmq()
371 371 check_for_readline()
372 372
373 373 #---------------------------------------------------------------------------
374 374 # VCS related
375 375 #---------------------------------------------------------------------------
376 376
377 377 # utils.submodule has checks for submodule status
378 378 execfile(pjoin('IPython','utils','submodule.py'), globals())
379 379
380 380 class UpdateSubmodules(Command):
381 381 """Update git submodules
382 382
383 383 IPython's external javascript dependencies live in a separate repo.
384 384 """
385 385 description = "Update git submodules"
386 386 user_options = []
387 387
388 388 def initialize_options(self):
389 389 pass
390 390
391 391 def finalize_options(self):
392 392 pass
393 393
394 394 def run(self):
395 395 failure = False
396 396 try:
397 397 self.spawn('git submodule init'.split())
398 398 self.spawn('git submodule update --recursive'.split())
399 399 except Exception as e:
400 400 failure = e
401 401 print(e)
402 402
403 403 if not check_submodule_status(repo_root) == 'clean':
404 404 print("submodules could not be checked out")
405 405 sys.exit(1)
406 406
407 407
408 408 def git_prebuild(pkg_dir, build_cmd=build_py):
409 409 """Return extended build or sdist command class for recording commit
410 410
411 411 records git commit in IPython.utils._sysinfo.commit
412 412
413 413 for use in IPython.utils.sysinfo.sys_info() calls after installation.
414 414
415 415 Also ensures that submodules exist prior to running
416 416 """
417 417
418 418 class MyBuildPy(build_cmd):
419 419 ''' Subclass to write commit data into installation tree '''
420 420 def run(self):
421 421 build_cmd.run(self)
422 422 # this one will only fire for build commands
423 423 if hasattr(self, 'build_lib'):
424 424 self._record_commit(self.build_lib)
425 425
426 426 def make_release_tree(self, base_dir, files):
427 427 # this one will fire for sdist
428 428 build_cmd.make_release_tree(self, base_dir, files)
429 429 self._record_commit(base_dir)
430 430
431 431 def _record_commit(self, base_dir):
432 432 import subprocess
433 433 proc = subprocess.Popen('git rev-parse --short HEAD',
434 434 stdout=subprocess.PIPE,
435 435 stderr=subprocess.PIPE,
436 436 shell=True)
437 437 repo_commit, _ = proc.communicate()
438 438 repo_commit = repo_commit.strip().decode("ascii")
439 439
440 440 out_pth = pjoin(base_dir, pkg_dir, 'utils', '_sysinfo.py')
441 441 if os.path.isfile(out_pth) and not repo_commit:
442 442 # nothing to write, don't clobber
443 443 return
444 444
445 445 print("writing git commit '%s' to %s" % (repo_commit, out_pth))
446 446
447 447 # remove to avoid overwriting original via hard link
448 448 try:
449 449 os.remove(out_pth)
450 450 except (IOError, OSError):
451 451 pass
452 452 with open(out_pth, 'w') as out_file:
453 453 out_file.writelines([
454 454 '# GENERATED BY setup.py\n',
455 455 'commit = "%s"\n' % repo_commit,
456 456 ])
457 457 return require_submodules(MyBuildPy)
458 458
459 459
460 460 def require_submodules(command):
461 461 """decorator for instructing a command to check for submodules before running"""
462 462 class DecoratedCommand(command):
463 463 def run(self):
464 464 if not check_submodule_status(repo_root) == 'clean':
465 465 print("submodules missing! Run `setup.py submodule` and try again")
466 466 sys.exit(1)
467 467 command.run(self)
468 468 return DecoratedCommand
This diff has been collapsed as it changes many lines, (665 lines changed) Show them Hide them
@@ -1,665 +0,0 b''
1 # -*- coding: utf-8 -*-
2 """Improved replacement for the Gnuplot.Gnuplot class.
3
4 This module imports Gnuplot and replaces some of its functionality with
5 improved versions. They add better handling of arrays for plotting and more
6 convenient PostScript generation, plus some fixes for hardcopy().
7
8 It also adds a convenient plot2 method for plotting dictionaries and
9 lists/tuples of arrays.
10
11 This module is meant to be used as a drop-in replacement to the original
12 Gnuplot, so it should be safe to do:
13
14 import IPython.Gnuplot2 as Gnuplot
15 """
16
17 import cStringIO
18 import os
19 import string
20 import sys
21 import tempfile
22 import time
23 import types
24
25 import Gnuplot as Gnuplot_ori
26 import Numeric
27
28 from IPython.utils.genutils import popkey,xsys
29
30 # needed by hardcopy():
31 gp = Gnuplot_ori.gp
32
33 # Patch for Gnuplot.py 1.6 compatibility.
34 # Thanks to Hayden Callow <h.callow@elec.canterbury.ac.nz>
35 try:
36 OptionException = Gnuplot_ori.PlotItems.OptionException
37 except AttributeError:
38 OptionException = Gnuplot_ori.Errors.OptionError
39
40 # exhibit a similar interface to Gnuplot so it can be somewhat drop-in
41 Data = Gnuplot_ori.Data
42 Func = Gnuplot_ori.Func
43 GridData = Gnuplot_ori.GridData
44 PlotItem = Gnuplot_ori.PlotItem
45 PlotItems = Gnuplot_ori.PlotItems
46
47 # Modify some of Gnuplot's functions with improved versions (or bugfixed, in
48 # hardcopy's case). In order to preserve the docstrings at runtime, I've
49 # copied them from the original code.
50
51 # After some significant changes in v 1.7 of Gnuplot.py, we need to do a bit
52 # of version checking.
53
54 if Gnuplot_ori.__version__ <= '1.6':
55 _BaseFileItem = PlotItems.File
56 _BaseTempFileItem = PlotItems.TempFile
57
58 # Fix the File class to add the 'index' option for Gnuplot versions < 1.7
59 class File(_BaseFileItem):
60
61 _option_list = _BaseFileItem._option_list.copy()
62 _option_list.update({
63 'index' : lambda self, index: self.set_option_index(index),
64 })
65
66 # A new initializer is needed b/c we want to add a modified
67 # _option_sequence list which includes 'index' in the right place.
68 def __init__(self,*args,**kw):
69 self._option_sequence = ['binary', 'index', 'using', 'smooth', 'axes',
70 'title', 'with']
71
72 _BaseFileItem.__init__(self,*args,**kw)
73
74 # Let's fix the constructor docstring
75 __newdoc = \
76 """Additional Keyword arguments added by IPython:
77
78 'index=<int>' -- similar to the `index` keyword in Gnuplot.
79 This allows only some of the datasets in a file to be
80 plotted. Datasets within a file are assumed to be separated
81 by _pairs_ of blank lines, and the first one is numbered as
82 0 (similar to C/Python usage)."""
83 __init__.__doc__ = PlotItems.File.__init__.__doc__ + __newdoc
84
85 def set_option_index(self, index):
86 if index is None:
87 self.clear_option('index')
88 elif type(index) in [type(''), type(1)]:
89 self._options['index'] = (index, 'index %s' % index)
90 elif type(index) is type(()):
91 self._options['index'] = (index,'index %s' %
92 string.join(map(repr, index), ':'))
93 else:
94 raise OptionException('index=%s' % (index,))
95
96 # We need a FileClass with a different name from 'File', which is a
97 # factory function in 1.7, so that our String class can subclass FileClass
98 # in any version.
99 _FileClass = File
100
101 elif Gnuplot_ori.__version__ =='1.7':
102 _FileClass = _BaseFileItem = PlotItems._FileItem
103 _BaseTempFileItem = PlotItems._TempFileItem
104 File = PlotItems.File
105
106 else: # changes in the newer version (svn as of March'06)
107 _FileClass = _BaseFileItem = PlotItems._FileItem
108 _BaseTempFileItem = PlotItems._NewFileItem
109 File = PlotItems.File
110
111
112 # Now, we can add our generic code which is version independent
113
114 # First some useful utilities
115 def eps_fix_bbox(fname):
116 """Fix the bounding box of an eps file by running ps2eps on it.
117
118 If its name ends in .eps, the original file is removed.
119
120 This is particularly useful for plots made by Gnuplot with square aspect
121 ratio: there is a bug in Gnuplot which makes it generate a bounding box
122 which is far wider than the actual plot.
123
124 This function assumes that ps2eps is installed in your system."""
125
126 # note: ps2ps and eps2eps do NOT work, ONLY ps2eps works correctly. The
127 # others make output with bitmapped fonts, which looks horrible.
128 print 'Fixing eps file: <%s>' % fname
129 xsys('ps2eps -f -q -l %s' % fname)
130 if fname.endswith('.eps'):
131 os.rename(fname+'.eps',fname)
132
133 def is_list1d(x,containers = [types.ListType,types.TupleType]):
134 """Returns true if x appears to be a 1d list/tuple/array.
135
136 The heuristics are: identify Numeric arrays, or lists/tuples whose first
137 element is not itself a list/tuple. This way zipped lists should work like
138 the original Gnuplot. There's no inexpensive way to know if a list doesn't
139 have a composite object after its first element, so that kind of input
140 will produce an error. But it should work well in most cases.
141 """
142 x_type = type(x)
143
144 return x_type == Numeric.ArrayType and len(x.shape)==1 or \
145 (x_type in containers and
146 type(x[0]) not in containers + [Numeric.ArrayType])
147
148 def zip_items(items,titles=None):
149 """zip together neighboring 1-d arrays, and zip standalone ones
150 with their index. Leave other plot items alone."""
151
152 class StandaloneItem(Exception): pass
153
154 def get_titles(titles):
155 """Return the next title and the input titles array.
156
157 The input array may be changed to None when no titles are left to
158 prevent extra unnecessary calls to this function."""
159
160 try:
161 title = titles[tit_ct[0]] # tit_ct[0] is in zip_items'scope
162 except IndexError:
163 titles = None # so we don't enter again
164 title = None
165 else:
166 tit_ct[0] += 1
167 return title,titles
168
169 new_items = []
170
171 if titles:
172 # Initialize counter. It was put in a list as a hack to allow the
173 # nested get_titles to modify it without raising a NameError.
174 tit_ct = [0]
175
176 n = 0 # this loop needs to be done by hand
177 while n < len(items):
178 item = items[n]
179 try:
180 if is_list1d(item):
181 if n==len(items)-1: # last in list
182 raise StandaloneItem
183 else: # check the next item and zip together if needed
184 next_item = items[n+1]
185 if next_item is None:
186 n += 1
187 raise StandaloneItem
188 elif is_list1d(next_item):
189 # this would be best done with an iterator
190 if titles:
191 title,titles = get_titles(titles)
192 else:
193 title = None
194 new_items.append(Data(zip(item,next_item),
195 title=title))
196 n += 1 # avoid double-inclusion of next item
197 else: # can't zip with next, zip with own index list
198 raise StandaloneItem
199 else: # not 1-d array
200 new_items.append(item)
201 except StandaloneItem:
202 if titles:
203 title,titles = get_titles(titles)
204 else:
205 title = None
206 new_items.append(Data(zip(range(len(item)),item),title=title))
207 except AttributeError:
208 new_items.append(item)
209 n+=1
210
211 return new_items
212
213 # And some classes with enhanced functionality.
214 class String(_FileClass):
215 """Make a PlotItem from data in a string with the same format as a File.
216
217 This allows writing data directly inside python scripts using the exact
218 same format and manipulation options which would be used for external
219 files."""
220
221 def __init__(self, data_str, **keyw):
222 """Construct a String object.
223
224 <data_str> is a string formatted exactly like a valid Gnuplot data
225 file would be. All options from the File constructor are valid here.
226
227 Warning: when used for interactive plotting in scripts which exit
228 immediately, you may get an error because the temporary file used to
229 hold the string data was deleted before Gnuplot had a chance to see
230 it. You can work around this problem by putting a raw_input() call at
231 the end of the script.
232
233 This problem does not appear when generating PostScript output, only
234 with Gnuplot windows."""
235
236 self.tmpfile = _BaseTempFileItem()
237 tmpfile = file(self.tmpfile.filename,'w')
238 tmpfile.write(data_str)
239 _BaseFileItem.__init__(self,self.tmpfile,**keyw)
240
241
242 class Gnuplot(Gnuplot_ori.Gnuplot):
243 """Improved Gnuplot class.
244
245 Enhancements: better plot,replot and hardcopy methods. New methods for
246 quick range setting.
247 """
248
249 def xrange(self,min='*',max='*'):
250 """Set xrange. If min/max is omitted, it is set to '*' (auto).
251
252 Note that this is different from the regular Gnuplot behavior, where
253 an unspecified limit means no change. Here any unspecified limit is
254 set to autoscaling, allowing these functions to be used for full
255 autoscaling when called with no arguments.
256
257 To preserve one limit's current value while changing the other, an
258 explicit '' argument must be given as the limit to be kept.
259
260 Similar functions exist for [y{2}z{2}rtuv]range."""
261
262 self('set xrange [%s:%s]' % (min,max))
263
264 def yrange(self,min='*',max='*'):
265 self('set yrange [%s:%s]' % (min,max))
266
267 def zrange(self,min='*',max='*'):
268 self('set zrange [%s:%s]' % (min,max))
269
270 def x2range(self,min='*',max='*'):
271 self('set xrange [%s:%s]' % (min,max))
272
273 def y2range(self,min='*',max='*'):
274 self('set yrange [%s:%s]' % (min,max))
275
276 def z2range(self,min='*',max='*'):
277 self('set zrange [%s:%s]' % (min,max))
278
279 def rrange(self,min='*',max='*'):
280 self('set rrange [%s:%s]' % (min,max))
281
282 def trange(self,min='*',max='*'):
283 self('set trange [%s:%s]' % (min,max))
284
285 def urange(self,min='*',max='*'):
286 self('set urange [%s:%s]' % (min,max))
287
288 def vrange(self,min='*',max='*'):
289 self('set vrange [%s:%s]' % (min,max))
290
291 def set_ps(self,option):
292 """Set an option for the PostScript terminal and reset default term."""
293
294 self('set terminal postscript %s ' % option)
295 self('set terminal %s' % gp.GnuplotOpts.default_term)
296
297 def __plot_ps(self, plot_method,*items, **keyw):
298 """Wrapper for plot/splot/replot, with processing of hardcopy options.
299
300 For internal use only."""
301
302 # Filter out PostScript options which will crash the normal plot/replot
303 psargs = {'filename':None,
304 'mode':None,
305 'eps':None,
306 'enhanced':None,
307 'color':None,
308 'solid':None,
309 'duplexing':None,
310 'fontname':None,
311 'fontsize':None,
312 'debug':0 }
313
314 for k in psargs.keys():
315 if keyw.has_key(k):
316 psargs[k] = keyw[k]
317 del keyw[k]
318
319 # Filter out other options the original plot doesn't know
320 hardcopy = popkey(keyw,'hardcopy',psargs['filename'] is not None)
321 titles = popkey(keyw,'titles',0)
322
323 # the filename keyword should control hardcopy generation, this is an
324 # override switch only which needs to be explicitly set to zero
325 if hardcopy:
326 if psargs['filename'] is None:
327 raise ValueError, \
328 'If you request hardcopy, you must give a filename.'
329
330 # set null output so nothing goes to screen. hardcopy() restores output
331 self('set term dumb')
332 # I don't know how to prevent screen output in Windows
333 if os.name == 'posix':
334 self('set output "/dev/null"')
335
336 new_items = zip_items(items,titles)
337 # plot_method is either plot or replot from the original Gnuplot class:
338 plot_method(self,*new_items,**keyw)
339
340 # Do hardcopy if requested
341 if hardcopy:
342 if psargs['filename'].endswith('.eps'):
343 psargs['eps'] = 1
344 self.hardcopy(**psargs)
345
346 def plot(self, *items, **keyw):
347 """Draw a new plot.
348
349 Clear the current plot and create a new 2-d plot containing
350 the specified items. Each arguments should be of the
351 following types:
352
353 'PlotItem' (e.g., 'Data', 'File', 'Func') -- This is the most
354 flexible way to call plot because the PlotItems can
355 contain suboptions. Moreover, PlotItems can be saved to
356 variables so that their lifetime is longer than one plot
357 command; thus they can be replotted with minimal overhead.
358
359 'string' (e.g., 'sin(x)') -- The string is interpreted as
360 'Func(string)' (a function that is computed by gnuplot).
361
362 Anything else -- The object, which should be convertible to an
363 array, is passed to the 'Data' constructor, and thus
364 plotted as data. If the conversion fails, an exception is
365 raised.
366
367
368 This is a modified version of plot(). Compared to the original in
369 Gnuplot.py, this version has several enhancements, listed below.
370
371
372 Modifications to the input arguments
373 ------------------------------------
374
375 (1-d array means Numeric array, list or tuple):
376
377 (i) Any 1-d array which is NOT followed by another 1-d array, is
378 automatically zipped with range(len(array_1d)). Typing g.plot(y) will
379 plot y against its indices.
380
381 (ii) If two 1-d arrays are contiguous in the argument list, they are
382 automatically zipped together. So g.plot(x,y) plots y vs. x, and
383 g.plot(x1,y1,x2,y2) plots y1 vs. x1 and y2 vs. x2.
384
385 (iii) Any 1-d array which is followed by None is automatically zipped
386 with range(len(array_1d)). In this form, typing g.plot(y1,None,y2)
387 will plot both y1 and y2 against their respective indices (and NOT
388 versus one another). The None prevents zipping y1 and y2 together, and
389 since y2 is unpaired it is automatically zipped to its indices by (i)
390
391 (iv) Any other arguments which don't match these cases are left alone and
392 passed to the code below.
393
394 For lists or tuples, the heuristics used to determine whether they are
395 in fact 1-d is fairly simplistic: their first element is checked, and
396 if it is not a list or tuple itself, it is assumed that the whole
397 object is one-dimensional.
398
399 An additional optional keyword 'titles' has been added: it must be a
400 list of strings to be used as labels for the individual plots which
401 are NOT PlotItem objects (since those objects carry their own labels
402 within).
403
404
405 PostScript generation
406 ---------------------
407
408 This version of plot() also handles automatically the production of
409 PostScript output. The main options are (given as keyword arguments):
410
411 - filename: a string, typically ending in .eps. If given, the plot is
412 sent to this file in PostScript format.
413
414 - hardcopy: this can be set to 0 to override 'filename'. It does not
415 need to be given to produce PostScript, its purpose is to allow
416 switching PostScript output off globally in scripts without having to
417 manually change 'filename' values in multiple calls.
418
419 All other keywords accepted by Gnuplot.hardcopy() are transparently
420 passed, and safely ignored if output is sent to the screen instead of
421 PostScript.
422
423 For example:
424
425 In [1]: x=frange(0,2*pi,npts=100)
426
427 Generate a plot in file 'sin.eps':
428
429 In [2]: plot(x,sin(x),filename = 'sin.eps')
430
431 Plot to screen instead, without having to change the filename:
432
433 In [3]: plot(x,sin(x),filename = 'sin.eps',hardcopy=0)
434
435 Pass the 'color=0' option to hardcopy for monochrome output:
436
437 In [4]: plot(x,sin(x),filename = 'sin.eps',color=0)
438
439 PostScript generation through plot() is useful mainly for scripting
440 uses where you are not interested in interactive plotting. For
441 interactive use, the hardcopy() function is typically more convenient:
442
443 In [5]: plot(x,sin(x))
444
445 In [6]: hardcopy('sin.eps') """
446
447 self.__plot_ps(Gnuplot_ori.Gnuplot.plot,*items,**keyw)
448
449 def plot2(self,arg,**kw):
450 """Plot the entries of a dictionary or a list/tuple of arrays.
451
452 This simple utility calls plot() with a list of Gnuplot.Data objects
453 constructed either from the values of the input dictionary, or the entries
454 in it if it is a tuple or list. Each item gets labeled with the key/index
455 in the Gnuplot legend.
456
457 Each item is plotted by zipping it with a list of its indices.
458
459 Any keywords are passed directly to plot()."""
460
461 if hasattr(arg,'keys'):
462 keys = arg.keys()
463 keys.sort()
464 else:
465 keys = range(len(arg))
466
467 pitems = [Data(zip(range(len(arg[k])),arg[k]),title=`k`) for k in keys]
468 self.plot(*pitems,**kw)
469
470 def splot(self, *items, **keyw):
471 """Draw a new three-dimensional plot.
472
473 Clear the current plot and create a new 3-d plot containing
474 the specified items. Arguments can be of the following types:
475
476 'PlotItem' (e.g., 'Data', 'File', 'Func', 'GridData' ) -- This
477 is the most flexible way to call plot because the
478 PlotItems can contain suboptions. Moreover, PlotItems can
479 be saved to variables so that their lifetime is longer
480 than one plot command--thus they can be replotted with
481 minimal overhead.
482
483 'string' (e.g., 'sin(x*y)') -- The string is interpreted as a
484 'Func()' (a function that is computed by gnuplot).
485
486 Anything else -- The object is converted to a Data() item, and
487 thus plotted as data. Note that each data point should
488 normally have at least three values associated with it
489 (i.e., x, y, and z). If the conversion fails, an
490 exception is raised.
491
492 This is a modified version of splot(). Compared to the original in
493 Gnuplot.py, this version has several enhancements, listed in the
494 plot() documentation.
495 """
496
497 self.__plot_ps(Gnuplot_ori.Gnuplot.splot,*items,**keyw)
498
499 def replot(self, *items, **keyw):
500 """Replot the data, possibly adding new 'PlotItem's.
501
502 Replot the existing graph, using the items in the current
503 itemlist. If arguments are specified, they are interpreted as
504 additional items to be plotted alongside the existing items on
505 the same graph. See 'plot' for details.
506
507 If you want to replot to a postscript file, you MUST give the
508 'filename' keyword argument in each call to replot. The Gnuplot python
509 interface has no way of knowing that your previous call to
510 Gnuplot.plot() was meant for PostScript output."""
511
512 self.__plot_ps(Gnuplot_ori.Gnuplot.replot,*items,**keyw)
513
514 # The original hardcopy has a bug. See fix at the end. The rest of the code
515 # was lifted verbatim from the original, so that people using IPython get the
516 # benefits without having to manually patch Gnuplot.py
517 def hardcopy(self, filename=None,
518 mode=None,
519 eps=None,
520 enhanced=None,
521 color=None,
522 solid=None,
523 duplexing=None,
524 fontname=None,
525 fontsize=None,
526 debug = 0,
527 ):
528 """Create a hardcopy of the current plot.
529
530 Create a postscript hardcopy of the current plot to the
531 default printer (if configured) or to the specified filename.
532
533 Note that gnuplot remembers the postscript suboptions across
534 terminal changes. Therefore if you set, for example, color=1
535 for one hardcopy then the next hardcopy will also be color
536 unless you explicitly choose color=0. Alternately you can
537 force all of the options to their defaults by setting
538 mode='default'. I consider this to be a bug in gnuplot.
539
540 Keyword arguments:
541
542 'filename=<string>' -- if a filename is specified, save the
543 output in that file; otherwise print it immediately
544 using the 'default_lpr' configuration option. If the
545 filename ends in '.eps', EPS mode is automatically
546 selected (like manually specifying eps=1 or mode='eps').
547
548 'mode=<string>' -- set the postscript submode ('landscape',
549 'portrait', 'eps', or 'default'). The default is
550 to leave this option unspecified.
551
552 'eps=<bool>' -- shorthand for 'mode="eps"'; asks gnuplot to
553 generate encapsulated postscript.
554
555 'enhanced=<bool>' -- if set (the default), then generate
556 enhanced postscript, which allows extra features like
557 font-switching, superscripts, and subscripts in axis
558 labels. (Some old gnuplot versions do not support
559 enhanced postscript; if this is the case set
560 gp.GnuplotOpts.prefer_enhanced_postscript=None.)
561
562 'color=<bool>' -- if set, create a plot with color. Default
563 is to leave this option unchanged.
564
565 'solid=<bool>' -- if set, force lines to be solid (i.e., not
566 dashed).
567
568 'duplexing=<string>' -- set duplexing option ('defaultplex',
569 'simplex', or 'duplex'). Only request double-sided
570 printing if your printer can handle it. Actually this
571 option is probably meaningless since hardcopy() can only
572 print a single plot at a time.
573
574 'fontname=<string>' -- set the default font to <string>,
575 which must be a valid postscript font. The default is
576 to leave this option unspecified.
577
578 'fontsize=<double>' -- set the default font size, in
579 postscript points.
580
581 'debug=<bool>' -- print extra debugging information (useful if
582 your PostScript files are misteriously not being created).
583 """
584
585 if filename is None:
586 assert gp.GnuplotOpts.default_lpr is not None, \
587 OptionException('default_lpr is not set, so you can only '
588 'print to a file.')
589 filename = gp.GnuplotOpts.default_lpr
590 lpr_output = 1
591 else:
592 if filename.endswith('.eps'):
593 eps = 1
594 lpr_output = 0
595
596 # Be careful processing the options. If the user didn't
597 # request an option explicitly, do not specify it on the 'set
598 # terminal' line (don't even specify the default value for the
599 # option). This is to avoid confusing older versions of
600 # gnuplot that do not support all of these options. The
601 # exception is 'enhanced', which is just too useful to have to
602 # specify each time!
603
604 setterm = ['set', 'terminal', 'postscript']
605 if eps:
606 assert mode is None or mode=='eps', \
607 OptionException('eps option and mode are incompatible')
608 setterm.append('eps')
609 else:
610 if mode is not None:
611 assert mode in ['landscape', 'portrait', 'eps', 'default'], \
612 OptionException('illegal mode "%s"' % mode)
613 setterm.append(mode)
614 if enhanced is None:
615 enhanced = gp.GnuplotOpts.prefer_enhanced_postscript
616 if enhanced is not None:
617 if enhanced: setterm.append('enhanced')
618 else: setterm.append('noenhanced')
619 if color is not None:
620 if color: setterm.append('color')
621 else: setterm.append('monochrome')
622 if solid is not None:
623 if solid: setterm.append('solid')
624 else: setterm.append('dashed')
625 if duplexing is not None:
626 assert duplexing in ['defaultplex', 'simplex', 'duplex'], \
627 OptionException('illegal duplexing mode "%s"' % duplexing)
628 setterm.append(duplexing)
629 if fontname is not None:
630 setterm.append('"%s"' % fontname)
631 if fontsize is not None:
632 setterm.append('%s' % fontsize)
633
634 self(string.join(setterm))
635 self.set_string('output', filename)
636 # replot the current figure (to the printer):
637 self.refresh()
638
639 # fperez. Ugly kludge: often for some reason the file is NOT created
640 # and we must reissue the creation commands. I have no idea why!
641 if not lpr_output:
642 #print 'Hardcopy <%s>' % filename # dbg
643 maxtries = 20
644 delay = 0.1 # delay (in seconds) between print attempts
645 for i in range(maxtries):
646 time.sleep(0.05) # safety, very small delay
647 if os.path.isfile(filename):
648 if debug:
649 print 'Hardcopy to file <%s> success at attempt #%s.' \
650 % (filename,i+1)
651 break
652 time.sleep(delay)
653 # try again, issue all commands just in case
654 self(string.join(setterm))
655 self.set_string('output', filename)
656 self.refresh()
657 if not os.path.isfile(filename):
658 print >> sys.stderr,'ERROR: Tried %s times and failed to '\
659 'create hardcopy file `%s`' % (maxtries,filename)
660
661 # reset the terminal to its `default' setting:
662 self('set terminal %s' % gp.GnuplotOpts.default_term)
663 self.set_string('output')
664
665 #********************** End of file <Gnuplot2.py> ************************
@@ -1,148 +0,0 b''
1 # -*- coding: utf-8 -*-
2 """Interactive functions and magic functions for Gnuplot usage.
3
4 This requires the Gnuplot.py module for interfacing python with Gnuplot, which
5 can be downloaded from:
6
7 http://gnuplot-py.sourceforge.net/
8
9 See gphelp() below for details on the services offered by this module.
10
11 Inspired by a suggestion/request from Arnd Baecker.
12 """
13
14 __all__ = ['Gnuplot','gp','gp_new','plot','plot2','splot','replot',
15 'hardcopy','gpdata','gpfile','gpstring','gpfunc','gpgrid',
16 'gphelp']
17
18 import IPython.GnuplotRuntime as GRun
19 from IPython.utils.genutils import warn
20 from IPython.core import page
21
22 # Set global names for interactive use
23 Gnuplot = GRun.Gnuplot
24 gp_new = GRun.gp_new
25 gp = GRun.gp
26 plot = gp.plot
27 plot2 = gp.plot2
28 splot = gp.splot
29 replot = gp.replot
30 hardcopy = gp.hardcopy
31
32 # Accessors for the main plot object constructors:
33 gpdata = Gnuplot.Data
34 gpfile = Gnuplot.File
35 gpstring = Gnuplot.String
36 gpfunc = Gnuplot.Func
37 gpgrid = Gnuplot.GridData
38
39 def gphelp():
40 """Print information about the Gnuplot facilities in IPython."""
41
42 page("""
43 IPython provides an interface to access the Gnuplot scientific plotting
44 system, in an environment similar to that of Mathematica or Matlab.
45
46 New top-level global objects
47 ----------------------------
48
49 Please see their respective docstrings for further details.
50
51 - gp: a running Gnuplot instance. You can access its methods as
52 gp.<method>. gp(`a string`) will execute the given string as if it had been
53 typed in an interactive gnuplot window.
54
55 - plot, splot, replot and hardcopy: aliases to the methods of the same name in
56 the global running Gnuplot instance gp. These allow you to simply type:
57
58 In [1]: plot(x,sin(x),title='Sin(x)') # assuming x is a Numeric array
59
60 and obtain a plot of sin(x) vs x with the title 'Sin(x)'.
61
62 - gp_new: a function which returns a new Gnuplot instance. This can be used to
63 have multiple Gnuplot instances running in your session to compare different
64 plots, each in a separate window.
65
66 - Gnuplot: alias to the Gnuplot2 module, an improved drop-in replacement for
67 the original Gnuplot.py. Gnuplot2 needs Gnuplot but redefines several of its
68 functions with improved versions (Gnuplot2 comes with IPython).
69
70 - gpdata, gpfile, gpstring, gpfunc, gpgrid: aliases to Gnuplot.Data,
71 Gnuplot.File, Gnuplot.String, Gnuplot.Func and Gnuplot.GridData
72 respectively. These functions create objects which can then be passed to the
73 plotting commands. See the Gnuplot.py documentation for details.
74
75 Keep in mind that all commands passed to a Gnuplot instance are executed in
76 the Gnuplot namespace, where no Python variables exist. For example, for
77 plotting sin(x) vs x as above, typing
78
79 In [2]: gp('plot x,sin(x)')
80
81 would not work. Instead, you would get the plot of BOTH the functions 'x' and
82 'sin(x)', since Gnuplot doesn't know about the 'x' Python array. The plot()
83 method lives in python and does know about these variables.
84
85
86 New magic functions
87 -------------------
88
89 %gpc: pass one command to Gnuplot and execute it or open a Gnuplot shell where
90 each line of input is executed.
91
92 %gp_set_default: reset the value of IPython's global Gnuplot instance.""")
93
94 # Code below is all for IPython use
95 # Define the magic functions for communicating with the above gnuplot instance.
96 def magic_gpc(self,parameter_s=''):
97 """Execute a gnuplot command or open a gnuplot shell.
98
99 Usage (omit the % if automagic is on). There are two ways to use it:
100
101 1) %gpc 'command' -> passes 'command' directly to the gnuplot instance.
102
103 2) %gpc -> will open up a prompt (gnuplot>>>) which takes input like the
104 standard gnuplot interactive prompt. If you need to type a multi-line
105 command, use \\ at the end of each intermediate line.
106
107 Upon exiting of the gnuplot sub-shell, you return to your IPython
108 session (the gnuplot sub-shell can be invoked as many times as needed).
109 """
110
111 if parameter_s.strip():
112 self.shell.gnuplot(parameter_s)
113 else:
114 self.shell.gnuplot.interact()
115
116 def magic_gp_set_default(self,parameter_s=''):
117 """Set the default gnuplot instance accessed by the %gp magic function.
118
119 %gp_set_default name
120
121 Call with the name of the new instance at the command line. If you want to
122 set this instance in your own code (using an embedded IPython, for
123 example), simply set the variable __IPYTHON__.gnuplot to your own gnuplot
124 instance object."""
125
126 gname = parameter_s.strip()
127 G = eval(gname,self.shell.user_ns)
128 self.shell.gnuplot = G
129 self.shell.user_ns.update({'plot':G.plot,'splot':G.splot,'plot2':G.plot2,
130 'replot':G.replot,'hardcopy':G.hardcopy})
131
132 try:
133 __IPYTHON__
134 except NameError:
135 pass
136 else:
137 # make the global Gnuplot instance known to IPython
138 __IPYTHON__.gnuplot = GRun.gp
139 __IPYTHON__.gnuplot.shell_first_time = 1
140
141 print """*** Type `gphelp` for help on the Gnuplot integration features."""
142
143 # Add the new magic functions to the class dict
144 from IPython.core.iplib import InteractiveShell
145 InteractiveShell.magic_gpc = magic_gpc
146 InteractiveShell.magic_gp_set_default = magic_gp_set_default
147
148 #********************** End of file <GnuplotInteractive.py> *******************
@@ -1,146 +0,0 b''
1 # -*- coding: utf-8 -*-
2 """Basic Gnuplot functionality for inclusion in other code.
3
4 This module creates a running Gnuplot instance called 'gp' and builds other
5 convenient globals for quick use in running scripts. It is intended to allow
6 you to script plotting tasks in Python with a minimum of effort. A typical
7 usage would be:
8
9 import IPython.GnuplotRuntime as GP # or some other short name
10 GP.gp.plot(GP.File('your_data.dat'))
11
12
13 This module exposes the following objects:
14
15 - gp: a running Gnuplot instance. You can access its methods as
16 gp.<method>. gp(`a string`) will execute the given string as if it had been
17 typed in an interactive gnuplot window.
18
19 - gp_new: a function which returns a new Gnuplot instance. This can be used to
20 have multiple Gnuplot instances running in your session to compare different
21 plots.
22
23 - Gnuplot: alias to the Gnuplot2 module, an improved drop-in replacement for
24 the original Gnuplot.py. Gnuplot2 needs Gnuplot but redefines several of its
25 functions with improved versions (Gnuplot2 comes with IPython).
26
27 - Data: alias to Gnuplot.Data, makes a PlotItem from array data.
28
29 - File: alias to Gnuplot.File, makes a PlotItem from a file.
30
31 - String: alias to Gnuplot.String, makes a PlotItem from a string formatted
32 exactly like a file for Gnuplot.File would be.
33
34 - Func: alias to Gnuplot.Func, makes a PlotItem from a function string.
35
36 - GridData: alias to Gnuplot.GridData, makes a PlotItem from grid data.
37
38 - pm3d_config: a string with Gnuplot commands to set up the pm3d mode for
39 surface plotting. You can activate it simply by calling gp(pm3d_config).
40
41 - eps_fix_bbox: A Unix-only function to fix eps files with bad bounding boxes
42 (which Gnuplot generates when the plot size is set to square).
43
44 This requires the Gnuplot.py module for interfacing Python with Gnuplot, which
45 can be downloaded from:
46
47 http://gnuplot-py.sourceforge.net/
48
49 Inspired by a suggestion/request from Arnd Baecker.
50 """
51
52 __all__ = ['Gnuplot','gp','gp_new','Data','File','Func','GridData',
53 'pm3d_config','eps_fix_bbox']
54
55 import os,tempfile,sys
56 from IPython.utils.process import getoutput
57
58 #---------------------------------------------------------------------------
59 # Notes on mouse support for Gnuplot.py
60
61 # If you do not have a mouse-enabled gnuplot, set gnuplot_mouse to 0. If you
62 # use gnuplot, you should really grab a recent, mouse enabled copy. It is an
63 # extremely useful feature. Mouse support is official as of gnuplot 4.0,
64 # released in April 2004.
65
66 # For the mouse features to work correctly, you MUST set your Gnuplot.py
67 # module to use temporary files instead of 'inline data' for data
68 # communication. Note that this is the default, so unless you've manually
69 # fiddled with it you should be ok. If you need to make changes, in the
70 # Gnuplot module directory, loook for the gp_unix.py file and make sure the
71 # prefer_inline_data variable is set to 0. If you set it to 1 Gnuplot.py will
72 # try to pass the data to gnuplot via standard input, which completely
73 # confuses the mouse control system (even though it may be a bit faster than
74 # using temp files).
75
76 # As of Gnuplot.py v1.7, a new option was added to use FIFOs (pipes). This
77 # mechanism, while fast, also breaks the mouse system. You must therefore set
78 # the variable prefer_fifo_data to 0 in gp_unix.py.
79
80 tmpname = tempfile.mktemp()
81 open(tmpname,'w').write('set mouse')
82 gnu_out = getoutput('gnuplot '+ tmpname)
83 os.unlink(tmpname)
84 if gnu_out: # Gnuplot won't print anything if it has mouse support
85 print "*** Your version of Gnuplot appears not to have mouse support."
86 gnuplot_mouse = 0
87 else:
88 gnuplot_mouse = 1
89 del tmpname,gnu_out
90
91 # Default state for persistence of new gnuplot instances
92 if os.name in ['nt','dos'] or sys.platform == 'cygwin':
93 gnuplot_persist = 0
94 else:
95 gnuplot_persist = 1
96
97 import IPython.Gnuplot2 as Gnuplot
98
99 class NotGiven: pass
100
101 def gp_new(mouse=NotGiven,persist=NotGiven):
102 """Return a new Gnuplot instance.
103
104 The instance returned uses the improved methods defined in Gnuplot2.
105
106 Options (boolean):
107
108 - mouse: if unspecified, the module global gnuplot_mouse is used.
109
110 - persist: if unspecified, the module global gnuplot_persist is used."""
111
112 if mouse is NotGiven:
113 mouse = gnuplot_mouse
114 if persist is NotGiven:
115 persist = gnuplot_persist
116 g = Gnuplot.Gnuplot(persist=persist)
117 if mouse:
118 g('set mouse')
119 return g
120
121 # Global-level names.
122
123 # A global Gnuplot instance for interactive use:
124 gp = gp_new()
125
126 # Accessors for the main plot object constructors:
127 Data = Gnuplot.Data
128 File = Gnuplot.File
129 Func = Gnuplot.Func
130 String = Gnuplot.String
131 GridData = Gnuplot.GridData
132
133 # A Unix-only function to fix eps files with bad bounding boxes (which Gnuplot
134 # generates when the plot size is set to square):
135 eps_fix_bbox = Gnuplot.eps_fix_bbox
136
137 # String for configuring pm3d. Simply call g(pm3d_config) to execute it. pm3d
138 # is a very nice mode for plotting colormaps on surfaces. Modify the defaults
139 # below to suit your taste.
140 pm3d_config = """
141 set pm3d solid
142 set hidden3d
143 unset surface
144 set isosamples 50
145 """
146 #******************** End of file <GnuplotRuntime.py> ******************
@@ -1,84 +0,0 b''
1 # -*- coding: utf-8 -*-
2 """Modified input prompt for entering quantities with units.
3
4 Modify the behavior of the interactive interpreter to allow direct input of
5 quantities with units without having to make a function call.
6
7 Now the following forms are accepted:
8
9 x = 4 m
10 y = -.45e3 m/s
11 g = 9.8 m/s**2
12 a = 2.3 m/s^2 # ^ -> ** automatically
13
14 All other input is processed normally.
15
16 Authors
17 -------
18 - Fernando Perez <Fernando.Perez@berkeley.edu>
19 """
20 #*****************************************************************************
21 # Copyright (C) 2008-2011 The IPython Development Team
22 # Copyright (C) 2001-2007 Fernando Perez <fperez@colorado.edu>
23 #
24 # Distributed under the terms of the BSD License. The full license is in
25 # the file COPYING, distributed as part of this software.
26 #*****************************************************************************
27
28 # This file is an example of how to modify IPython's line-processing behavior
29 # without touching the internal code. We'll define an alternate pre-processing
30 # stage which allows a special form of input (which is invalid Python syntax)
31 # for certain quantities, rewrites a line of proper Python in those cases, and
32 # then passes it off to IPython's normal processor for further work.
33
34 # With this kind of customization, IPython can be adapted for many
35 # special-purpose scenarios providing alternate input syntaxes.
36
37 # This file can be imported like a regular module.
38
39 # IPython has a prefilter() function that analyzes each input line. We redefine
40 # it here to first pre-process certain forms of input
41
42 # The prototype of any alternate prefilter must be like this one (the name
43 # doesn't matter):
44 # - line is a string containing the user input line.
45 # - continuation is a parameter which tells us if we are processing a first line of
46 # user input or the second or higher of a multi-line statement.
47
48 def prefilter_PQ(self,line,continuation):
49 """Alternate prefilter for input of PhysicalQuantityInteractive objects.
50
51 This assumes that the function PhysicalQuantityInteractive() has been
52 imported."""
53
54 from re import match
55 from IPython.core.iplib import InteractiveShell
56
57 # This regexp is what does the real work
58 unit_split = match(r'\s*(\w+)\s*=\s*(-?\d*\.?\d*[eE]?-?\d*)\s+([a-zA-Z].*)',
59 line)
60
61 # If special input was ecnountered, process it:
62 if unit_split:
63 var,val,units = unit_split.groups()
64 if var and val and units:
65 units = units.replace('^','**')
66 # Now a valid line needs to be constructed for IPython to process:
67 line = var +" = PhysicalQuantityInteractive(" + val + ", '" + \
68 units + "')"
69 #print 'New line:',line # dbg
70
71 # In the end, always call the default IPython _prefilter() function. Note
72 # that self must be passed explicitly, b/c we're calling the unbound class
73 # method (since this method will overwrite the instance prefilter())
74 return InteractiveShell._prefilter(self,line,continuation)
75
76 # Rebind this to be the new IPython prefilter:
77 from IPython.core.iplib import InteractiveShell
78 InteractiveShell.prefilter = prefilter_PQ
79
80 # Clean up the namespace.
81 del InteractiveShell,prefilter_PQ
82
83 # Just a heads up at the console
84 print '*** Simplified input for physical quantities enabled.'
@@ -1,90 +0,0 b''
1 # -*- coding: utf-8 -*-
2 """Modify the PhysicalQuantities class for more convenient interactive use.
3
4 Also redefine some math functions to operate on PhysQties with no need for
5 special method syntax. This just means moving them out to the global
6 namespace.
7
8 This module should always be loaded *after* math or Numeric, so it can
9 overwrite math functions with the versions that handle units.
10
11 Authors
12 -------
13 - Fernando Perez <Fernando.Perez@berkeley.edu>
14 """
15
16 #*****************************************************************************
17 # Copyright (C) 2008-2011 The IPython Development Team
18 # Copyright (C) 2001-2007 Fernando Perez <fperez@colorado.edu>
19 #
20 # Distributed under the terms of the BSD License. The full license is in
21 # the file COPYING, distributed as part of this software.
22 #*****************************************************************************
23
24 from Scientific.Physics.PhysicalQuantities import PhysicalQuantity
25
26 # This code can be set up to work with Numeric or with math for providing the
27 # mathematical functions. Uncomment the one you prefer to use below.
28
29 # If you use math, sin(x) won't work for x an array, only float or PhysQty
30 import math
31
32 # If you use Numeric, sin(x) works for x a float, PhysQty an array.
33 #import Numeric as math
34
35 class PhysicalQuantityFunction:
36 """Generic function wrapper for PhysicalQuantity instances.
37
38 Calls functions from either the math library or the instance's methods as
39 required. Allows using sin(theta) or sqrt(v**2) syntax irrespective of
40 whether theta is a pure number or a PhysicalQuantity.
41
42 This is *slow*. It's meant for convenient interactive use, not for
43 speed."""
44
45 def __init__(self,name):
46 self.name = name
47
48 def __call__(self,x):
49 if isinstance(x,PhysicalQuantity):
50 return PhysicalQuantity.__dict__[self.name](x)
51 else:
52 return math.__dict__[self.name](x)
53
54 class PhysicalQuantityInteractive(PhysicalQuantity):
55 """Physical quantity with units - modified for Interactive use.
56
57 Basically, the __str__ and __repr__ methods have been swapped for more
58 convenient interactive use. Powers are shown as ^ instead of ** and only 4
59 significant figures are shown.
60
61 Also adds the following aliases for commonly used methods:
62 b = PhysicalQuantity.inBaseUnits
63 u = PhysicalQuantity.inUnitsOf
64
65 These are useful when doing a lot of interactive calculations.
66 """
67
68 # shorthands for the most useful unit conversions
69 b = PhysicalQuantity.inBaseUnits # so you can just type x.b to get base units
70 u = PhysicalQuantity.inUnitsOf
71
72 # This can be done, but it can get dangerous when coupled with IPython's
73 # auto-calling. Everything ends up shown in baseunits and things like x*2
74 # get automatically converted to k(*2), which doesn't work.
75 # Probably not a good idea in general...
76 #__call__ = b
77
78 def __str__(self):
79 return PhysicalQuantity.__repr__(self)
80
81 def __repr__(self):
82 value = '%.4G' % self.value
83 units = self.unit.name().replace('**','^')
84 return value + ' ' + units
85
86 # implement the methods defined in PhysicalQuantity as PhysicalQuantityFunctions
87 sin = PhysicalQuantityFunction('sin')
88 cos = PhysicalQuantityFunction('cos')
89 tan = PhysicalQuantityFunction('tan')
90 sqrt = PhysicalQuantityFunction('sqrt')
@@ -1,42 +0,0 b''
1 #!/usr/bin/env python
2 # encoding: utf-8
3 """
4 A backwards compatibility layer for IPython.Shell.
5
6 Previously, IPython had an IPython.Shell module. IPython.Shell has been moved
7 to IPython.core.shell and is being refactored. This new module is provided
8 for backwards compatability. We strongly encourage everyone to start using
9 the new code in IPython.core.shell.
10 """
11
12 #-----------------------------------------------------------------------------
13 # Copyright (C) 2008-2011 The IPython Development Team
14 #
15 # Distributed under the terms of the BSD License. The full license is in
16 # the file COPYING, distributed as part of this software.
17 #-----------------------------------------------------------------------------
18
19 from warnings import warn
20
21 msg = """
22 This module (IPython.Shell) is deprecated. The classes that were in this
23 module have been replaced by:
24
25 IPShell->IPython.core.iplib.InteractiveShell
26 IPShellEmbed->IPython.core.embed.InteractiveShellEmbed
27
28 Please migrate your code to use these classes instead.
29 """
30
31 warn(msg, category=DeprecationWarning, stacklevel=1)
32
33 from IPython.core.iplib import InteractiveShell as IPShell
34 from IPython.core.embed import InteractiveShellEmbed as IPShellEmbed
35
36 def start(user_ns=None, embedded=False):
37 """Return an instance of :class:`InteractiveShell`."""
38 if embedded:
39 return IPShellEmbed(user_ns=user_ns)
40 else:
41 return IPShell(user_ns=user_ns)
42
@@ -1,400 +0,0 b''
1 """
2 ``astyle`` provides classes for adding style (foreground and background color;
3 bold; blink; etc.) to terminal and curses output.
4 """
5
6
7 import sys, os
8
9 try:
10 import curses
11 except ImportError:
12 curses = None
13
14
15 COLOR_BLACK = 0
16 COLOR_RED = 1
17 COLOR_GREEN = 2
18 COLOR_YELLOW = 3
19 COLOR_BLUE = 4
20 COLOR_MAGENTA = 5
21 COLOR_CYAN = 6
22 COLOR_WHITE = 7
23
24 A_BLINK = 1<<0 # Blinking text
25 A_BOLD = 1<<1 # Extra bright or bold text
26 A_DIM = 1<<2 # Half bright text
27 A_REVERSE = 1<<3 # Reverse-video text
28 A_STANDOUT = 1<<4 # The best highlighting mode available
29 A_UNDERLINE = 1<<5 # Underlined text
30
31
32 class Style(object):
33 """
34 Store foreground color, background color and attribute (bold, underlined
35 etc.).
36 """
37 __slots__ = ("fg", "bg", "attrs")
38
39 COLORNAMES = {
40 "black": COLOR_BLACK,
41 "red": COLOR_RED,
42 "green": COLOR_GREEN,
43 "yellow": COLOR_YELLOW,
44 "blue": COLOR_BLUE,
45 "magenta": COLOR_MAGENTA,
46 "cyan": COLOR_CYAN,
47 "white": COLOR_WHITE,
48 }
49 ATTRNAMES = {
50 "blink": A_BLINK,
51 "bold": A_BOLD,
52 "dim": A_DIM,
53 "reverse": A_REVERSE,
54 "standout": A_STANDOUT,
55 "underline": A_UNDERLINE,
56 }
57
58 def __init__(self, fg, bg, attrs=0):
59 """
60 Create a ``Style`` object with ``fg`` as the foreground color,
61 ``bg`` as the background color and ``attrs`` as the attributes.
62
63 Examples:
64 >>> Style(COLOR_RED, COLOR_BLACK)
65 <Style fg=red bg=black attrs=0>
66
67 >>> Style(COLOR_YELLOW, COLOR_BLUE, A_BOLD|A_UNDERLINE)
68 <Style fg=yellow bg=blue attrs=bold|underline>
69 """
70 self.fg = fg
71 self.bg = bg
72 self.attrs = attrs
73
74 def __call__(self, *args):
75 text = Text()
76 for arg in args:
77 if isinstance(arg, Text):
78 text.extend(arg)
79 else:
80 text.append((self, arg))
81 return text
82
83 def __eq__(self, other):
84 return self.fg == other.fg and self.bg == other.bg and self.attrs == other.attrs
85
86 def __neq__(self, other):
87 return self.fg != other.fg or self.bg != other.bg or self.attrs != other.attrs
88
89 def __repr__(self):
90 color2name = ("black", "red", "green", "yellow", "blue", "magenta", "cyan", "white")
91 attrs2name = ("blink", "bold", "dim", "reverse", "standout", "underline")
92
93 return "<%s fg=%s bg=%s attrs=%s>" % (
94 self.__class__.__name__, color2name[self.fg], color2name[self.bg],
95 "|".join([attrs2name[b] for b in xrange(6) if self.attrs&(1<<b)]) or 0)
96
97 def fromstr(cls, value):
98 """
99 Create a ``Style`` object from a string. The format looks like this:
100 ``"red:black:bold|blink"``.
101 """
102 # defaults
103 fg = COLOR_WHITE
104 bg = COLOR_BLACK
105 attrs = 0
106
107 parts = value.split(":")
108 if len(parts) > 0:
109 fg = cls.COLORNAMES[parts[0].lower()]
110 if len(parts) > 1:
111 bg = cls.COLORNAMES[parts[1].lower()]
112 if len(parts) > 2:
113 for strattr in parts[2].split("|"):
114 attrs |= cls.ATTRNAMES[strattr.lower()]
115 return cls(fg, bg, attrs)
116 fromstr = classmethod(fromstr)
117
118 def fromenv(cls, name, default):
119 """
120 Create a ``Style`` from an environment variable named ``name``
121 (using ``default`` if the environment variable doesn't exist).
122 """
123 return cls.fromstr(os.environ.get(name, default))
124 fromenv = classmethod(fromenv)
125
126
127 def switchstyle(s1, s2):
128 """
129 Return the ANSI escape sequence needed to switch from style ``s1`` to
130 style ``s2``.
131 """
132 attrmask = (A_BLINK|A_BOLD|A_UNDERLINE|A_REVERSE)
133 a1 = s1.attrs & attrmask
134 a2 = s2.attrs & attrmask
135
136 args = []
137 if s1 != s2:
138 # do we have to get rid of the bold/underline/blink bit?
139 # (can only be done by a reset)
140 # use reset when our target color is the default color
141 # (this is shorter than 37;40)
142 if (a1 & ~a2 or s2==style_default):
143 args.append("0")
144 s1 = style_default
145 a1 = 0
146
147 # now we know that old and new color have the same boldness,
148 # or the new color is bold and the old isn't,
149 # i.e. we only might have to switch bold on, not off
150 if not (a1 & A_BOLD) and (a2 & A_BOLD):
151 args.append("1")
152
153 # Fix underline
154 if not (a1 & A_UNDERLINE) and (a2 & A_UNDERLINE):
155 args.append("4")
156
157 # Fix blink
158 if not (a1 & A_BLINK) and (a2 & A_BLINK):
159 args.append("5")
160
161 # Fix reverse
162 if not (a1 & A_REVERSE) and (a2 & A_REVERSE):
163 args.append("7")
164
165 # Fix foreground color
166 if s1.fg != s2.fg:
167 args.append("3%d" % s2.fg)
168
169 # Finally fix the background color
170 if s1.bg != s2.bg:
171 args.append("4%d" % s2.bg)
172
173 if args:
174 return "\033[%sm" % ";".join(args)
175 return ""
176
177
178 class Text(list):
179 """
180 A colored string. A ``Text`` object is a sequence, the sequence
181 items will be ``(style, string)`` tuples.
182 """
183
184 def __init__(self, *args):
185 list.__init__(self)
186 self.append(*args)
187
188 def __repr__(self):
189 return "%s.%s(%s)" % (
190 self.__class__.__module__, self.__class__.__name__,
191 list.__repr__(self)[1:-1])
192
193 def append(self, *args):
194 for arg in args:
195 if isinstance(arg, Text):
196 self.extend(arg)
197 elif isinstance(arg, tuple): # must be (style, string)
198 list.append(self, arg)
199 elif isinstance(arg, unicode):
200 list.append(self, (style_default, arg))
201 else:
202 list.append(self, (style_default, str(arg)))
203
204 def insert(self, index, *args):
205 self[index:index] = Text(*args)
206
207 def __add__(self, other):
208 new = Text()
209 new.append(self)
210 new.append(other)
211 return new
212
213 def __iadd__(self, other):
214 self.append(other)
215 return self
216
217 def format(self, styled=True):
218 """
219 This generator yields the strings that will make up the final
220 colorized string.
221 """
222 if styled:
223 oldstyle = style_default
224 for (style, string) in self:
225 if not isinstance(style, (int, long)):
226 switch = switchstyle(oldstyle, style)
227 if switch:
228 yield switch
229 if string:
230 yield string
231 oldstyle = style
232 switch = switchstyle(oldstyle, style_default)
233 if switch:
234 yield switch
235 else:
236 for (style, string) in self:
237 if not isinstance(style, (int, long)):
238 yield string
239
240 def string(self, styled=True):
241 """
242 Return the resulting string (with escape sequences, if ``styled``
243 is true).
244 """
245 return "".join(self.format(styled))
246
247 def __str__(self):
248 """
249 Return ``self`` as a string (without ANSI escape sequences).
250 """
251 return self.string(False)
252
253 def write(self, stream, styled=True):
254 """
255 Write ``self`` to the output stream ``stream`` (with escape sequences,
256 if ``styled`` is true).
257 """
258 for part in self.format(styled):
259 stream.write(part)
260
261
262 try:
263 import ipipe
264 except ImportError:
265 pass
266 else:
267 def xrepr_astyle_text(self, mode="default"):
268 yield (-1, True)
269 for info in self:
270 yield info
271 ipipe.xrepr.when_type(Text)(xrepr_astyle_text)
272
273
274 def streamstyle(stream, styled=None):
275 """
276 If ``styled`` is ``None``, return whether ``stream`` refers to a terminal.
277 If this can't be determined (either because ``stream`` doesn't refer to a
278 real OS file, or because you're on Windows) return ``False``. If ``styled``
279 is not ``None`` ``styled`` will be returned unchanged.
280 """
281 if styled is None:
282 try:
283 styled = os.isatty(stream.fileno())
284 except (KeyboardInterrupt, SystemExit):
285 raise
286 except Exception:
287 styled = False
288 return styled
289
290
291 def write(stream, styled, *texts):
292 """
293 Write ``texts`` to ``stream``.
294 """
295 text = Text(*texts)
296 text.write(stream, streamstyle(stream, styled))
297
298
299 def writeln(stream, styled, *texts):
300 """
301 Write ``texts`` to ``stream`` and finish with a line feed.
302 """
303 write(stream, styled, *texts)
304 stream.write("\n")
305
306
307 class Stream(object):
308 """
309 Stream wrapper that adds color output.
310 """
311 def __init__(self, stream, styled=None):
312 self.stream = stream
313 self.styled = streamstyle(stream, styled)
314
315 def write(self, *texts):
316 write(self.stream, self.styled, *texts)
317
318 def writeln(self, *texts):
319 writeln(self.stream, self.styled, *texts)
320
321 def __getattr__(self, name):
322 return getattr(self.stream, name)
323
324
325 class stdout(object):
326 """
327 Stream wrapper for ``sys.stdout`` that adds color output.
328 """
329 def write(self, *texts):
330 write(sys.stdout, None, *texts)
331
332 def writeln(self, *texts):
333 writeln(sys.stdout, None, *texts)
334
335 def __getattr__(self, name):
336 return getattr(sys.stdout, name)
337 stdout = stdout()
338
339
340 class stderr(object):
341 """
342 Stream wrapper for ``sys.stderr`` that adds color output.
343 """
344 def write(self, *texts):
345 write(sys.stderr, None, *texts)
346
347 def writeln(self, *texts):
348 writeln(sys.stderr, None, *texts)
349
350 def __getattr__(self, name):
351 return getattr(sys.stdout, name)
352 stderr = stderr()
353
354
355 if curses is not None:
356 # This is probably just range(8)
357 COLOR2CURSES = [
358 COLOR_BLACK,
359 COLOR_RED,
360 COLOR_GREEN,
361 COLOR_YELLOW,
362 COLOR_BLUE,
363 COLOR_MAGENTA,
364 COLOR_CYAN,
365 COLOR_WHITE,
366 ]
367
368 A2CURSES = {
369 A_BLINK: curses.A_BLINK,
370 A_BOLD: curses.A_BOLD,
371 A_DIM: curses.A_DIM,
372 A_REVERSE: curses.A_REVERSE,
373 A_STANDOUT: curses.A_STANDOUT,
374 A_UNDERLINE: curses.A_UNDERLINE,
375 }
376
377
378 # default style
379 style_default = Style.fromstr("white:black")
380
381 # Styles for datatypes
382 style_type_none = Style.fromstr("magenta:black")
383 style_type_bool = Style.fromstr("magenta:black")
384 style_type_number = Style.fromstr("yellow:black")
385 style_type_datetime = Style.fromstr("magenta:black")
386 style_type_type = Style.fromstr("cyan:black")
387
388 # Style for URLs and file/directory names
389 style_url = Style.fromstr("green:black")
390 style_dir = Style.fromstr("cyan:black")
391 style_file = Style.fromstr("green:black")
392
393 # Style for ellipsis (when an output has been shortened
394 style_ellisis = Style.fromstr("red:black")
395
396 # Style for displaying exceptions
397 style_error = Style.fromstr("red:black")
398
399 # Style for displaying non-existing attributes
400 style_nodata = Style.fromstr("red:black")
@@ -1,137 +0,0 b''
1 """Doctest-related utilities for IPython.
2
3 For most common uses, all you should need to run is::
4
5 from IPython.dtutils import idoctest
6
7 See the idoctest docstring below for usage details.
8 """
9
10 import doctest
11 import sys
12
13 from IPython.core import ipapi
14 ip = ipapi.get()
15
16 def rundoctest(text,ns=None,eraise=False):
17 """Run a the input source as a doctest, in the caller's namespace.
18
19 :Parameters:
20 text : str
21 Source to execute.
22
23 :Keywords:
24 ns : dict (None)
25 Namespace where the code should be executed. If not given, the
26 caller's locals and globals are used.
27 eraise : bool (False)
28 If true, immediately raise any exceptions instead of reporting them at
29 the end. This allows you to then do interactive debugging via
30 IPython's facilities (use %debug after the fact, or with %pdb for
31 automatic activation).
32 """
33
34 name = 'interactive doctest'
35 filename = '<IPython console>'
36
37 if eraise:
38 runner = doctest.DebugRunner()
39 else:
40 runner = doctest.DocTestRunner()
41
42 parser = doctest.DocTestParser()
43 if ns is None:
44 f = sys._getframe(1)
45 ns = f.f_globals.copy()
46 ns.update(f.f_locals)
47
48 test = parser.get_doctest(text,ns,name,filename,0)
49 runner.run(test)
50 runner.summarize(True)
51
52
53 def idoctest(ns=None,eraise=False):
54 """Interactively prompt for input and run it as a doctest.
55
56 To finish entering input, enter two blank lines or Ctrl-D (EOF). If you
57 use Ctrl-C, the example is aborted and all input discarded.
58
59 :Keywords:
60 ns : dict (None)
61 Namespace where the code should be executed. If not given, the IPython
62 interactive namespace is used.
63 eraise : bool (False)
64 If true, immediately raise any exceptions instead of reporting them at
65 the end. This allows you to then do interactive debugging via
66 IPython's facilities (use %debug after the fact, or with %pdb for
67 automatic activation).
68 end_mark : str ('--')
69 String to explicitly indicate the end of input.
70
71 """
72
73 inlines = []
74 empty_lines = 0 # count consecutive empty lines
75 run_test = True
76
77 if ns is None:
78 ns = ip.user_ns
79
80 ip.savehist()
81 try:
82 while True:
83 line = raw_input()
84 if not line or line.isspace():
85 empty_lines += 1
86 else:
87 empty_lines = 0
88
89 if empty_lines>=2:
90 break
91
92 inlines.append(line)
93 except EOFError:
94 pass
95 except KeyboardInterrupt:
96 print "KeyboardInterrupt - Discarding input."
97 run_test = False
98
99 ip.reloadhist()
100
101 if run_test:
102 # Extra blank line at the end to ensure that the final docstring has a
103 # closing newline
104 inlines.append('')
105 rundoctest('\n'.join(inlines),ns,eraise)
106
107
108 # For debugging of this module itself.
109 if __name__ == "__main__":
110 t = """
111 >>> for i in range(10):
112 ... print i,
113 ...
114 0 1 2 3 4 5 6 7 8 9
115 """
116
117 t2 = """
118 A simple example::
119
120 >>> for i in range(10):
121 ... print i,
122 ...
123 0 1 2 3 4 5 6 7 8 9
124
125 Some more details::
126
127 >>> print "hello"
128 hello
129 """
130
131 t3 = """
132 A failing example::
133
134 >>> x=1
135 >>> x+1
136 3
137 """
This diff has been collapsed as it changes many lines, (518 lines changed) Show them Hide them
@@ -1,518 +0,0 b''
1 #!/usr/bin/python
2 # -*- coding: iso-8859-15 -*-
3 '''
4 Provides IPython remote instance.
5
6 @author: Laurent Dufrechou
7 laurent.dufrechou _at_ gmail.com
8 @license: BSD
9
10 All rights reserved. This program and the accompanying materials are made
11 available under the terms of the BSD which accompanies this distribution, and
12 is available at U{http://www.opensource.org/licenses/bsd-license.php}
13 '''
14
15 __version__ = 0.9
16 __author__ = "Laurent Dufrechou"
17 __email__ = "laurent.dufrechou _at_ gmail.com"
18 __license__ = "BSD"
19
20 import re
21 import sys
22 import os
23 import locale
24 from thread_ex import ThreadEx
25
26 from IPython.core import iplib
27 import IPython.utils.io
28
29 ##############################################################################
30 class _Helper(object):
31 """Redefine the built-in 'help'.
32 This is a wrapper around pydoc.help (with a twist).
33 """
34
35 def __init__(self, pager):
36 self._pager = pager
37
38 def __repr__(self):
39 return "Type help() for interactive help, " \
40 "or help(object) for help about object."
41
42 def __call__(self, *args, **kwds):
43 class DummyWriter(object):
44 '''Dumy class to handle help output'''
45 def __init__(self, pager):
46 self._pager = pager
47
48 def write(self, data):
49 '''hook to fill self._pager'''
50 self._pager(data)
51
52 import pydoc
53 pydoc.help.output = DummyWriter(self._pager)
54 pydoc.help.interact = lambda :1
55
56 return pydoc.help(*args, **kwds)
57
58
59 ##############################################################################
60 class _CodeExecutor(ThreadEx):
61 ''' Thread that execute ipython code '''
62 def __init__(self, instance):
63 ThreadEx.__init__(self)
64 self.instance = instance
65
66 def run(self):
67 '''Thread main loop'''
68 try:
69 self.instance._doc_text = None
70 self.instance._help_text = None
71 self.instance._execute()
72 # used for uper class to generate event after execution
73 self.instance._after_execute()
74
75 except KeyboardInterrupt:
76 pass
77
78
79 ##############################################################################
80 class NonBlockingIPShell(object):
81 '''
82 Create an IPython instance, running the commands in a separate,
83 non-blocking thread.
84 This allows embedding in any GUI without blockage.
85
86 Note: The ThreadEx class supports asynchroneous function call
87 via raise_exc()
88 '''
89
90 def __init__(self, user_ns={}, user_global_ns=None,
91 cin=None, cout=None, cerr=None,
92 ask_exit_handler=None):
93 '''
94 @param user_ns: User namespace.
95 @type user_ns: dictionary
96 @param user_global_ns: User global namespace.
97 @type user_global_ns: dictionary.
98 @param cin: Console standard input.
99 @type cin: IO stream
100 @param cout: Console standard output.
101 @type cout: IO stream
102 @param cerr: Console standard error.
103 @type cerr: IO stream
104 @param exit_handler: Replacement for builtin exit() function
105 @type exit_handler: function
106 @param time_loop: Define the sleep time between two thread's loop
107 @type int
108 '''
109 #ipython0 initialisation
110 self._IP = None
111 self.init_ipython0(user_ns, user_global_ns,
112 cin, cout, cerr,
113 ask_exit_handler)
114
115 #vars used by _execute
116 self._iter_more = 0
117 self._history_level = 0
118 self._complete_sep = re.compile('[\s\{\}\[\]\(\)\=]')
119 self._prompt = str(self._IP.outputcache.prompt1).strip()
120
121 #thread working vars
122 self._line_to_execute = ''
123 self._threading = True
124
125 #vars that will be checked by GUI loop to handle thread states...
126 #will be replaced later by PostEvent GUI funtions...
127 self._doc_text = None
128 self._help_text = None
129 self._add_button = None
130
131 def init_ipython0(self, user_ns={}, user_global_ns=None,
132 cin=None, cout=None, cerr=None,
133 ask_exit_handler=None):
134 ''' Initialize an ipython0 instance '''
135
136 #first we redefine in/out/error functions of IPython
137 #BUG: we've got a limitation form ipython0 there
138 #only one instance can be instanciated else tehre will be
139 #cin/cout/cerr clash...
140 if cin:
141 Term.cin = cin
142 if cout:
143 Term.cout = cout
144 if cerr:
145 Term.cerr = cerr
146
147 excepthook = sys.excepthook
148
149 #Hack to save sys.displayhook, because ipython seems to overwrite it...
150 self.sys_displayhook_ori = sys.displayhook
151 ipython0 = iplib.InteractiveShell(
152 parent=None, config=None,
153 user_ns=user_ns,
154 user_global_ns=user_global_ns
155 )
156 self._IP = ipython0
157
158 #we save ipython0 displayhook and we restore sys.displayhook
159 self.displayhook = sys.displayhook
160 sys.displayhook = self.sys_displayhook_ori
161
162 #we replace IPython default encoding by wx locale encoding
163 loc = locale.getpreferredencoding()
164 if loc:
165 self._IP.stdin_encoding = loc
166 #we replace the ipython default pager by our pager
167 self._IP.set_hook('show_in_pager', self._pager)
168
169 #we replace the ipython default shell command caller
170 #by our shell handler
171 self._IP.set_hook('shell_hook', self._shell)
172
173 #we replace the ipython default input command caller by our method
174 iplib.raw_input_original = self._raw_input_original
175 #we replace the ipython default exit command by our method
176 self._IP.exit = ask_exit_handler
177 #we replace the help command
178 self._IP.user_ns['help'] = _Helper(self._pager_help)
179
180 #we disable cpaste magic... until we found a way to use it properly.
181 def bypass_magic(self, arg):
182 print '%this magic is currently disabled.'
183 ipython0.define_magic('cpaste', bypass_magic)
184
185 import __builtin__
186 __builtin__.raw_input = self._raw_input
187
188 sys.excepthook = excepthook
189
190 #----------------------- Thread management section ----------------------
191 def do_execute(self, line):
192 """
193 Tell the thread to process the 'line' command
194 """
195
196 self._line_to_execute = line
197
198 if self._threading:
199 #we launch the ipython line execution in a thread to make it
200 #interruptible with include it in self namespace to be able
201 #to call ce.raise_exc(KeyboardInterrupt)
202 self.ce = _CodeExecutor(self)
203 self.ce.start()
204 else:
205 try:
206 self._doc_text = None
207 self._help_text = None
208 self._execute()
209 # used for uper class to generate event after execution
210 self._after_execute()
211
212 except KeyboardInterrupt:
213 pass
214
215 #----------------------- IPython management section ----------------------
216 def get_threading(self):
217 """
218 Returns threading status, is set to True, then each command sent to
219 the interpreter will be executed in a separated thread allowing,
220 for example, breaking a long running commands.
221 Disallowing it, permits better compatibilty with instance that is embedding
222 IPython instance.
223
224 @return: Execution method
225 @rtype: bool
226 """
227 return self._threading
228
229 def set_threading(self, state):
230 """
231 Sets threading state, if set to True, then each command sent to
232 the interpreter will be executed in a separated thread allowing,
233 for example, breaking a long running commands.
234 Disallowing it, permits better compatibilty with instance that is embedding
235 IPython instance.
236
237 @param state: Sets threading state
238 @type bool
239 """
240 self._threading = state
241
242 def get_doc_text(self):
243 """
244 Returns the output of the processing that need to be paged (if any)
245
246 @return: The std output string.
247 @rtype: string
248 """
249 return self._doc_text
250
251 def get_help_text(self):
252 """
253 Returns the output of the processing that need to be paged via help pager(if any)
254
255 @return: The std output string.
256 @rtype: string
257 """
258 return self._help_text
259
260 def get_banner(self):
261 """
262 Returns the IPython banner for useful info on IPython instance
263
264 @return: The banner string.
265 @rtype: string
266 """
267 return self._IP.banner
268
269 def get_prompt_count(self):
270 """
271 Returns the prompt number.
272 Each time a user execute a line in the IPython shell the prompt count is increased
273
274 @return: The prompt number
275 @rtype: int
276 """
277 return self._IP.outputcache.prompt_count
278
279 def get_prompt(self):
280 """
281 Returns current prompt inside IPython instance
282 (Can be In [...]: ot ...:)
283
284 @return: The current prompt.
285 @rtype: string
286 """
287 return self._prompt
288
289 def get_indentation(self):
290 """
291 Returns the current indentation level
292 Usefull to put the caret at the good start position if we want to do autoindentation.
293
294 @return: The indentation level.
295 @rtype: int
296 """
297 return self._IP.indent_current_nsp
298
299 def update_namespace(self, ns_dict):
300 '''
301 Add the current dictionary to the shell namespace.
302
303 @param ns_dict: A dictionary of symbol-values.
304 @type ns_dict: dictionary
305 '''
306 self._IP.user_ns.update(ns_dict)
307
308 def complete(self, line):
309 '''
310 Returns an auto completed line and/or posibilities for completion.
311
312 @param line: Given line so far.
313 @type line: string
314
315 @return: Line completed as for as possible,
316 and possible further completions.
317 @rtype: tuple
318 '''
319 split_line = self._complete_sep.split(line)
320 possibilities = self._IP.complete(split_line[-1])
321 if possibilities:
322
323 def _common_prefix(str1, str2):
324 '''
325 Reduction function. returns common prefix of two given strings.
326
327 @param str1: First string.
328 @type str1: string
329 @param str2: Second string
330 @type str2: string
331
332 @return: Common prefix to both strings.
333 @rtype: string
334 '''
335 for i in range(len(str1)):
336 if not str2.startswith(str1[:i+1]):
337 return str1[:i]
338 return str1
339 common_prefix = reduce(_common_prefix, possibilities)
340 completed = line[:-len(split_line[-1])]+common_prefix
341 else:
342 completed = line
343 return completed, possibilities
344
345 def history_back(self):
346 '''
347 Provides one history command back.
348
349 @return: The command string.
350 @rtype: string
351 '''
352 history = ''
353 #the below while loop is used to suppress empty history lines
354 while((history == '' or history == '\n') and self._history_level >0):
355 if self._history_level >= 1:
356 self._history_level -= 1
357 history = self._get_history()
358 return history
359
360 def history_forward(self):
361 '''
362 Provides one history command forward.
363
364 @return: The command string.
365 @rtype: string
366 '''
367 history = ''
368 #the below while loop is used to suppress empty history lines
369 while((history == '' or history == '\n') \
370 and self._history_level <= self._get_history_max_index()):
371 if self._history_level < self._get_history_max_index():
372 self._history_level += 1
373 history = self._get_history()
374 else:
375 if self._history_level == self._get_history_max_index():
376 history = self._get_history()
377 self._history_level += 1
378 else:
379 history = ''
380 return history
381
382 def init_history_index(self):
383 '''
384 set history to last command entered
385 '''
386 self._history_level = self._get_history_max_index()+1
387
388 #----------------------- IPython PRIVATE management section --------------
389 def _after_execute(self):
390 '''
391 Can be redefined to generate post event after excution is done
392 '''
393 pass
394
395 def _ask_exit(self):
396 '''
397 Can be redefined to generate post event to exit the Ipython shell
398 '''
399 pass
400
401 def _get_history_max_index(self):
402 '''
403 returns the max length of the history buffer
404
405 @return: history length
406 @rtype: int
407 '''
408 return len(self._IP.input_hist_raw)-1
409
410 def _get_history(self):
411 '''
412 Get's the command string of the current history level.
413
414 @return: Historic command stri
415 @rtype: string
416 '''
417 rv = self._IP.input_hist_raw[self._history_level].strip('\n')
418 return rv
419
420 def _pager_help(self, text):
421 '''
422 This function is used as a callback replacment to IPython help pager function
423
424 It puts the 'text' value inside the self._help_text string that can be retrived via
425 get_help_text function.
426 '''
427 if self._help_text == None:
428 self._help_text = text
429 else:
430 self._help_text += text
431
432 def _pager(self, IP, text):
433 '''
434 This function is used as a callback replacment to IPython pager function
435
436 It puts the 'text' value inside the self._doc_text string that can be retrived via
437 get_doc_text function.
438 '''
439 self._doc_text = text
440
441 def _raw_input_original(self, prompt=''):
442 '''
443 Custom raw_input() replacement. Get's current line from console buffer.
444
445 @param prompt: Prompt to print. Here for compatability as replacement.
446 @type prompt: string
447
448 @return: The current command line text.
449 @rtype: string
450 '''
451 return self._line_to_execute
452
453 def _raw_input(self, prompt=''):
454 """ A replacement from python's raw_input.
455 """
456 raise NotImplementedError
457
458 def _execute(self):
459 '''
460 Executes the current line provided by the shell object.
461 '''
462
463 orig_stdout = sys.stdout
464 sys.stdout = Term.cout
465 #self.sys_displayhook_ori = sys.displayhook
466 #sys.displayhook = self.displayhook
467
468 try:
469 line = self._IP.raw_input(None, self._iter_more)
470 if self._IP.autoindent:
471 self._IP.readline_startup_hook(None)
472
473 except KeyboardInterrupt:
474 self._IP.write('\nKeyboardInterrupt\n')
475 self._IP.resetbuffer()
476 # keep cache in sync with the prompt counter:
477 self._IP.outputcache.prompt_count -= 1
478
479 if self._IP.autoindent:
480 self._IP.indent_current_nsp = 0
481 self._iter_more = 0
482 except:
483 self._IP.showtraceback()
484 else:
485 self._IP.write(str(self._IP.outputcache.prompt_out).strip())
486 self._iter_more = self._IP.push_line(line)
487 if (self._IP.SyntaxTB.last_syntax_error and \
488 self._IP.autoedit_syntax):
489 self._IP.edit_syntax_error()
490 if self._iter_more:
491 self._prompt = str(self._IP.outputcache.prompt2).strip()
492 if self._IP.autoindent:
493 self._IP.readline_startup_hook(self._IP.pre_readline)
494 else:
495 self._prompt = str(self._IP.outputcache.prompt1).strip()
496 self._IP.indent_current_nsp = 0 #we set indentation to 0
497
498 sys.stdout = orig_stdout
499 #sys.displayhook = self.sys_displayhook_ori
500
501 def _shell(self, ip, cmd):
502 '''
503 Replacement method to allow shell commands without them blocking.
504
505 @param ip: Ipython instance, same as self._IP
506 @type cmd: Ipython instance
507 @param cmd: Shell command to execute.
508 @type cmd: string
509 '''
510 stdin, stdout = os.popen4(cmd)
511 result = stdout.read().decode('cp437').\
512 encode(locale.getpreferredencoding())
513 #we use print command because the shell command is called
514 #inside IPython instance and thus is redirected to thread cout
515 #"\x01\x1b[1;36m\x02" <-- add colour to the text...
516 print "\x01\x1b[1;36m\x02"+result
517 stdout.close()
518 stdin.close()
This diff has been collapsed as it changes many lines, (510 lines changed) Show them Hide them
@@ -1,510 +0,0 b''
1 #!/usr/bin/python
2 # -*- coding: iso-8859-15 -*-
3 import wx
4 import wx.stc as stc
5 import keyword
6
7 #-----------------------------------------
8 # History widget for IPython
9 __version__ = 0.5
10 __author__ = "Laurent Dufrechou"
11 __email__ = "laurent.dufrechou _at_ gmail.com"
12 __license__ = "BSD"
13 #-----------------------------------------
14
15 class IPythonHistoryPanel(wx.Panel):
16
17 def __init__(self, parent,flt_empty=True,
18 flt_doc=True,flt_cmd=True,flt_magic=True):
19
20 wx.Panel.__init__(self,parent,-1)
21 #text_ctrl = wx.TextCtrl(self, -1, style=wx.TE_MULTILINE)
22 text_ctrl = PythonSTC(self, -1)
23
24
25 st_filt = wx.StaticText(self, -1, " Filter:")
26
27 self.filter_empty = wx.CheckBox(self, -1, "Empty commands")
28 self.filter_doc = wx.CheckBox(self, -1, "?: Doc commands")
29 self.filter_cmd = wx.CheckBox(self, -1, "!: Sys commands")
30 self.filter_magic = wx.CheckBox(self, -1, "%: Magic keys")
31
32 self.options={'filter_empty':{'value':'True',
33 'checkbox':self.filter_empty, \
34 'True':True,'False':False,
35 'setfunc':lambda x:None},
36 'filter_doc':{'value':'True',
37 'checkbox':self.filter_doc, \
38 'True':True,'False':False,
39 'setfunc':lambda x:None},
40 'filter_cmd':{'value':'True',
41 'checkbox':self.filter_cmd, \
42 'True':True,'False':False,
43 'setfunc':lambda x:None},
44 'filter_magic':{'value':'True',
45 'checkbox':self.filter_magic, \
46 'True':True,'False':False,
47 'setfunc':lambda x:None},
48 }
49 self.reloadOptions(self.options)
50
51 self.filter_empty.Bind(wx.EVT_CHECKBOX, self.evtCheckEmptyFilter)
52 self.filter_doc.Bind(wx.EVT_CHECKBOX, self.evtCheckDocFilter)
53 self.filter_cmd.Bind(wx.EVT_CHECKBOX, self.evtCheckCmdFilter)
54 self.filter_magic.Bind(wx.EVT_CHECKBOX, self.evtCheckMagicFilter)
55
56 #self.filter_empty.SetValue(flt_empty)
57 #self.filter_doc.SetValue(flt_doc)
58 #self.filter_cmd.SetValue(flt_cmd)
59 #self.filter_magic.SetValue(flt_magic)
60
61 sizer = wx.BoxSizer(wx.VERTICAL)
62
63 sizer.Add(text_ctrl, 1, wx.EXPAND)
64 sizer.AddMany( [(5,5),
65 st_filt,
66 (10,10),
67 self.filter_empty,
68 self.filter_doc,
69 self.filter_cmd,
70 self.filter_magic,
71 (10,10),
72 ])
73 self.SetAutoLayout(True)
74 sizer.Fit(self)
75 sizer.SetSizeHints(self)
76 self.SetSizer(sizer)
77 self.text_ctrl=text_ctrl
78 #text_ctrl.SetText(demoText + open('Main.py').read())
79 text_ctrl.EmptyUndoBuffer()
80 text_ctrl.Colourise(0, -1)
81
82 # line numbers in the margin
83 text_ctrl.SetMarginType(1, stc.STC_MARGIN_NUMBER)
84 text_ctrl.SetMarginWidth(1, 15)
85
86
87 def write(self,history_line):
88 add = True
89 if self.filter_empty.GetValue() == True and history_line == '':
90 add = False
91 if len(history_line)>0:
92 if self.filter_doc.GetValue() == True and history_line[-1:] == '?':
93 add = False
94 if self.filter_cmd.GetValue() == True and history_line[0] == '!':
95 add = False
96 if self.filter_magic.GetValue() == True and history_line[0] == '%':
97 add = False
98 if add:
99 self.text_ctrl.AppendText(history_line+'\n')
100
101 #------------------------ Option Section -----------------------------------
102 def processOptionCheckedEvt(self, event, name):
103 if event.IsChecked():
104 self.options[name]['value']='True'
105 else:
106 self.options[name]['value']='False'
107 self.updateOptionTracker(name,
108 self.options[name]['value'])
109
110 def evtCheckEmptyFilter(self, event):
111 self.processOptionCheckedEvt(event, 'filter_empty')
112
113 def evtCheckDocFilter(self, event):
114 self.processOptionCheckedEvt(event, 'filter_doc')
115
116 def evtCheckCmdFilter(self, event):
117 self.processOptionCheckedEvt(event, 'filter_cmd')
118
119 def evtCheckMagicFilter(self, event):
120 self.processOptionCheckedEvt(event, 'filter_magic')
121
122 def getOptions(self):
123 return self.options
124
125 def reloadOptions(self,options):
126 self.options = options
127 for key in self.options.keys():
128 value = self.options[key]['value']
129 self.options[key]['checkbox'].SetValue(self.options[key][value])
130 self.options[key]['setfunc'](value)
131
132 #------------------------ Hook Section -----------------------------------
133 def updateOptionTracker(self,name,value):
134 '''
135 Default history tracker (does nothing)
136 '''
137 pass
138
139 def setOptionTrackerHook(self,func):
140 '''
141 Define a new history tracker
142 '''
143 self.updateOptionTracker = func
144
145
146 #----------------------------------------------------------------------
147 # Font definition for Styled Text Control
148
149 if wx.Platform == '__WXMSW__':
150 faces = { 'times': 'Times New Roman',
151 'mono' : 'Courier New',
152 'helv' : 'Arial',
153 'other': 'Comic Sans MS',
154 'size' : 8,
155 'size2': 6,
156 }
157 elif wx.Platform == '__WXMAC__':
158 faces = { 'times': 'Times New Roman',
159 'mono' : 'Monaco',
160 'helv' : 'Arial',
161 'other': 'Comic Sans MS',
162 'size' : 8,
163 'size2': 6,
164 }
165 else:
166 faces = { 'times': 'Times',
167 'mono' : 'Courier',
168 'helv' : 'Helvetica',
169 'other': 'new century schoolbook',
170 'size' : 8,
171 'size2': 6,
172 }
173
174
175 #----------------------------------------------------------------------
176
177 class PythonSTC(stc.StyledTextCtrl):
178
179 fold_symbols = 3
180
181 def __init__(self, parent, ID,
182 pos=wx.DefaultPosition, size=wx.DefaultSize,
183 style=0):
184 stc.StyledTextCtrl.__init__(self, parent, ID, pos, size, style)
185 #self.CmdKeyAssign(ord('B'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMIN)
186 #self.CmdKeyAssign(ord('N'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMOUT)
187
188 self.SetLexer(stc.STC_LEX_PYTHON)
189 self.SetKeyWords(0, " ".join(keyword.kwlist))
190
191 #self.SetProperty("fold", "1")
192 #self.SetProperty("tab.timmy.whinge.level", "1")
193 #self.SetMargins(0,0)
194
195 #self.SetViewWhiteSpace(False)
196 #self.SetBufferedDraw(False)
197 #self.SetViewEOL(True)
198 self.SetEOLMode(stc.STC_EOL_CRLF)
199 #self.SetUseAntiAliasing(True)
200
201 self.SetEdgeMode(stc.STC_EDGE_LINE)
202 self.SetEdgeColumn(80)
203 self.SetEdgeColour(wx.LIGHT_GREY)
204 self.SetLayoutCache(stc.STC_CACHE_PAGE)
205
206 # Setup a margin to hold fold markers
207 #self.SetFoldFlags(16)
208 ### WHAT IS THIS VALUE? WHAT ARE THE OTHER FLAGS? DOES IT MATTER?
209 self.SetMarginType(2, stc.STC_MARGIN_SYMBOL)
210 self.SetMarginMask(2, stc.STC_MASK_FOLDERS)
211 self.SetMarginSensitive(2, True)
212 self.SetMarginWidth(2, 12)
213
214 if self.fold_symbols == 0:
215 # Arrow pointing right for contracted folders,
216 # arrow pointing down for expanded
217 self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPEN, \
218 stc.STC_MARK_ARROWDOWN, "black", "black")
219 self.MarkerDefine(stc.STC_MARKNUM_FOLDER, \
220 stc.STC_MARK_ARROW, "black", "black")
221 self.MarkerDefine(stc.STC_MARKNUM_FOLDERSUB, \
222 stc.STC_MARK_EMPTY, "black", "black")
223 self.MarkerDefine(stc.STC_MARKNUM_FOLDERTAIL, \
224 stc.STC_MARK_EMPTY, "black", "black")
225 self.MarkerDefine(stc.STC_MARKNUM_FOLDEREND, \
226 stc.STC_MARK_EMPTY, "white", "black")
227 self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPENMID, \
228 stc.STC_MARK_EMPTY, "white", "black")
229 self.MarkerDefine(stc.STC_MARKNUM_FOLDERMIDTAIL, \
230 stc.STC_MARK_EMPTY, "white", "black")
231
232 elif self.fold_symbols == 1:
233 # Plus for contracted folders, minus for expanded
234 self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPEN, \
235 stc.STC_MARK_MINUS, "white", "black")
236 self.MarkerDefine(stc.STC_MARKNUM_FOLDER, \
237 stc.STC_MARK_PLUS, "white", "black")
238 self.MarkerDefine(stc.STC_MARKNUM_FOLDERSUB, \
239 stc.STC_MARK_EMPTY, "white", "black")
240 self.MarkerDefine(stc.STC_MARKNUM_FOLDERTAIL, \
241 stc.STC_MARK_EMPTY, "white", "black")
242 self.MarkerDefine(stc.STC_MARKNUM_FOLDEREND, \
243 stc.STC_MARK_EMPTY, "white", "black")
244 self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPENMID, \
245 stc.STC_MARK_EMPTY, "white", "black")
246 self.MarkerDefine(stc.STC_MARKNUM_FOLDERMIDTAIL, \
247 stc.STC_MARK_EMPTY, "white", "black")
248
249 elif self.fold_symbols == 2:
250 # Like a flattened tree control using circular headers and curved joins
251 self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPEN, \
252 stc.STC_MARK_CIRCLEMINUS, "white", "#404040")
253 self.MarkerDefine(stc.STC_MARKNUM_FOLDER, \
254 stc.STC_MARK_CIRCLEPLUS, "white", "#404040")
255 self.MarkerDefine(stc.STC_MARKNUM_FOLDERSUB, \
256 stc.STC_MARK_VLINE, "white", "#404040")
257 self.MarkerDefine(stc.STC_MARKNUM_FOLDERTAIL, \
258 stc.STC_MARK_LCORNERCURVE, "white", "#404040")
259 self.MarkerDefine(stc.STC_MARKNUM_FOLDEREND, \
260 stc.STC_MARK_CIRCLEPLUSCONNECTED, "white", "#404040")
261 self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPENMID, \
262 stc.STC_MARK_CIRCLEMINUSCONNECTED, "white", "#404040")
263 self.MarkerDefine(stc.STC_MARKNUM_FOLDERMIDTAIL, \
264 stc.STC_MARK_TCORNERCURVE, "white", "#404040")
265
266 elif self.fold_symbols == 3:
267 # Like a flattened tree control using square headers
268 self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPEN, \
269 stc.STC_MARK_BOXMINUS, "white", "#808080")
270 self.MarkerDefine(stc.STC_MARKNUM_FOLDER, \
271 stc.STC_MARK_BOXPLUS, "white", "#808080")
272 self.MarkerDefine(stc.STC_MARKNUM_FOLDERSUB, \
273 stc.STC_MARK_VLINE, "white", "#808080")
274 self.MarkerDefine(stc.STC_MARKNUM_FOLDERTAIL, \
275 stc.STC_MARK_LCORNER, "white", "#808080")
276 self.MarkerDefine(stc.STC_MARKNUM_FOLDEREND, \
277 stc.STC_MARK_BOXPLUSCONNECTED, "white", "#808080")
278 self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPENMID, \
279 stc.STC_MARK_BOXMINUSCONNECTED, "white", "#808080")
280 self.MarkerDefine(stc.STC_MARKNUM_FOLDERMIDTAIL, \
281 stc.STC_MARK_TCORNER, "white", "#808080")
282
283
284 self.Bind(stc.EVT_STC_UPDATEUI, self.OnUpdateUI)
285 self.Bind(stc.EVT_STC_MARGINCLICK, self.OnMarginClick)
286 self.Bind(wx.EVT_KEY_DOWN, self.OnKeyPressed)
287
288 # Make some styles, The lexer defines what each style is used for, we
289 # just have to define what each style looks like. This set is adapted from
290 # Scintilla sample property files.
291
292 # Global default styles for all languages
293 self.StyleSetSpec(stc.STC_STYLE_DEFAULT, "face:%(helv)s,size:%(size)d" % faces)
294 self.StyleClearAll() # Reset all to be like the default
295
296 # Global default styles for all languages
297 self.StyleSetSpec(stc.STC_STYLE_DEFAULT, "face:%(helv)s,size:%(size)d" % faces)
298 self.StyleSetSpec(stc.STC_STYLE_LINENUMBER, "back:#C0C0C0,face:%(helv)s,size:%(size2)d" % faces)
299 self.StyleSetSpec(stc.STC_STYLE_CONTROLCHAR, "face:%(other)s" % faces)
300 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT, "fore:#FFFFFF,back:#0000FF,bold")
301 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD, "fore:#000000,back:#FF0000,bold")
302
303 # Python styles
304 # Default
305 self.StyleSetSpec(stc.STC_P_DEFAULT, "fore:#000000,face:%(helv)s,size:%(size)d" % faces)
306 # Comments
307 self.StyleSetSpec(stc.STC_P_COMMENTLINE, "fore:#007F00,face:%(other)s,size:%(size)d" % faces)
308 # Number
309 self.StyleSetSpec(stc.STC_P_NUMBER, "fore:#007F7F,size:%(size)d" % faces)
310 # String
311 self.StyleSetSpec(stc.STC_P_STRING, "fore:#7F007F,face:%(helv)s,size:%(size)d" % faces)
312 # Single quoted string
313 self.StyleSetSpec(stc.STC_P_CHARACTER, "fore:#7F007F,face:%(helv)s,size:%(size)d" % faces)
314 # Keyword
315 self.StyleSetSpec(stc.STC_P_WORD, "fore:#00007F,bold,size:%(size)d" % faces)
316 # Triple quotes
317 self.StyleSetSpec(stc.STC_P_TRIPLE, "fore:#7F0000,size:%(size)d" % faces)
318 # Triple double quotes
319 self.StyleSetSpec(stc.STC_P_TRIPLEDOUBLE, "fore:#7F0000,size:%(size)d" % faces)
320 # Class name definition
321 self.StyleSetSpec(stc.STC_P_CLASSNAME, "fore:#0000FF,bold,underline,size:%(size)d" % faces)
322 # Function or method name definition
323 self.StyleSetSpec(stc.STC_P_DEFNAME, "fore:#007F7F,bold,size:%(size)d" % faces)
324 # Operators
325 self.StyleSetSpec(stc.STC_P_OPERATOR, "bold,size:%(size)d" % faces)
326 # Identifiers
327 self.StyleSetSpec(stc.STC_P_IDENTIFIER, "fore:#000000,face:%(helv)s,size:%(size)d" % faces)
328 # Comment-blocks
329 self.StyleSetSpec(stc.STC_P_COMMENTBLOCK, "fore:#7F7F7F,size:%(size)d" % faces)
330 # End of line where string is not closed
331 self.StyleSetSpec(stc.STC_P_STRINGEOL, "fore:#000000,face:%(mono)s,back:#E0C0E0,eol,size:%(size)d" % faces)
332
333 self.SetCaretForeground("BLUE")
334
335
336 # register some images for use in the AutoComplete box.
337 #self.RegisterImage(1, images.getSmilesBitmap())
338 #self.RegisterImage(2,
339 # wx.ArtProvider.GetBitmap(wx.ART_NEW, size=(16,16)))
340 #self.RegisterImage(3,
341 # wx.ArtProvider.GetBitmap(wx.ART_COPY, size=(16,16)))
342
343
344 def OnKeyPressed(self, event):
345 if self.CallTipActive():
346 self.CallTipCancel()
347 key = event.GetKeyCode()
348
349 if key == 32 and event.ControlDown():
350 pos = self.GetCurrentPos()
351
352 # Tips
353 if event.ShiftDown():
354 self.CallTipSetBackground("yellow")
355 self.CallTipShow(pos, 'lots of of text: blah, blah, blah\n\n'
356 'show some suff, maybe parameters..\n\n'
357 'fubar(param1, param2)')
358 # Code completion
359 else:
360 #lst = []
361 #for x in range(50000):
362 # lst.append('%05d' % x)
363 #st = " ".join(lst)
364 #print len(st)
365 #self.AutoCompShow(0, st)
366
367 kw = keyword.kwlist[:]
368
369 kw.sort() # Python sorts are case sensitive
370 self.AutoCompSetIgnoreCase(False) # so this needs to match
371
372 # Images are specified with a appended "?type"
373 for i in range(len(kw)):
374 if kw[i] in keyword.kwlist:
375 kw[i] = kw[i]# + "?1"
376
377 self.AutoCompShow(0, " ".join(kw))
378 else:
379 event.Skip()
380
381
382 def OnUpdateUI(self, evt):
383 # check for matching braces
384 braceAtCaret = -1
385 braceOpposite = -1
386 charBefore = None
387 caretPos = self.GetCurrentPos()
388
389 if caretPos > 0:
390 charBefore = self.GetCharAt(caretPos - 1)
391 styleBefore = self.GetStyleAt(caretPos - 1)
392
393 # check before
394 if charBefore and chr(charBefore) in "[]{}()" and styleBefore == stc.STC_P_OPERATOR:
395 braceAtCaret = caretPos - 1
396
397 # check after
398 if braceAtCaret < 0:
399 charAfter = self.GetCharAt(caretPos)
400 styleAfter = self.GetStyleAt(caretPos)
401
402 if charAfter and chr(charAfter) in "[]{}()" and styleAfter == stc.STC_P_OPERATOR:
403 braceAtCaret = caretPos
404
405 if braceAtCaret >= 0:
406 braceOpposite = self.BraceMatch(braceAtCaret)
407
408 if braceAtCaret != -1 and braceOpposite == -1:
409 self.BraceBadLight(braceAtCaret)
410 else:
411 self.BraceHighlight(braceAtCaret, braceOpposite)
412 #pt = self.PointFromPosition(braceOpposite)
413 #self.Refresh(True, wxRect(pt.x, pt.y, 5,5))
414 #print pt
415 #self.Refresh(False)
416
417
418 def OnMarginClick(self, evt):
419 # fold and unfold as needed
420 if evt.GetMargin() == 2:
421 if evt.GetShift() and evt.GetControl():
422 self.FoldAll()
423 else:
424 lineClicked = self.LineFromPosition(evt.GetPosition())
425
426 if self.GetFoldLevel(lineClicked) & stc.STC_FOLDLEVELHEADERFLAG:
427 if evt.GetShift():
428 self.SetFoldExpanded(lineClicked, True)
429 self.Expand(lineClicked, True, True, 1)
430 elif evt.GetControl():
431 if self.GetFoldExpanded(lineClicked):
432 self.SetFoldExpanded(lineClicked, False)
433 self.Expand(lineClicked, False, True, 0)
434 else:
435 self.SetFoldExpanded(lineClicked, True)
436 self.Expand(lineClicked, True, True, 100)
437 else:
438 self.ToggleFold(lineClicked)
439
440
441 def FoldAll(self):
442 lineCount = self.GetLineCount()
443 expanding = True
444
445 # find out if we are folding or unfolding
446 for lineNum in range(lineCount):
447 if self.GetFoldLevel(lineNum) & stc.STC_FOLDLEVELHEADERFLAG:
448 expanding = not self.GetFoldExpanded(lineNum)
449 break
450
451 lineNum = 0
452
453 while lineNum < lineCount:
454 level = self.GetFoldLevel(lineNum)
455 if level & stc.STC_FOLDLEVELHEADERFLAG and \
456 (level & stc.STC_FOLDLEVELNUMBERMASK) == stc.STC_FOLDLEVELBASE:
457
458 if expanding:
459 self.SetFoldExpanded(lineNum, True)
460 lineNum = self.Expand(lineNum, True)
461 lineNum = lineNum - 1
462 else:
463 lastChild = self.GetLastChild(lineNum, -1)
464 self.SetFoldExpanded(lineNum, False)
465
466 if lastChild > lineNum:
467 self.HideLines(lineNum+1, lastChild)
468
469 lineNum = lineNum + 1
470
471
472
473 def Expand(self, line, doExpand, force=False, visLevels=0, level=-1):
474 lastChild = self.GetLastChild(line, level)
475 line = line + 1
476
477 while line <= lastChild:
478 if force:
479 if visLevels > 0:
480 self.ShowLines(line, line)
481 else:
482 self.HideLines(line, line)
483 else:
484 if doExpand:
485 self.ShowLines(line, line)
486
487 if level == -1:
488 level = self.GetFoldLevel(line)
489
490 if level & stc.STC_FOLDLEVELHEADERFLAG:
491 if force:
492 if visLevels > 1:
493 self.SetFoldExpanded(line, True)
494 else:
495 self.SetFoldExpanded(line, False)
496
497 line = self.Expand(line, doExpand, force, visLevels-1)
498
499 else:
500 if doExpand and self.GetFoldExpanded(line):
501 line = self.Expand(line, True, force, visLevels-1)
502 else:
503 line = self.Expand(line, False, force, visLevels-1)
504 else:
505 line = line + 1
506
507 return line
508
509
510 #----------------------------------------------------------------------
This diff has been collapsed as it changes many lines, (942 lines changed) Show them Hide them
@@ -1,942 +0,0 b''
1 #!/usr/bin/python
2 # -*- coding: utf-8 -*-
3 '''
4 Provides IPython WX console widgets.
5
6 @author: Laurent Dufrechou
7 laurent.dufrechou _at_ gmail.com
8 This WX widget is based on the original work of Eitan Isaacson
9 that provided the console for the GTK toolkit.
10
11 Original work from:
12 @author: Eitan Isaacson
13 @organization: IBM Corporation
14 @copyright: Copyright (c) 2007 IBM Corporation
15 @license: BSD
16
17 All rights reserved. This program and the accompanying materials are made
18 available under the terms of the BSD which accompanies this distribution, and
19 is available at U{http://www.opensource.org/licenses/bsd-license.php}
20 '''
21
22 __version__ = 0.9
23 __author__ = "Laurent Dufrechou"
24 __email__ = "laurent.dufrechou _at_ gmail.com"
25 __license__ = "BSD"
26
27 import wx
28 import wx.stc as stc
29
30 import re
31 from StringIO import StringIO
32
33 import sys
34 import codecs
35 import locale
36 import time
37
38 for enc in (locale.getpreferredencoding(),
39 sys.getfilesystemencoding(),
40 sys.getdefaultencoding()):
41 try:
42 codecs.lookup(enc)
43 ENCODING = enc
44 break
45 except LookupError:
46 pass
47 else:
48 ENCODING = 'utf-8'
49
50 from ipshell_nonblocking import NonBlockingIPShell
51
52 class WxNonBlockingIPShell(NonBlockingIPShell):
53 '''
54 An NonBlockingIPShell Thread that is WX dependent.
55 '''
56 def __init__(self, parent,
57 argv=[],user_ns={},user_global_ns=None,
58 cin=None, cout=None, cerr=None,
59 ask_exit_handler=None):
60
61 NonBlockingIPShell.__init__(self, argv, user_ns, user_global_ns,
62 cin, cout, cerr,
63 ask_exit_handler)
64
65 self.parent = parent
66
67 self.ask_exit_callback = ask_exit_handler
68 self._IP.exit = self._ask_exit
69
70 def addGUIShortcut(self, text, func):
71 wx.CallAfter(self.parent.add_button_handler,
72 button_info={ 'text':text,
73 'func':self.parent.doExecuteLine(func)})
74
75 def _raw_input(self, prompt=''):
76 """ A replacement from python's raw_input.
77 """
78 self.answer = None
79 if(self._threading == True):
80 wx.CallAfter(self._yesNoBox, prompt)
81 while self.answer is None:
82 time.sleep(.1)
83 else:
84 self._yesNoBox(prompt)
85 return self.answer
86
87 def _yesNoBox(self, prompt):
88 """ yes/no box managed with wx.CallAfter jsut in case caler is executed in a thread"""
89 dlg = wx.TextEntryDialog(
90 self.parent, prompt,
91 'Input requested', 'Python')
92 dlg.SetValue("")
93
94 answer = ''
95 if dlg.ShowModal() == wx.ID_OK:
96 answer = dlg.GetValue()
97
98 dlg.Destroy()
99 self.answer = answer
100
101 def _ask_exit(self):
102 wx.CallAfter(self.ask_exit_callback, ())
103
104 def _after_execute(self):
105 wx.CallAfter(self.parent.evtStateExecuteDone, ())
106
107
108 class WxConsoleView(stc.StyledTextCtrl):
109 '''
110 Specialized styled text control view for console-like workflow.
111 We use here a scintilla frontend thus it can be reused in any GUI that
112 supports scintilla with less work.
113
114 @cvar ANSI_COLORS_BLACK: Mapping of terminal colors to X11 names.
115 (with Black background)
116 @type ANSI_COLORS_BLACK: dictionary
117
118 @cvar ANSI_COLORS_WHITE: Mapping of terminal colors to X11 names.
119 (with White background)
120 @type ANSI_COLORS_WHITE: dictionary
121
122 @ivar color_pat: Regex of terminal color pattern
123 @type color_pat: _sre.SRE_Pattern
124 '''
125 ANSI_STYLES_BLACK = {'0;30': [0, 'WHITE'], '0;31': [1, 'RED'],
126 '0;32': [2, 'GREEN'], '0;33': [3, 'BROWN'],
127 '0;34': [4, 'BLUE'], '0;35': [5, 'PURPLE'],
128 '0;36': [6, 'CYAN'], '0;37': [7, 'LIGHT GREY'],
129 '1;30': [8, 'DARK GREY'], '1;31': [9, 'RED'],
130 '1;32': [10, 'SEA GREEN'], '1;33': [11, 'YELLOW'],
131 '1;34': [12, 'LIGHT BLUE'], '1;35':
132 [13, 'MEDIUM VIOLET RED'],
133 '1;36': [14, 'LIGHT STEEL BLUE'], '1;37': [15, 'YELLOW']}
134
135 ANSI_STYLES_WHITE = {'0;30': [0, 'BLACK'], '0;31': [1, 'RED'],
136 '0;32': [2, 'GREEN'], '0;33': [3, 'BROWN'],
137 '0;34': [4, 'BLUE'], '0;35': [5, 'PURPLE'],
138 '0;36': [6, 'CYAN'], '0;37': [7, 'LIGHT GREY'],
139 '1;30': [8, 'DARK GREY'], '1;31': [9, 'RED'],
140 '1;32': [10, 'SEA GREEN'], '1;33': [11, 'YELLOW'],
141 '1;34': [12, 'LIGHT BLUE'], '1;35':
142 [13, 'MEDIUM VIOLET RED'],
143 '1;36': [14, 'LIGHT STEEL BLUE'], '1;37': [15, 'YELLOW']}
144
145 def __init__(self, parent, prompt, intro="", background_color="BLACK",
146 pos=wx.DefaultPosition, ID = -1, size=wx.DefaultSize,
147 style=0, autocomplete_mode = 'IPYTHON'):
148 '''
149 Initialize console view.
150
151 @param parent: Parent widget
152 @param prompt: User specified prompt
153 @type intro: string
154 @param intro: User specified startup introduction string
155 @type intro: string
156 @param background_color: Can be BLACK or WHITE
157 @type background_color: string
158 @param other: init param of styledTextControl (can be used as-is)
159 @param autocomplete_mode: Can be 'IPYTHON' or 'STC'
160 'IPYTHON' show autocompletion the ipython way
161 'STC" show it scintilla text control way
162 '''
163 stc.StyledTextCtrl.__init__(self, parent, ID, pos, size, style)
164
165 ####### Scintilla configuration ###################################
166
167 # Ctrl + B or Ctrl + N can be used to zoomin/zoomout the text inside
168 # the widget
169 self.CmdKeyAssign(ord('B'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMIN)
170 self.CmdKeyAssign(ord('N'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMOUT)
171
172 #We draw a line at position 80
173 self.SetEdgeMode(stc.STC_EDGE_LINE)
174 self.SetEdgeColumn(80)
175 self.SetEdgeColour(wx.LIGHT_GREY)
176
177 #self.SetViewWhiteSpace(True)
178 #self.SetViewEOL(True)
179 self.SetEOLMode(stc.STC_EOL_CRLF)
180 #self.SetWrapMode(stc.STC_WRAP_CHAR)
181 #self.SetWrapMode(stc.STC_WRAP_WORD)
182 self.SetBufferedDraw(True)
183 #self.SetUseAntiAliasing(True)
184 self.SetLayoutCache(stc.STC_CACHE_PAGE)
185 self.SetUndoCollection(False)
186 self.SetUseTabs(True)
187 self.SetIndent(4)
188 self.SetTabWidth(4)
189
190 self.EnsureCaretVisible()
191
192 self.SetMargins(3, 3) #text is moved away from border with 3px
193 # Suppressing Scintilla margins
194 self.SetMarginWidth(0, 0)
195 self.SetMarginWidth(1, 0)
196 self.SetMarginWidth(2, 0)
197
198 self.background_color = background_color
199 self.buildStyles()
200
201 self.indent = 0
202 self.prompt_count = 0
203 self.color_pat = re.compile('\x01?\x1b\[(.*?)m\x02?')
204
205 self.write(intro)
206 self.setPrompt(prompt)
207 self.showPrompt()
208
209 self.autocomplete_mode = autocomplete_mode
210
211 self.Bind(wx.EVT_KEY_DOWN, self._onKeypress)
212
213 def buildStyles(self):
214 #we define platform specific fonts
215 if wx.Platform == '__WXMSW__':
216 faces = { 'times': 'Times New Roman',
217 'mono' : 'Courier New',
218 'helv' : 'Arial',
219 'other': 'Comic Sans MS',
220 'size' : 10,
221 'size2': 8,
222 }
223 elif wx.Platform == '__WXMAC__':
224 faces = { 'times': 'Times New Roman',
225 'mono' : 'Monaco',
226 'helv' : 'Arial',
227 'other': 'Comic Sans MS',
228 'size' : 10,
229 'size2': 8,
230 }
231 else:
232 faces = { 'times': 'Times',
233 'mono' : 'Courier',
234 'helv' : 'Helvetica',
235 'other': 'new century schoolbook',
236 'size' : 10,
237 'size2': 8,
238 }
239
240 # make some styles
241 if self.background_color != "BLACK":
242 self.background_color = "WHITE"
243 self.SetCaretForeground("BLACK")
244 self.ANSI_STYLES = self.ANSI_STYLES_WHITE
245 else:
246 self.SetCaretForeground("WHITE")
247 self.ANSI_STYLES = self.ANSI_STYLES_BLACK
248
249 self.StyleSetSpec(stc.STC_STYLE_DEFAULT,
250 "fore:%s,back:%s,size:%d,face:%s"
251 % (self.ANSI_STYLES['0;30'][1],
252 self.background_color,
253 faces['size'], faces['mono']))
254 self.StyleClearAll()
255 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT,
256 "fore:#FF0000,back:#0000FF,bold")
257 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD,
258 "fore:#000000,back:#FF0000,bold")
259
260 for style in self.ANSI_STYLES.values():
261 self.StyleSetSpec(style[0], "bold,fore:%s" % style[1])
262
263 #######################################################################
264
265 def setBackgroundColor(self, color):
266 self.background_color = color
267 self.buildStyles()
268
269 def getBackgroundColor(self, color):
270 return self.background_color
271
272 def asyncWrite(self, text):
273 '''
274 Write given text to buffer in an asynchroneous way.
275 It is used from another thread to be able to acces the GUI.
276 @param text: Text to append
277 @type text: string
278 '''
279 try:
280 wx.MutexGuiEnter()
281
282 #be sure not to be interrutpted before the MutexGuiLeave!
283 self.write(text)
284
285 except KeyboardInterrupt:
286 wx.MutexGuiLeave()
287 raise KeyboardInterrupt
288 wx.MutexGuiLeave()
289
290
291 def write(self, text):
292 '''
293 Write given text to buffer.
294
295 @param text: Text to append.
296 @type text: string
297 '''
298 segments = self.color_pat.split(text)
299 segment = segments.pop(0)
300 self.StartStyling(self.getCurrentLineEnd(), 0xFF)
301 self.AppendText(segment)
302
303 if segments:
304 ansi_tags = self.color_pat.findall(text)
305
306 for tag in ansi_tags:
307 i = segments.index(tag)
308 self.StartStyling(self.getCurrentLineEnd(), 0xFF)
309 self.AppendText(segments[i+1])
310
311 if tag != '0':
312 self.SetStyling(len(segments[i+1]), self.ANSI_STYLES[tag][0])
313
314 segments.pop(i)
315
316 self.moveCursor(self.getCurrentLineEnd())
317
318 def getPromptLen(self):
319 '''
320 Return the length of current prompt
321 '''
322 return len(str(self.prompt_count)) + 7
323
324 def setPrompt(self, prompt):
325 self.prompt = prompt
326
327 def setIndentation(self, indentation):
328 self.indent = indentation
329
330 def setPromptCount(self, count):
331 self.prompt_count = count
332
333 def showPrompt(self):
334 '''
335 Prints prompt at start of line.
336
337 @param prompt: Prompt to print.
338 @type prompt: string
339 '''
340 self.write(self.prompt)
341 #now we update the position of end of prompt
342 self.current_start = self.getCurrentLineEnd()
343
344 autoindent = self.indent*' '
345 autoindent = autoindent.replace(' ','\t')
346 self.write(autoindent)
347
348 def changeLine(self, text):
349 '''
350 Replace currently entered command line with given text.
351
352 @param text: Text to use as replacement.
353 @type text: string
354 '''
355 self.SetSelection(self.getCurrentPromptStart(), self.getCurrentLineEnd())
356 self.ReplaceSelection(text)
357 self.moveCursor(self.getCurrentLineEnd())
358
359 def getCurrentPromptStart(self):
360 return self.current_start
361
362 def getCurrentLineStart(self):
363 return self.GotoLine(self.LineFromPosition(self.GetCurrentPos()))
364
365 def getCurrentLineEnd(self):
366 return self.GetLength()
367
368 def getCurrentLine(self):
369 '''
370 Get text in current command line.
371
372 @return: Text of current command line.
373 @rtype: string
374 '''
375 return self.GetTextRange(self.getCurrentPromptStart(),
376 self.getCurrentLineEnd())
377
378 def moveCursorOnNewValidKey(self):
379 #If cursor is at wrong position put it at last line...
380 if self.GetCurrentPos() < self.getCurrentPromptStart():
381 self.GotoPos(self.getCurrentPromptStart())
382
383 def removeFromTo(self, from_pos, to_pos):
384 if from_pos < to_pos:
385 self.SetSelection(from_pos, to_pos)
386 self.DeleteBack()
387
388 def removeCurrentLine(self):
389 self.LineDelete()
390
391 def moveCursor(self, position):
392 self.GotoPos(position)
393
394 def getCursorPos(self):
395 return self.GetCurrentPos()
396
397 def selectFromTo(self, from_pos, to_pos):
398 self.SetSelectionStart(from_pos)
399 self.SetSelectionEnd(to_pos)
400
401 def writeHistory(self, history):
402 self.removeFromTo(self.getCurrentPromptStart(), self.getCurrentLineEnd())
403 self.changeLine(history)
404
405 def setCompletionMethod(self, completion):
406 if completion in ['IPYTHON', 'STC']:
407 self.autocomplete_mode = completion
408 else:
409 raise AttributeError
410
411 def getCompletionMethod(self, completion):
412 return self.autocomplete_mode
413
414 def writeCompletion(self, possibilities):
415 if self.autocomplete_mode == 'IPYTHON':
416 max_len = len(max(possibilities, key=len))
417 max_symbol = ' '*max_len
418
419 #now we check how much symbol we can put on a line...
420 test_buffer = max_symbol + ' '*4
421
422 allowed_symbols = 80/len(test_buffer)
423 if allowed_symbols == 0:
424 allowed_symbols = 1
425
426 pos = 1
427 buf = ''
428 for symbol in possibilities:
429 #buf += symbol+'\n'#*spaces)
430 if pos < allowed_symbols:
431 spaces = max_len - len(symbol) + 4
432 buf += symbol+' '*spaces
433 pos += 1
434 else:
435 buf += symbol+'\n'
436 pos = 1
437 self.write(buf)
438 else:
439 possibilities.sort() # Python sorts are case sensitive
440 self.AutoCompSetIgnoreCase(False)
441 self.AutoCompSetAutoHide(False)
442 #let compute the length ot last word
443 splitter = [' ', '(', '[', '{','=']
444 last_word = self.getCurrentLine()
445 for breaker in splitter:
446 last_word = last_word.split(breaker)[-1]
447 self.AutoCompShow(len(last_word), " ".join(possibilities))
448
449 def _onKeypress(self, event, skip=True):
450 '''
451 Key press callback used for correcting behavior for console-like
452 interfaces. For example 'home' should go to prompt, not to begining of
453 line.
454
455 @param widget: Widget that key press accored in.
456 @type widget: gtk.Widget
457 @param event: Event object
458 @type event: gtk.gdk.Event
459
460 @return: Return True if event as been catched.
461 @rtype: boolean
462 '''
463 if not self.AutoCompActive():
464 if event.GetKeyCode() == wx.WXK_HOME:
465 if event.Modifiers == wx.MOD_NONE:
466 self.moveCursorOnNewValidKey()
467 self.moveCursor(self.getCurrentPromptStart())
468 return True
469 elif event.Modifiers == wx.MOD_SHIFT:
470 self.moveCursorOnNewValidKey()
471 self.selectFromTo(self.getCurrentPromptStart(), self.getCursorPos())
472 return True
473 else:
474 return False
475
476 elif event.GetKeyCode() == wx.WXK_LEFT:
477 if event.Modifiers == wx.MOD_NONE:
478 self.moveCursorOnNewValidKey()
479
480 self.moveCursor(self.getCursorPos()-1)
481 if self.getCursorPos() < self.getCurrentPromptStart():
482 self.moveCursor(self.getCurrentPromptStart())
483 return True
484
485 elif event.GetKeyCode() == wx.WXK_BACK:
486 self.moveCursorOnNewValidKey()
487 if self.getCursorPos() > self.getCurrentPromptStart():
488 event.Skip()
489 return True
490
491 if skip:
492 if event.GetKeyCode() not in [wx.WXK_PAGEUP, wx.WXK_PAGEDOWN]\
493 and event.Modifiers == wx.MOD_NONE:
494 self.moveCursorOnNewValidKey()
495
496 event.Skip()
497 return True
498 return False
499 else:
500 event.Skip()
501
502 def OnUpdateUI(self, evt):
503 # check for matching braces
504 braceAtCaret = -1
505 braceOpposite = -1
506 charBefore = None
507 caretPos = self.GetCurrentPos()
508
509 if caretPos > 0:
510 charBefore = self.GetCharAt(caretPos - 1)
511 styleBefore = self.GetStyleAt(caretPos - 1)
512
513 # check before
514 if charBefore and chr(charBefore) in "[]{}()" and styleBefore == stc.STC_P_OPERATOR:
515 braceAtCaret = caretPos - 1
516
517 # check after
518 if braceAtCaret < 0:
519 charAfter = self.GetCharAt(caretPos)
520 styleAfter = self.GetStyleAt(caretPos)
521
522 if charAfter and chr(charAfter) in "[]{}()" and styleAfter == stc.STC_P_OPERATOR:
523 braceAtCaret = caretPos
524
525 if braceAtCaret >= 0:
526 braceOpposite = self.BraceMatch(braceAtCaret)
527
528 if braceAtCaret != -1 and braceOpposite == -1:
529 self.BraceBadLight(braceAtCaret)
530 else:
531 self.BraceHighlight(braceAtCaret, braceOpposite)
532 #pt = self.PointFromPosition(braceOpposite)
533 #self.Refresh(True, wxRect(pt.x, pt.y, 5,5))
534 #print pt
535 #self.Refresh(False)
536
537 class IPShellWidget(wx.Panel):
538 '''
539 This is wx.Panel that embbed the IPython Thread and the wx.StyledTextControl
540 If you want to port this to any other GUI toolkit, just replace the
541 WxConsoleView by YOURGUIConsoleView and make YOURGUIIPythonView derivate
542 from whatever container you want. I've choosed to derivate from a wx.Panel
543 because it seems to be more useful
544 Any idea to make it more 'generic' welcomed.
545 '''
546
547 def __init__(self, parent, intro=None,
548 background_color="BLACK", add_button_handler=None,
549 wx_ip_shell=None, user_ns={},user_global_ns=None,
550 ):
551 '''
552 Initialize.
553 Instanciate an IPython thread.
554 Instanciate a WxConsoleView.
555 Redirect I/O to console.
556 '''
557 wx.Panel.__init__(self,parent,wx.ID_ANY)
558
559 self.parent = parent
560 ### IPython non blocking shell instanciation ###
561 self.cout = StringIO()
562 self.add_button_handler = add_button_handler
563
564 if wx_ip_shell is not None:
565 self.IP = wx_ip_shell
566 else:
567 self.IP = WxNonBlockingIPShell(self,
568 cout = self.cout, cerr = self.cout,
569 ask_exit_handler = self.askExitCallback)
570
571 ### IPython wx console view instanciation ###
572 #If user didn't defined an intro text, we create one for him
573 #If you really wnat an empty intro just call wxIPythonViewPanel
574 #with intro=''
575 if intro is None:
576 welcome_text = "Welcome to WxIPython Shell.\n\n"
577 welcome_text+= self.IP.get_banner()
578 welcome_text+= "!command -> Execute command in shell\n"
579 welcome_text+= "TAB -> Autocompletion\n"
580 else:
581 welcome_text = intro
582
583 self.text_ctrl = WxConsoleView(self,
584 self.IP.get_prompt(),
585 intro=welcome_text,
586 background_color=background_color)
587
588 option_text = wx.StaticText(self, -1, "Options:")
589 self.completion_option = wx.CheckBox(self, -1, "Scintilla Completion")
590 self.completion_option.SetToolTip(wx.ToolTip(
591 "Selects the completion type:\nEither Ipython default style or Scintilla one"))
592 #self.completion_option.SetValue(False)
593 self.background_option = wx.CheckBox(self, -1, "White Background")
594 self.background_option.SetToolTip(wx.ToolTip(
595 "Selects the back ground color: BLACK or WHITE"))
596 #self.background_option.SetValue(False)
597 self.threading_option = wx.CheckBox(self, -1, "Execute in thread")
598 self.threading_option.SetToolTip(wx.ToolTip(
599 "Use threading: infinite loop don't freeze the GUI and commands can be breaked\nNo threading: maximum compatibility"))
600 #self.threading_option.SetValue(False)
601
602 self.options={'completion':{'value':'IPYTHON',
603 'checkbox':self.completion_option,'STC':True,'IPYTHON':False,
604 'setfunc':self.text_ctrl.setCompletionMethod},
605 'background_color':{'value':'BLACK',
606 'checkbox':self.background_option,'WHITE':True,'BLACK':False,
607 'setfunc':self.text_ctrl.setBackgroundColor},
608 'threading':{'value':'True',
609 'checkbox':self.threading_option,'True':True,'False':False,
610 'setfunc':self.IP.set_threading},
611 }
612
613 #self.cout.write dEfault option is asynchroneous because default sate is threading ON
614 self.cout.write = self.text_ctrl.asyncWrite
615 #we reloard options
616 self.reloadOptions(self.options)
617
618 self.text_ctrl.Bind(wx.EVT_KEY_DOWN, self.keyPress)
619 self.completion_option.Bind(wx.EVT_CHECKBOX, self.evtCheckOptionCompletion)
620 self.background_option.Bind(wx.EVT_CHECKBOX, self.evtCheckOptionBackgroundColor)
621 self.threading_option.Bind(wx.EVT_CHECKBOX, self.evtCheckOptionThreading)
622
623 ### making the layout of the panel ###
624 sizer = wx.BoxSizer(wx.VERTICAL)
625 sizer.Add(self.text_ctrl, 1, wx.EXPAND)
626 option_sizer = wx.BoxSizer(wx.HORIZONTAL)
627 sizer.Add(option_sizer, 0)
628 option_sizer.AddMany([(10, 20),
629 (option_text, 0, wx.ALIGN_CENTER_VERTICAL),
630 (5, 5),
631 (self.completion_option, 0, wx.ALIGN_CENTER_VERTICAL),
632 (8, 8),
633 (self.background_option, 0, wx.ALIGN_CENTER_VERTICAL),
634 (8, 8),
635 (self.threading_option, 0, wx.ALIGN_CENTER_VERTICAL)
636 ])
637 self.SetAutoLayout(True)
638 sizer.Fit(self)
639 sizer.SetSizeHints(self)
640 self.SetSizer(sizer)
641 #and we focus on the widget :)
642 self.SetFocus()
643
644 #widget state management (for key handling different cases)
645 self.setCurrentState('IDLE')
646 self.pager_state = 'DONE'
647 self.raw_input_current_line = 0
648
649 def askExitCallback(self, event):
650 self.askExitHandler(event)
651
652 #---------------------- IPython Thread Management ------------------------
653 def stateDoExecuteLine(self):
654 lines=self.text_ctrl.getCurrentLine()
655 self.text_ctrl.write('\n')
656 lines_to_execute = lines.replace('\t',' '*4)
657 lines_to_execute = lines_to_execute.replace('\r','')
658 self.IP.do_execute(lines_to_execute.encode(ENCODING))
659 self.updateHistoryTracker(lines)
660 if(self.text_ctrl.getCursorPos()!=0):
661 self.text_ctrl.removeCurrentLine()
662 self.setCurrentState('WAIT_END_OF_EXECUTION')
663
664 def evtStateExecuteDone(self,evt):
665 self.doc = self.IP.get_doc_text()
666 self.help = self.IP.get_help_text()
667 if self.doc:
668 self.pager_lines = self.doc[7:].split('\n')
669 self.pager_state = 'INIT'
670 self.setCurrentState('SHOW_DOC')
671 self.pager(self.doc)
672 elif self.help:
673 self.pager_lines = self.help.split('\n')
674 self.pager_state = 'INIT'
675 self.setCurrentState('SHOW_DOC')
676 self.pager(self.help)
677 else:
678 if(self.text_ctrl.getCursorPos()!=0):
679 self.text_ctrl.removeCurrentLine()
680 self.stateShowPrompt()
681
682 def stateShowPrompt(self):
683 self.setCurrentState('SHOW_PROMPT')
684 self.text_ctrl.setPrompt(self.IP.get_prompt())
685 self.text_ctrl.setIndentation(self.IP.get_indentation())
686 self.text_ctrl.setPromptCount(self.IP.get_prompt_count())
687 self.text_ctrl.showPrompt()
688 self.IP.init_history_index()
689 self.setCurrentState('IDLE')
690
691 def setCurrentState(self, state):
692 self.cur_state = state
693 self.updateStatusTracker(self.cur_state)
694
695 def pager(self,text):
696
697 if self.pager_state == 'INIT':
698 #print >>sys.__stdout__,"PAGER state:",self.pager_state
699 self.pager_nb_lines = len(self.pager_lines)
700 self.pager_index = 0
701 self.pager_do_remove = False
702 self.text_ctrl.write('\n')
703 self.pager_state = 'PROCESS_LINES'
704
705 if self.pager_state == 'PROCESS_LINES':
706 #print >>sys.__stdout__,"PAGER state:",self.pager_state
707 if self.pager_do_remove == True:
708 self.text_ctrl.removeCurrentLine()
709 self.pager_do_remove = False
710
711 if self.pager_nb_lines > 10:
712 #print >>sys.__stdout__,"PAGER processing 10 lines"
713 if self.pager_index > 0:
714 self.text_ctrl.write(">\x01\x1b[1;36m\x02"+self.pager_lines[self.pager_index]+'\n')
715 else:
716 self.text_ctrl.write("\x01\x1b[1;36m\x02 "+self.pager_lines[self.pager_index]+'\n')
717
718 for line in self.pager_lines[self.pager_index+1:self.pager_index+9]:
719 self.text_ctrl.write("\x01\x1b[1;36m\x02 "+line+'\n')
720 self.pager_index += 10
721 self.pager_nb_lines -= 10
722 self.text_ctrl.write("--- Push Enter to continue or 'Q' to quit---")
723 self.pager_do_remove = True
724 self.pager_state = 'WAITING'
725 return
726 else:
727 #print >>sys.__stdout__,"PAGER processing last lines"
728 if self.pager_nb_lines > 0:
729 if self.pager_index > 0:
730 self.text_ctrl.write(">\x01\x1b[1;36m\x02"+self.pager_lines[self.pager_index]+'\n')
731 else:
732 self.text_ctrl.write("\x01\x1b[1;36m\x02 "+self.pager_lines[self.pager_index]+'\n')
733
734 self.pager_index += 1
735 self.pager_nb_lines -= 1
736 if self.pager_nb_lines > 0:
737 for line in self.pager_lines[self.pager_index:]:
738 self.text_ctrl.write("\x01\x1b[1;36m\x02 "+line+'\n')
739 self.pager_nb_lines = 0
740 self.pager_state = 'DONE'
741 self.stateShowPrompt()
742
743 #------------------------ Key Handler ------------------------------------
744 def keyPress(self, event):
745 '''
746 Key press callback with plenty of shell goodness, like history,
747 autocompletions, etc.
748 '''
749 if event.GetKeyCode() == ord('C'):
750 if event.Modifiers == wx.MOD_CONTROL or event.Modifiers == wx.MOD_ALT:
751 if self.cur_state == 'WAIT_END_OF_EXECUTION':
752 #we raise an exception inside the IPython thread container
753 self.IP.ce.raise_exc(KeyboardInterrupt)
754 return
755
756 #let this before 'wx.WXK_RETURN' because we have to put 'IDLE'
757 #mode if AutoComp has been set as inactive
758 if self.cur_state == 'COMPLETING':
759 if not self.text_ctrl.AutoCompActive():
760 self.cur_state = 'IDLE'
761 else:
762 event.Skip()
763
764 if event.KeyCode == wx.WXK_RETURN:
765 if self.cur_state == 'IDLE':
766 #we change the state ot the state machine
767 self.setCurrentState('DO_EXECUTE_LINE')
768 self.stateDoExecuteLine()
769 return
770
771 if self.pager_state == 'WAITING':
772 self.pager_state = 'PROCESS_LINES'
773 self.pager(self.doc)
774 return
775
776 if self.cur_state == 'WAITING_USER_INPUT':
777 line=self.text_ctrl.getCurrentLine()
778 self.text_ctrl.write('\n')
779 self.setCurrentState('WAIT_END_OF_EXECUTION')
780 return
781
782 if event.GetKeyCode() in [ord('q'),ord('Q')]:
783 if self.pager_state == 'WAITING':
784 self.pager_state = 'DONE'
785 self.text_ctrl.write('\n')
786 self.stateShowPrompt()
787 return
788
789 if self.cur_state == 'WAITING_USER_INPUT':
790 event.Skip()
791
792 if self.cur_state == 'IDLE':
793 if event.KeyCode == wx.WXK_UP:
794 history = self.IP.history_back()
795 self.text_ctrl.writeHistory(history)
796 return
797 if event.KeyCode == wx.WXK_DOWN:
798 history = self.IP.history_forward()
799 self.text_ctrl.writeHistory(history)
800 return
801 if event.KeyCode == wx.WXK_TAB:
802 #if line empty we disable tab completion
803 if not self.text_ctrl.getCurrentLine().strip():
804 self.text_ctrl.write('\t')
805 return
806 completed, possibilities = self.IP.complete(self.text_ctrl.getCurrentLine())
807 if len(possibilities) > 1:
808 if self.text_ctrl.autocomplete_mode == 'IPYTHON':
809 cur_slice = self.text_ctrl.getCurrentLine()
810 self.text_ctrl.write('\n')
811 self.text_ctrl.writeCompletion(possibilities)
812 self.text_ctrl.write('\n')
813
814 self.text_ctrl.showPrompt()
815 self.text_ctrl.write(cur_slice)
816 self.text_ctrl.changeLine(completed or cur_slice)
817 else:
818 self.cur_state = 'COMPLETING'
819 self.text_ctrl.writeCompletion(possibilities)
820 else:
821 self.text_ctrl.changeLine(completed or cur_slice)
822 return
823 event.Skip()
824
825 #------------------------ Option Section ---------------------------------
826 def evtCheckOptionCompletion(self, event):
827 if event.IsChecked():
828 self.options['completion']['value']='STC'
829 else:
830 self.options['completion']['value']='IPYTHON'
831 self.text_ctrl.setCompletionMethod(self.options['completion']['value'])
832 self.updateOptionTracker('completion',
833 self.options['completion']['value'])
834 self.text_ctrl.SetFocus()
835
836 def evtCheckOptionBackgroundColor(self, event):
837 if event.IsChecked():
838 self.options['background_color']['value']='WHITE'
839 else:
840 self.options['background_color']['value']='BLACK'
841 self.text_ctrl.setBackgroundColor(self.options['background_color']['value'])
842 self.updateOptionTracker('background_color',
843 self.options['background_color']['value'])
844 self.text_ctrl.SetFocus()
845
846 def evtCheckOptionThreading(self, event):
847 if event.IsChecked():
848 self.options['threading']['value']='True'
849 self.IP.set_threading(True)
850 self.cout.write = self.text_ctrl.asyncWrite
851 else:
852 self.options['threading']['value']='False'
853 self.IP.set_threading(False)
854 self.cout.write = self.text_ctrl.write
855 self.updateOptionTracker('threading',
856 self.options['threading']['value'])
857 self.text_ctrl.SetFocus()
858
859 def getOptions(self):
860 return self.options
861
862 def reloadOptions(self,options):
863 self.options = options
864 for key in self.options.keys():
865 value = self.options[key]['value']
866 self.options[key]['checkbox'].SetValue(self.options[key][value])
867 self.options[key]['setfunc'](value)
868
869 if self.options['threading']['value']=='True':
870 self.IP.set_threading(True)
871 self.cout.write = self.text_ctrl.asyncWrite
872 else:
873 self.IP.set_threading(False)
874 self.cout.write = self.text_ctrl.write
875
876 #------------------------ Hook Section -----------------------------------
877 def updateOptionTracker(self,name,value):
878 '''
879 Default history tracker (does nothing)
880 '''
881 pass
882
883 def setOptionTrackerHook(self,func):
884 '''
885 Define a new history tracker
886 '''
887 self.updateOptionTracker = func
888
889 def updateHistoryTracker(self,command_line):
890 '''
891 Default history tracker (does nothing)
892 '''
893 pass
894
895 def setHistoryTrackerHook(self,func):
896 '''
897 Define a new history tracker
898 '''
899 self.updateHistoryTracker = func
900
901 def updateStatusTracker(self,status):
902 '''
903 Default status tracker (does nothing)
904 '''
905 pass
906
907 def setStatusTrackerHook(self,func):
908 '''
909 Define a new status tracker
910 '''
911 self.updateStatusTracker = func
912
913 def askExitHandler(self, event):
914 '''
915 Default exit handler
916 '''
917 self.text_ctrl.write('\nExit callback has not been set.')
918
919 def setAskExitHandler(self, func):
920 '''
921 Define an exit handler
922 '''
923 self.askExitHandler = func
924
925 if __name__ == '__main__':
926 # Some simple code to test the shell widget.
927 class MainWindow(wx.Frame):
928 def __init__(self, parent, id, title):
929 wx.Frame.__init__(self, parent, id, title, size=(300,250))
930 self._sizer = wx.BoxSizer(wx.VERTICAL)
931 self.shell = IPShellWidget(self)
932 self._sizer.Add(self.shell, 1, wx.EXPAND)
933 self.SetSizer(self._sizer)
934 self.SetAutoLayout(1)
935 self.Show(True)
936
937 app = wx.PySimpleApp()
938 frame = MainWindow(None, wx.ID_ANY, 'Ipython')
939 frame.SetSize((780, 460))
940 shell = frame.shell
941
942 app.MainLoop()
@@ -1,50 +0,0 b''
1 """
2 Thread subclass that can deal with asynchronously function calls via
3 raise_exc.
4 """
5
6 import threading
7 import inspect
8 import ctypes
9
10
11 def _async_raise(tid, exctype):
12 """raises the exception, performs cleanup if needed"""
13 if not inspect.isclass(exctype):
14 raise TypeError("Only types can be raised (not instances)")
15 res = ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, ctypes.py_object(exctype))
16 if res == 0:
17 raise ValueError("invalid thread id")
18 elif res != 1:
19 # """if it returns a number greater than one, you're in trouble,
20 # and you should call it again with exc=NULL to revert the effect"""
21 ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, 0)
22 raise SystemError("PyThreadState_SetAsyncExc failed")
23
24
25 class ThreadEx(threading.Thread):
26 def _get_my_tid(self):
27 """determines this (self's) thread id"""
28 if not self.isAlive():
29 raise threading.ThreadError("the thread is not active")
30
31 # do we have it cached?
32 if hasattr(self, "_thread_id"):
33 return self._thread_id
34
35 # no, look for it in the _active dict
36 for tid, tobj in threading._active.items():
37 if tobj is self:
38 self._thread_id = tid
39 return tid
40
41 raise AssertionError("could not determine the thread's id")
42
43 def raise_exc(self, exctype):
44 """raises the given exception type in the context of this thread"""
45 _async_raise(self._get_my_tid(), exctype)
46
47 def kill(self):
48 """raises SystemExit in the context of the given thread, which should
49 cause the thread to exit silently (unless caught)"""
50 self.raise_exc(SystemExit)
@@ -1,266 +0,0 b''
1 #!/usr/bin/python
2 # -*- coding: iso-8859-15 -*-
3
4 import wx.aui
5 import sys
6 #used for about dialog
7 from wx.lib.wordwrap import wordwrap
8
9 #used for ipython GUI objects
10 from IPython.gui.wx.ipython_view import IPShellWidget
11 from IPython.gui.wx.ipython_history import IPythonHistoryPanel
12
13 #used to invoke ipython1 wx implementation
14 ### FIXME ### temporary disabled due to interference with 'show_in_pager' hook
15 is_sync_frontend_ok = False
16 try:
17 from IPython.frontend.wx.ipythonx import IPythonXController
18 except ImportError:
19 is_sync_frontend_ok = False
20
21 #used to create options.conf file in user directory
22 from IPython.core.ipapi import get
23
24 __version__ = 0.91
25 __author__ = "Laurent Dufrechou"
26 __email__ = "laurent.dufrechou _at_ gmail.com"
27 __license__ = "BSD"
28
29 #-----------------------------------------
30 # Creating one main frame for our
31 # application with movables windows
32 #-----------------------------------------
33 class MyFrame(wx.Frame):
34 """Creating one main frame for our
35 application with movables windows"""
36 def __init__(self, parent=None, id=-1, title="WxIPython",
37 pos=wx.DefaultPosition,
38 size=(800, 600), style=wx.DEFAULT_FRAME_STYLE, sync_ok=False):
39 wx.Frame.__init__(self, parent, id, title, pos, size, style)
40 self._mgr = wx.aui.AuiManager()
41
42 # notify PyAUI which frame to use
43 self._mgr.SetManagedWindow(self)
44
45 #create differents panels and make them persistant
46 self.history_panel = IPythonHistoryPanel(self)
47
48 self.history_panel.setOptionTrackerHook(self.optionSave)
49
50 self.ipython_panel = IPShellWidget(self,background_color = "BLACK")
51 #self.ipython_panel = IPShellWidget(self,background_color = "WHITE")
52 if(sync_ok):
53 self.ipython_panel2 = IPythonXController(self)
54 else:
55 self.ipython_panel2 = None
56 self.ipython_panel.setHistoryTrackerHook(self.history_panel.write)
57 self.ipython_panel.setStatusTrackerHook(self.updateStatus)
58 self.ipython_panel.setAskExitHandler(self.OnExitDlg)
59 self.ipython_panel.setOptionTrackerHook(self.optionSave)
60
61 #Create a notebook to display different IPython shell implementations
62 self.nb = wx.aui.AuiNotebook(self)
63
64 self.optionLoad()
65
66 self.statusbar = self.createStatus()
67 self.createMenu()
68
69 ########################################################################
70 ### add the panes to the manager
71 # main panels
72 self._mgr.AddPane(self.nb , wx.CENTER, "IPython Shells")
73 self.nb.AddPage(self.ipython_panel , "IPython0 Shell")
74 if(sync_ok):
75 self.nb.AddPage(self.ipython_panel2, "IPython1 Synchroneous Shell")
76
77 self._mgr.AddPane(self.history_panel , wx.RIGHT, "IPython history")
78
79 # now we specify some panel characteristics
80 self._mgr.GetPane(self.ipython_panel).CaptionVisible(True);
81 self._mgr.GetPane(self.history_panel).CaptionVisible(True);
82 self._mgr.GetPane(self.history_panel).MinSize((200,400));
83
84 # tell the manager to "commit" all the changes just made
85 self._mgr.Update()
86
87 #global event handling
88 self.Bind(wx.EVT_CLOSE, self.OnClose)
89 self.Bind(wx.EVT_MENU, self.OnClose,id=wx.ID_EXIT)
90 self.Bind(wx.EVT_MENU, self.OnShowIPythonPanel,id=wx.ID_HIGHEST+1)
91 self.Bind(wx.EVT_MENU, self.OnShowHistoryPanel,id=wx.ID_HIGHEST+2)
92 self.Bind(wx.EVT_MENU, self.OnShowAbout, id=wx.ID_HIGHEST+3)
93 self.Bind(wx.EVT_MENU, self.OnShowAllPanel,id=wx.ID_HIGHEST+6)
94
95 warn_text = 'Hello from IPython and wxPython.\n'
96 warn_text +='Please Note that this work is still EXPERIMENTAL\n'
97 warn_text +='It does NOT emulate currently all the IPython functions.\n'
98 warn_text +="\nIf you use MATPLOTLIB with show() you'll need to deactivate the THREADING option.\n"
99 if(not sync_ok):
100 warn_text +="\n->No twisted package detected, IPython1 example deactivated."
101
102 dlg = wx.MessageDialog(self,
103 warn_text,
104 'Warning Box',
105 wx.OK | wx.ICON_INFORMATION
106 )
107 dlg.ShowModal()
108 dlg.Destroy()
109
110 def optionSave(self, name, value):
111 ip = get()
112 path = ip.ipython_dir
113 opt = open(path + '/options.conf','w')
114
115 try:
116 options_ipython_panel = self.ipython_panel.getOptions()
117 options_history_panel = self.history_panel.getOptions()
118
119 for key in options_ipython_panel.keys():
120 opt.write(key + '=' + options_ipython_panel[key]['value']+'\n')
121 for key in options_history_panel.keys():
122 opt.write(key + '=' + options_history_panel[key]['value']+'\n')
123 finally:
124 opt.close()
125
126 def optionLoad(self):
127 try:
128 ip = get()
129 path = ip.ipython_dir
130 opt = open(path + '/options.conf','r')
131 lines = opt.readlines()
132 opt.close()
133
134 options_ipython_panel = self.ipython_panel.getOptions()
135 options_history_panel = self.history_panel.getOptions()
136
137 for line in lines:
138 key = line.split('=')[0]
139 value = line.split('=')[1].replace('\n','').replace('\r','')
140 if key in options_ipython_panel.keys():
141 options_ipython_panel[key]['value'] = value
142 elif key in options_history_panel.keys():
143 options_history_panel[key]['value'] = value
144 else:
145 print >>sys.__stdout__,"Warning: key ",key,"not found in widget options. Check Options.conf"
146 self.ipython_panel.reloadOptions(options_ipython_panel)
147 self.history_panel.reloadOptions(options_history_panel)
148
149 except IOError:
150 print >>sys.__stdout__,"Could not open Options.conf, defaulting to default values."
151
152
153 def createMenu(self):
154 """local method used to create one menu bar"""
155
156 mb = wx.MenuBar()
157
158 file_menu = wx.Menu()
159 file_menu.Append(wx.ID_EXIT, "Exit")
160
161 view_menu = wx.Menu()
162 view_menu.Append(wx.ID_HIGHEST+1, "Show IPython Panel")
163 view_menu.Append(wx.ID_HIGHEST+2, "Show History Panel")
164 view_menu.AppendSeparator()
165 view_menu.Append(wx.ID_HIGHEST+6, "Show All")
166
167 about_menu = wx.Menu()
168 about_menu.Append(wx.ID_HIGHEST+3, "About")
169
170 mb.Append(file_menu, "File")
171 mb.Append(view_menu, "View")
172 mb.Append(about_menu, "About")
173 #mb.Append(options_menu, "Options")
174
175 self.SetMenuBar(mb)
176
177 def createStatus(self):
178 statusbar = self.CreateStatusBar(2, wx.ST_SIZEGRIP)
179 statusbar.SetStatusWidths([-2, -3])
180 statusbar.SetStatusText("Ready", 0)
181 statusbar.SetStatusText("WxIPython "+str(__version__), 1)
182 return statusbar
183
184 def updateStatus(self,text):
185 states = {'IDLE':'Idle',
186 'DO_EXECUTE_LINE':'Send command',
187 'WAIT_END_OF_EXECUTION':'Running command',
188 'WAITING_USER_INPUT':'Waiting user input',
189 'SHOW_DOC':'Showing doc',
190 'SHOW_PROMPT':'Showing prompt'}
191 self.statusbar.SetStatusText(states[text], 0)
192
193 def OnClose(self, event):
194 """#event used to close program """
195 # deinitialize the frame manager
196 self._mgr.UnInit()
197 self.Destroy()
198 event.Skip()
199
200 def OnExitDlg(self, event):
201 dlg = wx.MessageDialog(self, 'Are you sure you want to quit WxIPython',
202 'WxIPython exit',
203 wx.ICON_QUESTION |
204 wx.YES_NO | wx.NO_DEFAULT
205 )
206 if dlg.ShowModal() == wx.ID_YES:
207 dlg.Destroy()
208 self._mgr.UnInit()
209 self.Destroy()
210 dlg.Destroy()
211
212 #event to display IPython pannel
213 def OnShowIPythonPanel(self,event):
214 """ #event to display Boxpannel """
215 self._mgr.GetPane(self.ipython_panel).Show(True)
216 self._mgr.Update()
217 #event to display History pannel
218 def OnShowHistoryPanel(self,event):
219 self._mgr.GetPane(self.history_panel).Show(True)
220 self._mgr.Update()
221
222 def OnShowAllPanel(self,event):
223 """#event to display all Pannels"""
224 self._mgr.GetPane(self.ipython_panel).Show(True)
225 self._mgr.GetPane(self.history_panel).Show(True)
226 self._mgr.Update()
227
228 def OnShowAbout(self, event):
229 # First we create and fill the info object
230 info = wx.AboutDialogInfo()
231 info.Name = "WxIPython"
232 info.Version = str(__version__)
233 info.Copyright = "(C) 2007 Laurent Dufrechou"
234 info.Description = wordwrap(
235 "A Gui that embbed a multithreaded IPython Shell",
236 350, wx.ClientDC(self))
237 info.WebSite = ("http://ipython.scipy.org/", "IPython home page")
238 info.Developers = [ "Laurent Dufrechou" ]
239 licenseText="BSD License.\nAll rights reserved. This program and the accompanying materials are made available under the terms of the BSD which accompanies this distribution, and is available at http://www.opensource.org/licenses/bsd-license.php"
240 info.License = wordwrap(licenseText, 500, wx.ClientDC(self))
241
242 # Then we call wx.AboutBox giving it that info object
243 wx.AboutBox(info)
244
245 #-----------------------------------------
246 #Creating our application
247 #-----------------------------------------
248 class MyApp(wx.PySimpleApp):
249 """Creating our application"""
250 def __init__(self, sync_ok=False):
251 wx.PySimpleApp.__init__(self)
252
253 self.frame = MyFrame(sync_ok=sync_ok)
254 self.frame.Show()
255
256 #-----------------------------------------
257 #Main loop
258 #-----------------------------------------
259 def main():
260 app = MyApp(is_sync_frontend_ok)
261 app.SetTopWindow(app.frame)
262 app.MainLoop()
263
264 #if launched as main program run this
265 if __name__ == '__main__':
266 main()
This diff has been collapsed as it changes many lines, (1767 lines changed) Show them Hide them
@@ -1,1767 +0,0 b''
1 # -*- coding: iso-8859-1 -*-
2
3 import curses, fcntl, signal, struct, tty, textwrap, inspect
4
5 from IPython.core import ipapi
6
7 import astyle, ipipe
8
9
10 # Python 2.3 compatibility
11 try:
12 set
13 except NameError:
14 import sets
15 set = sets.Set
16
17 # Python 2.3 compatibility
18 try:
19 sorted
20 except NameError:
21 from ipipe import sorted
22
23
24 class UnassignedKeyError(Exception):
25 """
26 Exception that is used for reporting unassigned keys.
27 """
28
29
30 class UnknownCommandError(Exception):
31 """
32 Exception that is used for reporting unknown commands (this should never
33 happen).
34 """
35
36
37 class CommandError(Exception):
38 """
39 Exception that is used for reporting that a command can't be executed.
40 """
41
42
43 class Keymap(dict):
44 """
45 Stores mapping of keys to commands.
46 """
47 def __init__(self):
48 self._keymap = {}
49
50 def __setitem__(self, key, command):
51 if isinstance(key, str):
52 for c in key:
53 dict.__setitem__(self, ord(c), command)
54 else:
55 dict.__setitem__(self, key, command)
56
57 def __getitem__(self, key):
58 if isinstance(key, str):
59 key = ord(key)
60 return dict.__getitem__(self, key)
61
62 def __detitem__(self, key):
63 if isinstance(key, str):
64 key = ord(key)
65 dict.__detitem__(self, key)
66
67 def register(self, command, *keys):
68 for key in keys:
69 self[key] = command
70
71 def get(self, key, default=None):
72 if isinstance(key, str):
73 key = ord(key)
74 return dict.get(self, key, default)
75
76 def findkey(self, command, default=ipipe.noitem):
77 for (key, commandcandidate) in self.iteritems():
78 if commandcandidate == command:
79 return key
80 if default is ipipe.noitem:
81 raise KeyError(command)
82 return default
83
84
85 class _BrowserCachedItem(object):
86 # This is used internally by ``ibrowse`` to store a item together with its
87 # marked status.
88 __slots__ = ("item", "marked")
89
90 def __init__(self, item):
91 self.item = item
92 self.marked = False
93
94
95 class _BrowserHelp(object):
96 style_header = astyle.Style.fromstr("yellow:black:bold")
97 # This is used internally by ``ibrowse`` for displaying the help screen.
98 def __init__(self, browser):
99 self.browser = browser
100
101 def __xrepr__(self, mode):
102 yield (-1, True)
103 if mode == "header" or mode == "footer":
104 yield (astyle.style_default, "ibrowse help screen")
105 else:
106 yield (astyle.style_default, repr(self))
107
108 def __iter__(self):
109 # Get reverse key mapping
110 allkeys = {}
111 for (key, cmd) in self.browser.keymap.iteritems():
112 allkeys.setdefault(cmd, []).append(key)
113
114 fields = ("key", "description")
115
116 commands = []
117 for name in dir(self.browser):
118 if name.startswith("cmd_"):
119 command = getattr(self.browser, name)
120 commands.append((inspect.getsourcelines(command)[-1], name[4:], command))
121 commands.sort()
122 commands = [(c[1], c[2]) for c in commands]
123 for (i, (name, command)) in enumerate(commands):
124 if i:
125 yield ipipe.Fields(fields, key="", description="")
126
127 description = command.__doc__
128 if description is None:
129 lines = []
130 else:
131 lines = [l.strip() for l in description.splitlines() if l.strip()]
132 description = "\n".join(lines)
133 lines = textwrap.wrap(description, 60)
134 keys = allkeys.get(name, [])
135
136 yield ipipe.Fields(fields, key="", description=astyle.Text((self.style_header, name)))
137 for i in xrange(max(len(keys), len(lines))):
138 try:
139 key = self.browser.keylabel(keys[i])
140 except IndexError:
141 key = ""
142 try:
143 line = lines[i]
144 except IndexError:
145 line = ""
146 yield ipipe.Fields(fields, key=key, description=line)
147
148
149 class _BrowserLevel(object):
150 # This is used internally to store the state (iterator, fetch items,
151 # position of cursor and screen, etc.) of one browser level
152 # An ``ibrowse`` object keeps multiple ``_BrowserLevel`` objects in
153 # a stack.
154 def __init__(self, browser, input, mainsizey, *attrs):
155 self.browser = browser
156 self.input = input
157 self.header = [x for x in ipipe.xrepr(input, "header") if not isinstance(x[0], int)]
158 # iterator for the input
159 self.iterator = ipipe.xiter(input)
160
161 # is the iterator exhausted?
162 self.exhausted = False
163
164 # attributes to be display (autodetected if empty)
165 self.attrs = attrs
166
167 # fetched items (+ marked flag)
168 self.items = ipipe.deque()
169
170 # Number of marked objects
171 self.marked = 0
172
173 # Vertical cursor position
174 self.cury = 0
175
176 # Horizontal cursor position
177 self.curx = 0
178
179 # Index of first data column
180 self.datastartx = 0
181
182 # Index of first data line
183 self.datastarty = 0
184
185 # height of the data display area
186 self.mainsizey = mainsizey
187
188 # width of the data display area (changes when scrolling)
189 self.mainsizex = 0
190
191 # Size of row number (changes when scrolling)
192 self.numbersizex = 0
193
194 # Attributes to display (in this order)
195 self.displayattrs = []
196
197 # index and attribute under the cursor
198 self.displayattr = (None, ipipe.noitem)
199
200 # Maps attributes to column widths
201 self.colwidths = {}
202
203 # Set of hidden attributes
204 self.hiddenattrs = set()
205
206 # This takes care of all the caches etc.
207 self.moveto(0, 0, refresh=True)
208
209 def fetch(self, count):
210 # Try to fill ``self.items`` with at least ``count`` objects.
211 have = len(self.items)
212 while not self.exhausted and have < count:
213 try:
214 item = self.iterator.next()
215 except StopIteration:
216 self.exhausted = True
217 break
218 except (KeyboardInterrupt, SystemExit):
219 raise
220 except Exception as exc:
221 have += 1
222 self.items.append(_BrowserCachedItem(exc))
223 self.exhausted = True
224 break
225 else:
226 have += 1
227 self.items.append(_BrowserCachedItem(item))
228
229 def calcdisplayattrs(self):
230 # Calculate which attributes are available from the objects that are
231 # currently visible on screen (and store it in ``self.displayattrs``)
232
233 attrs = set()
234 self.displayattrs = []
235 if self.attrs:
236 # If the browser object specifies a fixed list of attributes,
237 # simply use it (removing hidden attributes).
238 for attr in self.attrs:
239 attr = ipipe.upgradexattr(attr)
240 if attr not in attrs and attr not in self.hiddenattrs:
241 self.displayattrs.append(attr)
242 attrs.add(attr)
243 else:
244 endy = min(self.datastarty+self.mainsizey, len(self.items))
245 for i in xrange(self.datastarty, endy):
246 for attr in ipipe.xattrs(self.items[i].item, "default"):
247 if attr not in attrs and attr not in self.hiddenattrs:
248 self.displayattrs.append(attr)
249 attrs.add(attr)
250
251 def getrow(self, i):
252 # Return a dictionary with the attributes for the object
253 # ``self.items[i]``. Attribute names are taken from
254 # ``self.displayattrs`` so ``calcdisplayattrs()`` must have been
255 # called before.
256 row = {}
257 item = self.items[i].item
258 for attr in self.displayattrs:
259 try:
260 value = attr.value(item)
261 except (KeyboardInterrupt, SystemExit):
262 raise
263 except Exception as exc:
264 value = exc
265 # only store attribute if it exists (or we got an exception)
266 if value is not ipipe.noitem:
267 # remember alignment, length and colored text
268 row[attr] = ipipe.xformat(value, "cell", self.browser.maxattrlength)
269 return row
270
271 def calcwidths(self):
272 # Recalculate the displayed fields and their widths.
273 # ``calcdisplayattrs()'' must have been called and the cache
274 # for attributes of the objects on screen (``self.displayrows``)
275 # must have been filled. This sets ``self.colwidths`` which maps
276 # attribute descriptors to widths.
277 self.colwidths = {}
278 for row in self.displayrows:
279 for attr in self.displayattrs:
280 try:
281 length = row[attr][1]
282 except KeyError:
283 length = 0
284 # always add attribute to colwidths, even if it doesn't exist
285 if attr not in self.colwidths:
286 self.colwidths[attr] = len(attr.name())
287 newwidth = max(self.colwidths[attr], length)
288 self.colwidths[attr] = newwidth
289
290 # How many characters do we need to paint the largest item number?
291 self.numbersizex = len(str(self.datastarty+self.mainsizey-1))
292 # How must space have we got to display data?
293 self.mainsizex = self.browser.scrsizex-self.numbersizex-3
294 # width of all columns
295 self.datasizex = sum(self.colwidths.itervalues()) + len(self.colwidths)
296
297 def calcdisplayattr(self):
298 # Find out which attribute the cursor is on and store this
299 # information in ``self.displayattr``.
300 pos = 0
301 for (i, attr) in enumerate(self.displayattrs):
302 if pos+self.colwidths[attr] >= self.curx:
303 self.displayattr = (i, attr)
304 break
305 pos += self.colwidths[attr]+1
306 else:
307 self.displayattr = (None, ipipe.noitem)
308
309 def moveto(self, x, y, refresh=False):
310 # Move the cursor to the position ``(x,y)`` (in data coordinates,
311 # not in screen coordinates). If ``refresh`` is true, all cached
312 # values will be recalculated (e.g. because the list has been
313 # resorted, so screen positions etc. are no longer valid).
314 olddatastarty = self.datastarty
315 oldx = self.curx
316 oldy = self.cury
317 x = int(x+0.5)
318 y = int(y+0.5)
319 newx = x # remember where we wanted to move
320 newy = y # remember where we wanted to move
321
322 scrollbordery = min(self.browser.scrollbordery, self.mainsizey//2)
323 scrollborderx = min(self.browser.scrollborderx, self.mainsizex//2)
324
325 # Make sure that the cursor didn't leave the main area vertically
326 if y < 0:
327 y = 0
328 # try to get enough items to fill the screen
329 self.fetch(max(y+scrollbordery+1, self.mainsizey))
330 if y >= len(self.items):
331 y = max(0, len(self.items)-1)
332
333 # Make sure that the cursor stays on screen vertically
334 if y < self.datastarty+scrollbordery:
335 self.datastarty = max(0, y-scrollbordery)
336 elif y >= self.datastarty+self.mainsizey-scrollbordery:
337 self.datastarty = max(0, min(y-self.mainsizey+scrollbordery+1,
338 len(self.items)-self.mainsizey))
339
340 if refresh: # Do we need to refresh the complete display?
341 self.calcdisplayattrs()
342 endy = min(self.datastarty+self.mainsizey, len(self.items))
343 self.displayrows = map(self.getrow, xrange(self.datastarty, endy))
344 self.calcwidths()
345 # Did we scroll vertically => update displayrows
346 # and various other attributes
347 elif self.datastarty != olddatastarty:
348 # Recalculate which attributes we have to display
349 olddisplayattrs = self.displayattrs
350 self.calcdisplayattrs()
351 # If there are new attributes, recreate the cache
352 if self.displayattrs != olddisplayattrs:
353 endy = min(self.datastarty+self.mainsizey, len(self.items))
354 self.displayrows = map(self.getrow, xrange(self.datastarty, endy))
355 elif self.datastarty<olddatastarty: # we did scroll up
356 # drop rows from the end
357 del self.displayrows[self.datastarty-olddatastarty:]
358 # fetch new items
359 for i in xrange(min(olddatastarty, self.datastarty+self.mainsizey)-1,
360 self.datastarty-1, -1):
361 try:
362 row = self.getrow(i)
363 except IndexError:
364 # we didn't have enough objects to fill the screen
365 break
366 self.displayrows.insert(0, row)
367 else: # we did scroll down
368 # drop rows from the start
369 del self.displayrows[:self.datastarty-olddatastarty]
370 # fetch new items
371 for i in xrange(max(olddatastarty+self.mainsizey, self.datastarty),
372 self.datastarty+self.mainsizey):
373 try:
374 row = self.getrow(i)
375 except IndexError:
376 # we didn't have enough objects to fill the screen
377 break
378 self.displayrows.append(row)
379 self.calcwidths()
380
381 # Make sure that the cursor didn't leave the data area horizontally
382 if x < 0:
383 x = 0
384 elif x >= self.datasizex:
385 x = max(0, self.datasizex-1)
386
387 # Make sure that the cursor stays on screen horizontally
388 if x < self.datastartx+scrollborderx:
389 self.datastartx = max(0, x-scrollborderx)
390 elif x >= self.datastartx+self.mainsizex-scrollborderx:
391 self.datastartx = max(0, min(x-self.mainsizex+scrollborderx+1,
392 self.datasizex-self.mainsizex))
393
394 if x == oldx and y == oldy and (x != newx or y != newy): # couldn't move
395 self.browser.beep()
396 else:
397 self.curx = x
398 self.cury = y
399 self.calcdisplayattr()
400
401 def sort(self, key, reverse=False):
402 """
403 Sort the currently list of items using the key function ``key``. If
404 ``reverse`` is true the sort order is reversed.
405 """
406 curitem = self.items[self.cury] # Remember where the cursor is now
407
408 # Sort items
409 def realkey(item):
410 return key(item.item)
411 self.items = ipipe.deque(sorted(self.items, key=realkey, reverse=reverse))
412
413 # Find out where the object under the cursor went
414 cury = self.cury
415 for (i, item) in enumerate(self.items):
416 if item is curitem:
417 cury = i
418 break
419
420 self.moveto(self.curx, cury, refresh=True)
421
422 def refresh(self):
423 """
424 Restart iterating the input.
425 """
426 self.iterator = ipipe.xiter(self.input)
427 self.items.clear()
428 self.exhausted = False
429 self.datastartx = self.datastarty = 0
430 self.moveto(0, 0, refresh=True)
431
432 def refreshfind(self):
433 """
434 Restart iterating the input and go back to the same object as before
435 (if it can be found in the new iterator).
436 """
437 try:
438 oldobject = self.items[self.cury].item
439 except IndexError:
440 oldobject = ipipe.noitem
441 self.iterator = ipipe.xiter(self.input)
442 self.items.clear()
443 self.exhausted = False
444 while True:
445 self.fetch(len(self.items)+1)
446 if self.exhausted:
447 curses.beep()
448 self.datastartx = self.datastarty = 0
449 self.moveto(self.curx, 0, refresh=True)
450 break
451 if self.items[-1].item == oldobject:
452 self.datastartx = self.datastarty = 0
453 self.moveto(self.curx, len(self.items)-1, refresh=True)
454 break
455
456
457 class _CommandInput(object):
458 keymap = Keymap()
459 keymap.register("left", curses.KEY_LEFT)
460 keymap.register("right", curses.KEY_RIGHT)
461 keymap.register("home", curses.KEY_HOME, "\x01") # Ctrl-A
462 keymap.register("end", curses.KEY_END, "\x05") # Ctrl-E
463 # FIXME: What's happening here?
464 keymap.register("backspace", curses.KEY_BACKSPACE, "\x08\x7f")
465 keymap.register("delete", curses.KEY_DC)
466 keymap.register("delend", 0x0b) # Ctrl-K
467 keymap.register("execute", "\r\n")
468 keymap.register("up", curses.KEY_UP)
469 keymap.register("down", curses.KEY_DOWN)
470 keymap.register("incsearchup", curses.KEY_PPAGE)
471 keymap.register("incsearchdown", curses.KEY_NPAGE)
472 keymap.register("exit", "\x18"), # Ctrl-X
473
474 def __init__(self, prompt):
475 self.prompt = prompt
476 self.history = []
477 self.maxhistory = 100
478 self.input = ""
479 self.curx = 0
480 self.cury = -1 # blank line
481
482 def start(self):
483 self.input = ""
484 self.curx = 0
485 self.cury = -1 # blank line
486
487 def handlekey(self, browser, key):
488 cmdname = self.keymap.get(key, None)
489 if cmdname is not None:
490 cmdfunc = getattr(self, "cmd_%s" % cmdname, None)
491 if cmdfunc is not None:
492 return cmdfunc(browser)
493 curses.beep()
494 elif key != -1:
495 try:
496 char = chr(key)
497 except ValueError:
498 curses.beep()
499 else:
500 return self.handlechar(browser, char)
501
502 def handlechar(self, browser, char):
503 self.input = self.input[:self.curx] + char + self.input[self.curx:]
504 self.curx += 1
505 return True
506
507 def dohistory(self):
508 self.history.insert(0, self.input)
509 del self.history[:-self.maxhistory]
510
511 def cmd_backspace(self, browser):
512 if self.curx:
513 self.input = self.input[:self.curx-1] + self.input[self.curx:]
514 self.curx -= 1
515 return True
516 else:
517 curses.beep()
518
519 def cmd_delete(self, browser):
520 if self.curx<len(self.input):
521 self.input = self.input[:self.curx] + self.input[self.curx+1:]
522 return True
523 else:
524 curses.beep()
525
526 def cmd_delend(self, browser):
527 if self.curx<len(self.input):
528 self.input = self.input[:self.curx]
529 return True
530
531 def cmd_left(self, browser):
532 if self.curx:
533 self.curx -= 1
534 return True
535 else:
536 curses.beep()
537
538 def cmd_right(self, browser):
539 if self.curx < len(self.input):
540 self.curx += 1
541 return True
542 else:
543 curses.beep()
544
545 def cmd_home(self, browser):
546 if self.curx:
547 self.curx = 0
548 return True
549 else:
550 curses.beep()
551
552 def cmd_end(self, browser):
553 if self.curx < len(self.input):
554 self.curx = len(self.input)
555 return True
556 else:
557 curses.beep()
558
559 def cmd_up(self, browser):
560 if self.cury < len(self.history)-1:
561 self.cury += 1
562 self.input = self.history[self.cury]
563 self.curx = len(self.input)
564 return True
565 else:
566 curses.beep()
567
568 def cmd_down(self, browser):
569 if self.cury >= 0:
570 self.cury -= 1
571 if self.cury>=0:
572 self.input = self.history[self.cury]
573 else:
574 self.input = ""
575 self.curx = len(self.input)
576 return True
577 else:
578 curses.beep()
579
580 def cmd_incsearchup(self, browser):
581 prefix = self.input[:self.curx]
582 cury = self.cury
583 while True:
584 cury += 1
585 if cury >= len(self.history):
586 break
587 if self.history[cury].startswith(prefix):
588 self.input = self.history[cury]
589 self.cury = cury
590 return True
591 curses.beep()
592
593 def cmd_incsearchdown(self, browser):
594 prefix = self.input[:self.curx]
595 cury = self.cury
596 while True:
597 cury -= 1
598 if cury <= 0:
599 break
600 if self.history[cury].startswith(prefix):
601 self.input = self.history[self.cury]
602 self.cury = cury
603 return True
604 curses.beep()
605
606 def cmd_exit(self, browser):
607 browser.mode = "default"
608 return True
609
610 def cmd_execute(self, browser):
611 raise NotImplementedError
612
613
614 class _CommandGoto(_CommandInput):
615 def __init__(self):
616 _CommandInput.__init__(self, "goto object #")
617
618 def handlechar(self, browser, char):
619 # Only accept digits
620 if not "0" <= char <= "9":
621 curses.beep()
622 else:
623 return _CommandInput.handlechar(self, browser, char)
624
625 def cmd_execute(self, browser):
626 level = browser.levels[-1]
627 if self.input:
628 self.dohistory()
629 level.moveto(level.curx, int(self.input))
630 browser.mode = "default"
631 return True
632
633
634 class _CommandFind(_CommandInput):
635 def __init__(self):
636 _CommandInput.__init__(self, "find expression")
637
638 def cmd_execute(self, browser):
639 level = browser.levels[-1]
640 if self.input:
641 self.dohistory()
642 while True:
643 cury = level.cury
644 level.moveto(level.curx, cury+1)
645 if cury == level.cury:
646 curses.beep()
647 break # hit end
648 item = level.items[level.cury].item
649 try:
650 globals = ipipe.getglobals(None)
651 if eval(self.input, globals, ipipe.AttrNamespace(item)):
652 break # found something
653 except (KeyboardInterrupt, SystemExit):
654 raise
655 except Exception as exc:
656 browser.report(exc)
657 curses.beep()
658 break # break on error
659 browser.mode = "default"
660 return True
661
662
663 class _CommandFindBackwards(_CommandInput):
664 def __init__(self):
665 _CommandInput.__init__(self, "find backwards expression")
666
667 def cmd_execute(self, browser):
668 level = browser.levels[-1]
669 if self.input:
670 self.dohistory()
671 while level.cury:
672 level.moveto(level.curx, level.cury-1)
673 item = level.items[level.cury].item
674 try:
675 globals = ipipe.getglobals(None)
676 if eval(self.input, globals, ipipe.AttrNamespace(item)):
677 break # found something
678 except (KeyboardInterrupt, SystemExit):
679 raise
680 except Exception as exc:
681 browser.report(exc)
682 curses.beep()
683 break # break on error
684 else:
685 curses.beep()
686 browser.mode = "default"
687 return True
688
689
690 class ibrowse(ipipe.Display):
691 # Show this many lines from the previous screen when paging horizontally
692 pageoverlapx = 1
693
694 # Show this many lines from the previous screen when paging vertically
695 pageoverlapy = 1
696
697 # Start scrolling when the cursor is less than this number of columns
698 # away from the left or right screen edge
699 scrollborderx = 10
700
701 # Start scrolling when the cursor is less than this number of lines
702 # away from the top or bottom screen edge
703 scrollbordery = 5
704
705 # Accelerate by this factor when scrolling horizontally
706 acceleratex = 1.05
707
708 # Accelerate by this factor when scrolling vertically
709 acceleratey = 1.05
710
711 # The maximum horizontal scroll speed
712 # (as a factor of the screen width (i.e. 0.5 == half a screen width)
713 maxspeedx = 0.5
714
715 # The maximum vertical scroll speed
716 # (as a factor of the screen height (i.e. 0.5 == half a screen height)
717 maxspeedy = 0.5
718
719 # The maximum number of header lines for browser level
720 # if the nesting is deeper, only the innermost levels are displayed
721 maxheaders = 5
722
723 # The approximate maximum length of a column entry
724 maxattrlength = 200
725
726 # Styles for various parts of the GUI
727 style_objheadertext = astyle.Style.fromstr("white:black:bold|reverse")
728 style_objheadernumber = astyle.Style.fromstr("white:blue:bold|reverse")
729 style_objheaderobject = astyle.Style.fromstr("white:black:reverse")
730 style_colheader = astyle.Style.fromstr("blue:white:reverse")
731 style_colheaderhere = astyle.Style.fromstr("green:black:bold|reverse")
732 style_colheadersep = astyle.Style.fromstr("blue:black:reverse")
733 style_number = astyle.Style.fromstr("blue:white:reverse")
734 style_numberhere = astyle.Style.fromstr("green:black:bold|reverse")
735 style_sep = astyle.Style.fromstr("blue:black")
736 style_data = astyle.Style.fromstr("white:black")
737 style_datapad = astyle.Style.fromstr("blue:black:bold")
738 style_footer = astyle.Style.fromstr("black:white")
739 style_report = astyle.Style.fromstr("white:black")
740
741 # Column separator in header
742 headersepchar = "|"
743
744 # Character for padding data cell entries
745 datapadchar = "."
746
747 # Column separator in data area
748 datasepchar = "|"
749
750 # Character to use for "empty" cell (i.e. for non-existing attributes)
751 nodatachar = "-"
752
753 # Prompts for modes that require keyboard input
754 prompts = {
755 "goto": _CommandGoto(),
756 "find": _CommandFind(),
757 "findbackwards": _CommandFindBackwards()
758 }
759
760 # Maps curses key codes to "function" names
761 keymap = Keymap()
762 keymap.register("quit", "q")
763 keymap.register("up", curses.KEY_UP)
764 keymap.register("down", curses.KEY_DOWN)
765 keymap.register("pageup", curses.KEY_PPAGE)
766 keymap.register("pagedown", curses.KEY_NPAGE)
767 keymap.register("left", curses.KEY_LEFT)
768 keymap.register("right", curses.KEY_RIGHT)
769 keymap.register("home", curses.KEY_HOME, "\x01")
770 keymap.register("end", curses.KEY_END, "\x05")
771 keymap.register("prevattr", "<\x1b")
772 keymap.register("nextattr", ">\t")
773 keymap.register("pick", "p")
774 keymap.register("pickattr", "P")
775 keymap.register("pickallattrs", "C")
776 keymap.register("pickmarked", "m")
777 keymap.register("pickmarkedattr", "M")
778 keymap.register("pickinput", "i")
779 keymap.register("pickinputattr", "I")
780 keymap.register("hideattr", "h")
781 keymap.register("unhideattrs", "H")
782 keymap.register("help", "?")
783 keymap.register("enter", "\r\n")
784 keymap.register("enterattr", "E")
785 # FIXME: What's happening here?
786 keymap.register("leave", curses.KEY_BACKSPACE, "x\x08\x7f")
787 keymap.register("detail", "d")
788 keymap.register("detailattr", "D")
789 keymap.register("tooglemark", " ")
790 keymap.register("markrange", "%")
791 keymap.register("sortattrasc", "v")
792 keymap.register("sortattrdesc", "V")
793 keymap.register("goto", "g")
794 keymap.register("find", "f")
795 keymap.register("findbackwards", "b")
796 keymap.register("refresh", "r")
797 keymap.register("refreshfind", "R")
798
799 def __init__(self, input=None, *attrs):
800 """
801 Create a new browser. If ``attrs`` is not empty, it is the list
802 of attributes that will be displayed in the browser, otherwise
803 these will be determined by the objects on screen.
804 """
805 ipipe.Display.__init__(self, input)
806
807 self.attrs = attrs
808
809 # Stack of browser levels
810 self.levels = []
811 # how many colums to scroll (Changes when accelerating)
812 self.stepx = 1.
813
814 # how many rows to scroll (Changes when accelerating)
815 self.stepy = 1.
816
817 # Beep on the edges of the data area? (Will be set to ``False``
818 # once the cursor hits the edge of the screen, so we don't get
819 # multiple beeps).
820 self._dobeep = True
821
822 # Cache for registered ``curses`` colors and styles.
823 self._styles = {}
824 self._colors = {}
825 self._maxcolor = 1
826
827 # How many header lines do we want to paint (the numbers of levels
828 # we have, but with an upper bound)
829 self._headerlines = 1
830
831 # Index of first header line
832 self._firstheaderline = 0
833
834 # curses window
835 self.scr = None
836 # report in the footer line (error, executed command etc.)
837 self._report = None
838
839 # value to be returned to the caller (set by commands)
840 self.returnvalue = None
841
842 # The mode the browser is in
843 # e.g. normal browsing or entering an argument for a command
844 self.mode = "default"
845
846 # set by the SIGWINCH signal handler
847 self.resized = False
848
849 def nextstepx(self, step):
850 """
851 Accelerate horizontally.
852 """
853 return max(1., min(step*self.acceleratex,
854 self.maxspeedx*self.levels[-1].mainsizex))
855
856 def nextstepy(self, step):
857 """
858 Accelerate vertically.
859 """
860 return max(1., min(step*self.acceleratey,
861 self.maxspeedy*self.levels[-1].mainsizey))
862
863 def getstyle(self, style):
864 """
865 Register the ``style`` with ``curses`` or get it from the cache,
866 if it has been registered before.
867 """
868 try:
869 return self._styles[style.fg, style.bg, style.attrs]
870 except KeyError:
871 attrs = 0
872 for b in astyle.A2CURSES:
873 if style.attrs & b:
874 attrs |= astyle.A2CURSES[b]
875 try:
876 color = self._colors[style.fg, style.bg]
877 except KeyError:
878 curses.init_pair(
879 self._maxcolor,
880 astyle.COLOR2CURSES[style.fg],
881 astyle.COLOR2CURSES[style.bg]
882 )
883 color = curses.color_pair(self._maxcolor)
884 self._colors[style.fg, style.bg] = color
885 self._maxcolor += 1
886 c = color | attrs
887 self._styles[style.fg, style.bg, style.attrs] = c
888 return c
889
890 def addstr(self, y, x, begx, endx, text, style):
891 """
892 A version of ``curses.addstr()`` that can handle ``x`` coordinates
893 that are outside the screen.
894 """
895 text2 = text[max(0, begx-x):max(0, endx-x)]
896 if text2:
897 self.scr.addstr(y, max(x, begx), text2, self.getstyle(style))
898 return len(text)
899
900 def addchr(self, y, x, begx, endx, c, l, style):
901 x0 = max(x, begx)
902 x1 = min(x+l, endx)
903 if x1>x0:
904 self.scr.addstr(y, x0, c*(x1-x0), self.getstyle(style))
905 return l
906
907 def _calcheaderlines(self, levels):
908 # Calculate how many headerlines do we have to display, if we have
909 # ``levels`` browser levels
910 if levels is None:
911 levels = len(self.levels)
912 self._headerlines = min(self.maxheaders, levels)
913 self._firstheaderline = levels-self._headerlines
914
915 def getstylehere(self, style):
916 """
917 Return a style for displaying the original style ``style``
918 in the row the cursor is on.
919 """
920 return astyle.Style(style.fg, astyle.COLOR_BLUE, style.attrs | astyle.A_BOLD)
921
922 def report(self, msg):
923 """
924 Store the message ``msg`` for display below the footer line. This
925 will be displayed as soon as the screen is redrawn.
926 """
927 self._report = msg
928
929 def enter(self, item, *attrs):
930 """
931 Enter the object ``item``. If ``attrs`` is specified, it will be used
932 as a fixed list of attributes to display.
933 """
934 if self.levels and item is self.levels[-1].input:
935 curses.beep()
936 self.report(CommandError("Recursion on input object"))
937 else:
938 oldlevels = len(self.levels)
939 self._calcheaderlines(oldlevels+1)
940 try:
941 level = _BrowserLevel(
942 self,
943 item,
944 self.scrsizey-1-self._headerlines-2,
945 *attrs
946 )
947 except (KeyboardInterrupt, SystemExit):
948 raise
949 except Exception as exc:
950 if not self.levels:
951 raise
952 self._calcheaderlines(oldlevels)
953 curses.beep()
954 self.report(exc)
955 else:
956 self.levels.append(level)
957
958 def startkeyboardinput(self, mode):
959 """
960 Enter mode ``mode``, which requires keyboard input.
961 """
962 self.mode = mode
963 self.prompts[mode].start()
964
965 def keylabel(self, keycode):
966 """
967 Return a pretty name for the ``curses`` key ``keycode`` (used in the
968 help screen and in reports about unassigned keys).
969 """
970 if keycode <= 0xff:
971 specialsnames = {
972 ord("\n"): "RETURN",
973 ord(" "): "SPACE",
974 ord("\t"): "TAB",
975 ord("\x7f"): "DELETE",
976 ord("\x08"): "BACKSPACE",
977 }
978 if keycode in specialsnames:
979 return specialsnames[keycode]
980 elif 0x00 < keycode < 0x20:
981 return "CTRL-%s" % chr(keycode + 64)
982 return repr(chr(keycode))
983 for name in dir(curses):
984 if name.startswith("KEY_") and getattr(curses, name) == keycode:
985 return name
986 return str(keycode)
987
988 def beep(self, force=False):
989 if force or self._dobeep:
990 curses.beep()
991 # don't beep again (as long as the same key is pressed)
992 self._dobeep = False
993
994 def cmd_up(self):
995 """
996 Move the cursor to the previous row.
997 """
998 level = self.levels[-1]
999 self.report("up")
1000 level.moveto(level.curx, level.cury-self.stepy)
1001
1002 def cmd_down(self):
1003 """
1004 Move the cursor to the next row.
1005 """
1006 level = self.levels[-1]
1007 self.report("down")
1008 level.moveto(level.curx, level.cury+self.stepy)
1009
1010 def cmd_pageup(self):
1011 """
1012 Move the cursor up one page.
1013 """
1014 level = self.levels[-1]
1015 self.report("page up")
1016 level.moveto(level.curx, level.cury-level.mainsizey+self.pageoverlapy)
1017
1018 def cmd_pagedown(self):
1019 """
1020 Move the cursor down one page.
1021 """
1022 level = self.levels[-1]
1023 self.report("page down")
1024 level.moveto(level.curx, level.cury+level.mainsizey-self.pageoverlapy)
1025
1026 def cmd_left(self):
1027 """
1028 Move the cursor left.
1029 """
1030 level = self.levels[-1]
1031 self.report("left")
1032 level.moveto(level.curx-self.stepx, level.cury)
1033
1034 def cmd_right(self):
1035 """
1036 Move the cursor right.
1037 """
1038 level = self.levels[-1]
1039 self.report("right")
1040 level.moveto(level.curx+self.stepx, level.cury)
1041
1042 def cmd_home(self):
1043 """
1044 Move the cursor to the first column.
1045 """
1046 level = self.levels[-1]
1047 self.report("home")
1048 level.moveto(0, level.cury)
1049
1050 def cmd_end(self):
1051 """
1052 Move the cursor to the last column.
1053 """
1054 level = self.levels[-1]
1055 self.report("end")
1056 level.moveto(level.datasizex+level.mainsizey-self.pageoverlapx, level.cury)
1057
1058 def cmd_prevattr(self):
1059 """
1060 Move the cursor one attribute column to the left.
1061 """
1062 level = self.levels[-1]
1063 if level.displayattr[0] is None or level.displayattr[0] == 0:
1064 self.beep()
1065 else:
1066 self.report("prevattr")
1067 pos = 0
1068 for (i, attrname) in enumerate(level.displayattrs):
1069 if i == level.displayattr[0]-1:
1070 break
1071 pos += level.colwidths[attrname] + 1
1072 level.moveto(pos, level.cury)
1073
1074 def cmd_nextattr(self):
1075 """
1076 Move the cursor one attribute column to the right.
1077 """
1078 level = self.levels[-1]
1079 if level.displayattr[0] is None or level.displayattr[0] == len(level.displayattrs)-1:
1080 self.beep()
1081 else:
1082 self.report("nextattr")
1083 pos = 0
1084 for (i, attrname) in enumerate(level.displayattrs):
1085 if i == level.displayattr[0]+1:
1086 break
1087 pos += level.colwidths[attrname] + 1
1088 level.moveto(pos, level.cury)
1089
1090 def cmd_pick(self):
1091 """
1092 'Pick' the object under the cursor (i.e. the row the cursor is on).
1093 This leaves the browser and returns the picked object to the caller.
1094 (In IPython this object will be available as the ``_`` variable.)
1095 """
1096 level = self.levels[-1]
1097 self.returnvalue = level.items[level.cury].item
1098 return True
1099
1100 def cmd_pickattr(self):
1101 """
1102 'Pick' the attribute under the cursor (i.e. the row/column the
1103 cursor is on).
1104 """
1105 level = self.levels[-1]
1106 attr = level.displayattr[1]
1107 if attr is ipipe.noitem:
1108 curses.beep()
1109 self.report(CommandError("no column under cursor"))
1110 return
1111 value = attr.value(level.items[level.cury].item)
1112 if value is ipipe.noitem:
1113 curses.beep()
1114 self.report(AttributeError(attr.name()))
1115 else:
1116 self.returnvalue = value
1117 return True
1118
1119 def cmd_pickallattrs(self):
1120 """
1121 Pick' the complete column under the cursor (i.e. the attribute under
1122 the cursor) from all currently fetched objects. These attributes
1123 will be returned as a list.
1124 """
1125 level = self.levels[-1]
1126 attr = level.displayattr[1]
1127 if attr is ipipe.noitem:
1128 curses.beep()
1129 self.report(CommandError("no column under cursor"))
1130 return
1131 result = []
1132 for cache in level.items:
1133 value = attr.value(cache.item)
1134 if value is not ipipe.noitem:
1135 result.append(value)
1136 self.returnvalue = result
1137 return True
1138
1139 def cmd_pickmarked(self):
1140 """
1141 'Pick' marked objects. Marked objects will be returned as a list.
1142 """
1143 level = self.levels[-1]
1144 self.returnvalue = [cache.item for cache in level.items if cache.marked]
1145 return True
1146
1147 def cmd_pickmarkedattr(self):
1148 """
1149 'Pick' the attribute under the cursor from all marked objects
1150 (This returns a list).
1151 """
1152
1153 level = self.levels[-1]
1154 attr = level.displayattr[1]
1155 if attr is ipipe.noitem:
1156 curses.beep()
1157 self.report(CommandError("no column under cursor"))
1158 return
1159 result = []
1160 for cache in level.items:
1161 if cache.marked:
1162 value = attr.value(cache.item)
1163 if value is not ipipe.noitem:
1164 result.append(value)
1165 self.returnvalue = result
1166 return True
1167
1168 def cmd_pickinput(self):
1169 """
1170 Use the object under the cursor (i.e. the row the cursor is on) as
1171 the next input line. This leaves the browser and puts the picked object
1172 in the input.
1173 """
1174 level = self.levels[-1]
1175 value = level.items[level.cury].item
1176 self.returnvalue = None
1177 api = ipapi.get()
1178 api.set_next_input(str(value))
1179 return True
1180
1181 def cmd_pickinputattr(self):
1182 """
1183 Use the attribute under the cursor i.e. the row/column the cursor is on)
1184 as the next input line. This leaves the browser and puts the picked
1185 object in the input.
1186 """
1187 level = self.levels[-1]
1188 attr = level.displayattr[1]
1189 if attr is ipipe.noitem:
1190 curses.beep()
1191 self.report(CommandError("no column under cursor"))
1192 return
1193 value = attr.value(level.items[level.cury].item)
1194 if value is ipipe.noitem:
1195 curses.beep()
1196 self.report(AttributeError(attr.name()))
1197 self.returnvalue = None
1198 api = ipapi.get()
1199 api.set_next_input(str(value))
1200 return True
1201
1202 def cmd_markrange(self):
1203 """
1204 Mark all objects from the last marked object before the current cursor
1205 position to the cursor position.
1206 """
1207 level = self.levels[-1]
1208 self.report("markrange")
1209 start = None
1210 if level.items:
1211 for i in xrange(level.cury, -1, -1):
1212 if level.items[i].marked:
1213 start = i
1214 break
1215 if start is None:
1216 self.report(CommandError("no mark before cursor"))
1217 curses.beep()
1218 else:
1219 for i in xrange(start, level.cury+1):
1220 cache = level.items[i]
1221 if not cache.marked:
1222 cache.marked = True
1223 level.marked += 1
1224
1225 def cmd_enter(self):
1226 """
1227 Enter the object under the cursor. (what this mean depends on the object
1228 itself (i.e. how it implements iteration). This opens a new browser 'level'.
1229 """
1230 level = self.levels[-1]
1231 try:
1232 item = level.items[level.cury].item
1233 except IndexError:
1234 self.report(CommandError("No object"))
1235 curses.beep()
1236 else:
1237 self.report("entering object...")
1238 self.enter(item)
1239
1240 def cmd_leave(self):
1241 """
1242 Leave the current browser level and go back to the previous one.
1243 """
1244 self.report("leave")
1245 if len(self.levels) > 1:
1246 self._calcheaderlines(len(self.levels)-1)
1247 self.levels.pop(-1)
1248 else:
1249 self.report(CommandError("This is the last level"))
1250 curses.beep()
1251
1252 def cmd_enterattr(self):
1253 """
1254 Enter the attribute under the cursor.
1255 """
1256 level = self.levels[-1]
1257 attr = level.displayattr[1]
1258 if attr is ipipe.noitem:
1259 curses.beep()
1260 self.report(CommandError("no column under cursor"))
1261 return
1262 try:
1263 item = level.items[level.cury].item
1264 except IndexError:
1265 self.report(CommandError("No object"))
1266 curses.beep()
1267 else:
1268 value = attr.value(item)
1269 name = attr.name()
1270 if value is ipipe.noitem:
1271 self.report(AttributeError(name))
1272 else:
1273 self.report("entering object attribute %s..." % name)
1274 self.enter(value)
1275
1276 def cmd_detail(self):
1277 """
1278 Show a detail view of the object under the cursor. This shows the
1279 name, type, doc string and value of the object attributes (and it
1280 might show more attributes than in the list view, depending on
1281 the object).
1282 """
1283 level = self.levels[-1]
1284 try:
1285 item = level.items[level.cury].item
1286 except IndexError:
1287 self.report(CommandError("No object"))
1288 curses.beep()
1289 else:
1290 self.report("entering detail view for object...")
1291 attrs = [ipipe.AttributeDetail(item, attr) for attr in ipipe.xattrs(item, "detail")]
1292 self.enter(attrs)
1293
1294 def cmd_detailattr(self):
1295 """
1296 Show a detail view of the attribute under the cursor.
1297 """
1298 level = self.levels[-1]
1299 attr = level.displayattr[1]
1300 if attr is ipipe.noitem:
1301 curses.beep()
1302 self.report(CommandError("no attribute"))
1303 return
1304 try:
1305 item = level.items[level.cury].item
1306 except IndexError:
1307 self.report(CommandError("No object"))
1308 curses.beep()
1309 else:
1310 try:
1311 item = attr.value(item)
1312 except (KeyboardInterrupt, SystemExit):
1313 raise
1314 except Exception as exc:
1315 self.report(exc)
1316 else:
1317 self.report("entering detail view for attribute %s..." % attr.name())
1318 attrs = [ipipe.AttributeDetail(item, attr) for attr in ipipe.xattrs(item, "detail")]
1319 self.enter(attrs)
1320
1321 def cmd_tooglemark(self):
1322 """
1323 Mark/unmark the object under the cursor. Marked objects have a '!'
1324 after the row number).
1325 """
1326 level = self.levels[-1]
1327 self.report("toggle mark")
1328 try:
1329 item = level.items[level.cury]
1330 except IndexError: # no items?
1331 pass
1332 else:
1333 if item.marked:
1334 item.marked = False
1335 level.marked -= 1
1336 else:
1337 item.marked = True
1338 level.marked += 1
1339
1340 def cmd_sortattrasc(self):
1341 """
1342 Sort the objects (in ascending order) using the attribute under
1343 the cursor as the sort key.
1344 """
1345 level = self.levels[-1]
1346 attr = level.displayattr[1]
1347 if attr is ipipe.noitem:
1348 curses.beep()
1349 self.report(CommandError("no column under cursor"))
1350 return
1351 self.report("sort by %s (ascending)" % attr.name())
1352 def key(item):
1353 try:
1354 return attr.value(item)
1355 except (KeyboardInterrupt, SystemExit):
1356 raise
1357 except Exception:
1358 return None
1359 level.sort(key)
1360
1361 def cmd_sortattrdesc(self):
1362 """
1363 Sort the objects (in descending order) using the attribute under
1364 the cursor as the sort key.
1365 """
1366 level = self.levels[-1]
1367 attr = level.displayattr[1]
1368 if attr is ipipe.noitem:
1369 curses.beep()
1370 self.report(CommandError("no column under cursor"))
1371 return
1372 self.report("sort by %s (descending)" % attr.name())
1373 def key(item):
1374 try:
1375 return attr.value(item)
1376 except (KeyboardInterrupt, SystemExit):
1377 raise
1378 except Exception:
1379 return None
1380 level.sort(key, reverse=True)
1381
1382 def cmd_hideattr(self):
1383 """
1384 Hide the attribute under the cursor.
1385 """
1386 level = self.levels[-1]
1387 if level.displayattr[0] is None:
1388 self.beep()
1389 else:
1390 self.report("hideattr")
1391 level.hiddenattrs.add(level.displayattr[1])
1392 level.moveto(level.curx, level.cury, refresh=True)
1393
1394 def cmd_unhideattrs(self):
1395 """
1396 Make all attributes visible again.
1397 """
1398 level = self.levels[-1]
1399 self.report("unhideattrs")
1400 level.hiddenattrs.clear()
1401 level.moveto(level.curx, level.cury, refresh=True)
1402
1403 def cmd_goto(self):
1404 """
1405 Jump to a row. The row number can be entered at the
1406 bottom of the screen.
1407 """
1408 self.startkeyboardinput("goto")
1409
1410 def cmd_find(self):
1411 """
1412 Search forward for a row. The search condition can be entered at the
1413 bottom of the screen.
1414 """
1415 self.startkeyboardinput("find")
1416
1417 def cmd_findbackwards(self):
1418 """
1419 Search backward for a row. The search condition can be entered at the
1420 bottom of the screen.
1421 """
1422 self.startkeyboardinput("findbackwards")
1423
1424 def cmd_refresh(self):
1425 """
1426 Refreshes the display by restarting the iterator.
1427 """
1428 level = self.levels[-1]
1429 self.report("refresh")
1430 level.refresh()
1431
1432 def cmd_refreshfind(self):
1433 """
1434 Refreshes the display by restarting the iterator and goes back to the
1435 same object the cursor was on before restarting (if this object can't be
1436 found the cursor jumps back to the first object).
1437 """
1438 level = self.levels[-1]
1439 self.report("refreshfind")
1440 level.refreshfind()
1441
1442 def cmd_help(self):
1443 """
1444 Opens the help screen as a new browser level, describing keyboard
1445 shortcuts.
1446 """
1447 for level in self.levels:
1448 if isinstance(level.input, _BrowserHelp):
1449 curses.beep()
1450 self.report(CommandError("help already active"))
1451 return
1452
1453 self.enter(_BrowserHelp(self))
1454
1455 def cmd_quit(self):
1456 """
1457 Quit the browser and return to the IPython prompt.
1458 """
1459 self.returnvalue = None
1460 return True
1461
1462 def sigwinchhandler(self, signal, frame):
1463 self.resized = True
1464
1465 def _dodisplay(self, scr):
1466 """
1467 This method is the workhorse of the browser. It handles screen
1468 drawing and the keyboard.
1469 """
1470 self.scr = scr
1471 curses.halfdelay(1)
1472 footery = 2
1473
1474 keys = []
1475 for cmd in ("quit", "help"):
1476 key = self.keymap.findkey(cmd, None)
1477 if key is not None:
1478 keys.append("%s=%s" % (self.keylabel(key), cmd))
1479 helpmsg = " | %s" % " ".join(keys)
1480
1481 scr.clear()
1482 msg = "Fetching first batch of objects..."
1483 (self.scrsizey, self.scrsizex) = scr.getmaxyx()
1484 scr.addstr(self.scrsizey//2, (self.scrsizex-len(msg))//2, msg)
1485 scr.refresh()
1486
1487 lastc = -1
1488
1489 self.levels = []
1490 # enter the first level
1491 self.enter(self.input, *self.attrs)
1492
1493 self._calcheaderlines(None)
1494
1495 while True:
1496 level = self.levels[-1]
1497 (self.scrsizey, self.scrsizex) = scr.getmaxyx()
1498 level.mainsizey = self.scrsizey-1-self._headerlines-footery
1499
1500 # Paint object header
1501 for i in xrange(self._firstheaderline, self._firstheaderline+self._headerlines):
1502 lv = self.levels[i]
1503 posx = 0
1504 posy = i-self._firstheaderline
1505 endx = self.scrsizex
1506 if i: # not the first level
1507 msg = " (%d/%d" % (self.levels[i-1].cury, len(self.levels[i-1].items))
1508 if not self.levels[i-1].exhausted:
1509 msg += "+"
1510 msg += ") "
1511 endx -= len(msg)+1
1512 posx += self.addstr(posy, posx, 0, endx, " ibrowse #%d: " % i, self.style_objheadertext)
1513 for (style, text) in lv.header:
1514 posx += self.addstr(posy, posx, 0, endx, text, self.style_objheaderobject)
1515 if posx >= endx:
1516 break
1517 if i:
1518 posx += self.addstr(posy, posx, 0, self.scrsizex, msg, self.style_objheadernumber)
1519 posx += self.addchr(posy, posx, 0, self.scrsizex, " ", self.scrsizex-posx, self.style_objheadernumber)
1520
1521 if not level.items:
1522 self.addchr(self._headerlines, 0, 0, self.scrsizex, " ", self.scrsizex, self.style_colheader)
1523 self.addstr(self._headerlines+1, 0, 0, self.scrsizex, " <empty>", astyle.style_error)
1524 scr.clrtobot()
1525 else:
1526 # Paint column headers
1527 scr.move(self._headerlines, 0)
1528 scr.addstr(" %*s " % (level.numbersizex, "#"), self.getstyle(self.style_colheader))
1529 scr.addstr(self.headersepchar, self.getstyle(self.style_colheadersep))
1530 begx = level.numbersizex+3
1531 posx = begx-level.datastartx
1532 for attr in level.displayattrs:
1533 attrname = attr.name()
1534 cwidth = level.colwidths[attr]
1535 header = attrname.ljust(cwidth)
1536 if attr is level.displayattr[1]:
1537 style = self.style_colheaderhere
1538 else:
1539 style = self.style_colheader
1540 posx += self.addstr(self._headerlines, posx, begx, self.scrsizex, header, style)
1541 posx += self.addstr(self._headerlines, posx, begx, self.scrsizex, self.headersepchar, self.style_colheadersep)
1542 if posx >= self.scrsizex:
1543 break
1544 else:
1545 scr.addstr(" "*(self.scrsizex-posx), self.getstyle(self.style_colheader))
1546
1547 # Paint rows
1548 posy = self._headerlines+1+level.datastarty
1549 for i in xrange(level.datastarty, min(level.datastarty+level.mainsizey, len(level.items))):
1550 cache = level.items[i]
1551 if i == level.cury:
1552 style = self.style_numberhere
1553 else:
1554 style = self.style_number
1555
1556 posy = self._headerlines+1+i-level.datastarty
1557 posx = begx-level.datastartx
1558
1559 scr.move(posy, 0)
1560 scr.addstr(" %*d%s" % (level.numbersizex, i, " !"[cache.marked]), self.getstyle(style))
1561 scr.addstr(self.headersepchar, self.getstyle(self.style_sep))
1562
1563 for attrname in level.displayattrs:
1564 cwidth = level.colwidths[attrname]
1565 try:
1566 (align, length, parts) = level.displayrows[i-level.datastarty][attrname]
1567 except KeyError:
1568 align = 2
1569 style = astyle.style_nodata
1570 if i == level.cury:
1571 style = self.getstylehere(style)
1572 padstyle = self.style_datapad
1573 sepstyle = self.style_sep
1574 if i == level.cury:
1575 padstyle = self.getstylehere(padstyle)
1576 sepstyle = self.getstylehere(sepstyle)
1577 if align == 2:
1578 posx += self.addchr(posy, posx, begx, self.scrsizex, self.nodatachar, cwidth, style)
1579 else:
1580 if align == 1:
1581 posx += self.addchr(posy, posx, begx, self.scrsizex, self.datapadchar, cwidth-length, padstyle)
1582 elif align == 0:
1583 pad1 = (cwidth-length)//2
1584 pad2 = cwidth-length-len(pad1)
1585 posx += self.addchr(posy, posx, begx, self.scrsizex, self.datapadchar, pad1, padstyle)
1586 for (style, text) in parts:
1587 if i == level.cury:
1588 style = self.getstylehere(style)
1589 posx += self.addstr(posy, posx, begx, self.scrsizex, text, style)
1590 if posx >= self.scrsizex:
1591 break
1592 if align == -1:
1593 posx += self.addchr(posy, posx, begx, self.scrsizex, self.datapadchar, cwidth-length, padstyle)
1594 elif align == 0:
1595 posx += self.addchr(posy, posx, begx, self.scrsizex, self.datapadchar, pad2, padstyle)
1596 posx += self.addstr(posy, posx, begx, self.scrsizex, self.datasepchar, sepstyle)
1597 else:
1598 scr.clrtoeol()
1599
1600 # Add blank row headers for the rest of the screen
1601 for posy in xrange(posy+1, self.scrsizey-2):
1602 scr.addstr(posy, 0, " " * (level.numbersizex+2), self.getstyle(self.style_colheader))
1603 scr.clrtoeol()
1604
1605 posy = self.scrsizey-footery
1606 # Display footer
1607 scr.addstr(posy, 0, " "*self.scrsizex, self.getstyle(self.style_footer))
1608
1609 if level.exhausted:
1610 flag = ""
1611 else:
1612 flag = "+"
1613
1614 endx = self.scrsizex-len(helpmsg)-1
1615 scr.addstr(posy, endx, helpmsg, self.getstyle(self.style_footer))
1616
1617 posx = 0
1618 msg = " %d%s objects (%d marked): " % (len(level.items), flag, level.marked)
1619 posx += self.addstr(posy, posx, 0, endx, msg, self.style_footer)
1620 try:
1621 item = level.items[level.cury].item
1622 except IndexError: # empty
1623 pass
1624 else:
1625 for (nostyle, text) in ipipe.xrepr(item, "footer"):
1626 if not isinstance(nostyle, int):
1627 posx += self.addstr(posy, posx, 0, endx, text, self.style_footer)
1628 if posx >= endx:
1629 break
1630
1631 attrstyle = [(astyle.style_default, "no attribute")]
1632 attr = level.displayattr[1]
1633 if attr is not ipipe.noitem and not isinstance(attr, ipipe.SelfDescriptor):
1634 posx += self.addstr(posy, posx, 0, endx, " | ", self.style_footer)
1635 posx += self.addstr(posy, posx, 0, endx, attr.name(), self.style_footer)
1636 posx += self.addstr(posy, posx, 0, endx, ": ", self.style_footer)
1637 try:
1638 value = attr.value(item)
1639 except (SystemExit, KeyboardInterrupt):
1640 raise
1641 except Exception as exc:
1642 value = exc
1643 if value is not ipipe.noitem:
1644 attrstyle = ipipe.xrepr(value, "footer")
1645 for (nostyle, text) in attrstyle:
1646 if not isinstance(nostyle, int):
1647 posx += self.addstr(posy, posx, 0, endx, text, self.style_footer)
1648 if posx >= endx:
1649 break
1650
1651 try:
1652 # Display input prompt
1653 if self.mode in self.prompts:
1654 history = self.prompts[self.mode]
1655 posx = 0
1656 posy = self.scrsizey-1
1657 posx += self.addstr(posy, posx, 0, endx, history.prompt, astyle.style_default)
1658 posx += self.addstr(posy, posx, 0, endx, " [", astyle.style_default)
1659 if history.cury==-1:
1660 text = "new"
1661 else:
1662 text = str(history.cury+1)
1663 posx += self.addstr(posy, posx, 0, endx, text, astyle.style_type_number)
1664 if history.history:
1665 posx += self.addstr(posy, posx, 0, endx, "/", astyle.style_default)
1666 posx += self.addstr(posy, posx, 0, endx, str(len(history.history)), astyle.style_type_number)
1667 posx += self.addstr(posy, posx, 0, endx, "]: ", astyle.style_default)
1668 inputstartx = posx
1669 posx += self.addstr(posy, posx, 0, endx, history.input, astyle.style_default)
1670 # Display report
1671 else:
1672 if self._report is not None:
1673 if isinstance(self._report, Exception):
1674 style = self.getstyle(astyle.style_error)
1675 if self._report.__class__.__module__ == "exceptions":
1676 msg = "%s: %s" % \
1677 (self._report.__class__.__name__, self._report)
1678 else:
1679 msg = "%s.%s: %s" % \
1680 (self._report.__class__.__module__,
1681 self._report.__class__.__name__, self._report)
1682 else:
1683 style = self.getstyle(self.style_report)
1684 msg = self._report
1685 scr.addstr(self.scrsizey-1, 0, msg[:self.scrsizex], style)
1686 self._report = None
1687 else:
1688 scr.move(self.scrsizey-1, 0)
1689 except curses.error:
1690 # Protect against errors from writing to the last line
1691 pass
1692 scr.clrtoeol()
1693
1694 # Position cursor
1695 if self.mode in self.prompts:
1696 history = self.prompts[self.mode]
1697 scr.move(self.scrsizey-1, inputstartx+history.curx)
1698 else:
1699 scr.move(
1700 1+self._headerlines+level.cury-level.datastarty,
1701 level.numbersizex+3+level.curx-level.datastartx
1702 )
1703 scr.refresh()
1704
1705 # Check keyboard
1706 while True:
1707 c = scr.getch()
1708 if self.resized:
1709 size = fcntl.ioctl(0, tty.TIOCGWINSZ, "12345678")
1710 size = struct.unpack("4H", size)
1711 oldsize = scr.getmaxyx()
1712 scr.erase()
1713 curses.resize_term(size[0], size[1])
1714 newsize = scr.getmaxyx()
1715 scr.erase()
1716 for l in self.levels:
1717 l.mainsizey += newsize[0]-oldsize[0]
1718 l.moveto(l.curx, l.cury, refresh=True)
1719 scr.refresh()
1720 self.resized = False
1721 break # Redisplay
1722 if self.mode in self.prompts:
1723 if self.prompts[self.mode].handlekey(self, c):
1724 break # Redisplay
1725 else:
1726 # if no key is pressed slow down and beep again
1727 if c == -1:
1728 self.stepx = 1.
1729 self.stepy = 1.
1730 self._dobeep = True
1731 else:
1732 # if a different key was pressed slow down and beep too
1733 if c != lastc:
1734 lastc = c
1735 self.stepx = 1.
1736 self.stepy = 1.
1737 self._dobeep = True
1738 cmdname = self.keymap.get(c, None)
1739 if cmdname is None:
1740 self.report(
1741 UnassignedKeyError("Unassigned key %s" %
1742 self.keylabel(c)))
1743 else:
1744 cmdfunc = getattr(self, "cmd_%s" % cmdname, None)
1745 if cmdfunc is None:
1746 self.report(
1747 UnknownCommandError("Unknown command %r" %
1748 (cmdname,)))
1749 elif cmdfunc():
1750 returnvalue = self.returnvalue
1751 self.returnvalue = None
1752 return returnvalue
1753 self.stepx = self.nextstepx(self.stepx)
1754 self.stepy = self.nextstepy(self.stepy)
1755 curses.flushinp() # get rid of type ahead
1756 break # Redisplay
1757 self.scr = None
1758
1759 def display(self):
1760 if hasattr(curses, "resize_term") and hasattr(signal, 'SIGWINCH'):
1761 oldhandler = signal.signal(signal.SIGWINCH, self.sigwinchhandler)
1762 try:
1763 return curses.wrapper(self._dodisplay)
1764 finally:
1765 signal.signal(signal.SIGWINCH, oldhandler)
1766 else:
1767 return curses.wrapper(self._dodisplay)
This diff has been collapsed as it changes many lines, (1126 lines changed) Show them Hide them
@@ -1,1126 +0,0 b''
1 # -*- coding: iso-8859-1 -*-
2
3 import ipipe, os, webbrowser, urllib
4 from IPython.core import ipapi
5 import wx
6 import wx.grid, wx.html
7
8 try:
9 sorted
10 except NameError:
11 from ipipe import sorted
12 try:
13 set
14 except:
15 from sets import Set as set
16
17
18 __all__ = ["igrid"]
19
20
21 help = """
22 <?xml version='1.0' encoding='iso-8859-1'?>
23 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
24 <html>
25 <head>
26 <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
27 <link rel="stylesheet" href="igrid_help.css" type="text/css" />
28 <title>igrid help</title>
29 </head>
30 <body>
31 <h1>igrid help</h1>
32
33
34 <h2>Commands</h2>
35
36
37 <h3>pick (P)</h3>
38 <p>Pick the whole row (object is available as "_")</p>
39
40 <h3>pickattr (Shift-P)</h3>
41 <p>Pick the attribute under the cursor</p>
42
43 <h3>pickallattrs (Shift-C)</h3>
44 <p>Pick the complete column under the cursor (i.e. the attribute under the
45 cursor) from all currently fetched objects. These attributes will be returned
46 as a list.</p>
47
48 <h3>pickinput (I)</h3>
49 <p>Pick the current row as next input line in IPython. Additionally the row is stored as "_"</p>
50
51 <h3>pickinputattr (Shift-I)</h3>
52 <p>Pick the attribute under the cursor as next input line in IPython. Additionally the row is stored as "_"</p>
53
54 <h3>enter (E)</h3>
55 <p>Enter the object under the cursor. (what this mean depends on the object
56 itself, i.e. how it implements iteration). This opens a new browser 'level'.</p>
57
58 <h3>enterattr (Shift-E)</h3>
59 <p>Enter the attribute under the cursor.</p>
60
61 <h3>detail (D)</h3>
62 <p>Show a detail view of the object under the cursor. This shows the name,
63 type, doc string and value of the object attributes (and it might show more
64 attributes than in the list view, depending on the object).</p>
65
66 <h3>detailattr (Shift-D)</h3>
67 <p>Show a detail view of the attribute under the cursor.</p>
68
69 <h3>pickrows (M)</h3>
70 <p>Pick multiple selected rows (M)</p>
71
72 <h3>pickrowsattr (CTRL-M)</h3>
73 <p>From multiple selected rows pick the cells matching the attribute the cursor is in (CTRL-M)</p>
74
75 <h3>find (CTRL-F)</h3>
76 <p>Find text</p>
77
78 <h3>find_expression (CTRL-Shift-F)</h3>
79 <p>Find entries matching an expression</p>
80
81 <h3>find_next (F3)</h3>
82 <p>Find next occurrence</p>
83
84 <h3>find_previous (Shift-F3)</h3>
85 <p>Find previous occurrence</p>
86
87 <h3>sortattrasc (V)</h3>
88 <p>Sort the objects (in ascending order) using the attribute under the cursor as the sort key.</p>
89
90 <h3>sortattrdesc (Shift-V)</h3>
91 <p>Sort the objects (in descending order) using the attribute under the cursor as the sort key.</p>
92
93 <h3>refresh_once (R, F5)</h3>
94 <p>Refreshes the display by restarting the iterator</p>
95
96 <h3>refresh_every_second</h3>
97 <p>Refreshes the display by restarting the iterator every second until stopped by stop_refresh.</p>
98
99 <h3>refresh_interval</h3>
100 <p>Refreshes the display by restarting the iterator every X ms (X is a custom interval set by the user) until stopped by stop_refresh.</p>
101
102 <h3>stop_refresh</h3>
103 <p>Stops all refresh timers.</p>
104
105 <h3>leave (Backspace, DEL, X)</h3>
106 <p>Close current tab (and all the tabs to the right of the current one).</h3>
107
108 <h3>quit (ESC,Q)</h3>
109 <p>Quit igrid and return to the IPython prompt.</p>
110
111
112 <h2>Navigation</h2>
113
114
115 <h3>Jump to the last column of the current row (END, CTRL-E, CTRL-Right)</h3>
116
117 <h3>Jump to the first column of the current row (HOME, CTRL-A, CTRL-Left)</h3>
118
119 <h3>Move the cursor one column to the left (&lt;)</h3>
120
121 <h3>Move the cursor one column to the right (&gt;)</h3>
122
123 <h3>Jump to the first row in the current column (CTRL-Up)</h3>
124
125 <h3>Jump to the last row in the current column (CTRL-Down)</h3>
126
127 </body>
128 </html>
129
130 """
131
132
133 class IGridRenderer(wx.grid.PyGridCellRenderer):
134 """
135 This is a custom renderer for our IGridGrid
136 """
137 def __init__(self, table):
138 self.maxchars = 200
139 self.table = table
140 self.colormap = (
141 ( 0, 0, 0),
142 (174, 0, 0),
143 ( 0, 174, 0),
144 (174, 174, 0),
145 ( 0, 0, 174),
146 (174, 0, 174),
147 ( 0, 174, 174),
148 ( 64, 64, 64)
149 )
150
151 wx.grid.PyGridCellRenderer.__init__(self)
152
153 def _getvalue(self, row, col):
154 try:
155 value = self.table._displayattrs[col].value(self.table.items[row])
156 (align, width, text) = ipipe.xformat(value, "cell", self.maxchars)
157 except Exception as exc:
158 (align, width, text) = ipipe.xformat(exc, "cell", self.maxchars)
159 return (align, text)
160
161 def GetBestSize(self, grid, attr, dc, row, col):
162 text = grid.GetCellValue(row, col)
163 (align, text) = self._getvalue(row, col)
164 dc.SetFont(attr.GetFont())
165 (w, h) = dc.GetTextExtent(str(text))
166 return wx.Size(min(w+2, 600), h+2) # add border
167
168 def Draw(self, grid, attr, dc, rect, row, col, isSelected):
169 """
170 Takes care of drawing everything in the cell; aligns the text
171 """
172 text = grid.GetCellValue(row, col)
173 (align, text) = self._getvalue(row, col)
174 if isSelected:
175 bg = grid.GetSelectionBackground()
176 else:
177 bg = ["white", (240, 240, 240)][row%2]
178 dc.SetTextBackground(bg)
179 dc.SetBrush(wx.Brush(bg, wx.SOLID))
180 dc.SetPen(wx.TRANSPARENT_PEN)
181 dc.SetFont(attr.GetFont())
182 dc.DrawRectangleRect(rect)
183 dc.SetClippingRect(rect)
184 # Format the text
185 if align == -1: # left alignment
186 (width, height) = dc.GetTextExtent(str(text))
187 x = rect[0]+1
188 y = rect[1]+0.5*(rect[3]-height)
189
190 for (style, part) in text:
191 if isSelected:
192 fg = grid.GetSelectionForeground()
193 else:
194 fg = self.colormap[style.fg]
195 dc.SetTextForeground(fg)
196 (w, h) = dc.GetTextExtent(part)
197 dc.DrawText(part, x, y)
198 x += w
199 elif align == 0: # center alignment
200 (width, height) = dc.GetTextExtent(str(text))
201 x = rect[0]+0.5*(rect[2]-width)
202 y = rect[1]+0.5*(rect[3]-height)
203 for (style, part) in text:
204 if isSelected:
205 fg = grid.GetSelectionForeground()
206 else:
207 fg = self.colormap[style.fg]
208 dc.SetTextForeground(fg)
209 (w, h) = dc.GetTextExtent(part)
210 dc.DrawText(part, x, y)
211 x += w
212 else: # right alignment
213 (width, height) = dc.GetTextExtent(str(text))
214 x = rect[0]+rect[2]-1
215 y = rect[1]+0.5*(rect[3]-height)
216 for (style, part) in reversed(text):
217 (w, h) = dc.GetTextExtent(part)
218 x -= w
219 if isSelected:
220 fg = grid.GetSelectionForeground()
221 else:
222 fg = self.colormap[style.fg]
223 dc.SetTextForeground(fg)
224 dc.DrawText(part, x, y)
225 dc.DestroyClippingRegion()
226
227 def Clone(self):
228 return IGridRenderer(self.table)
229
230
231 class IGridTable(wx.grid.PyGridTableBase):
232 # The data table for the ``IGridGrid``. Some dirty tricks were used here:
233 # ``GetValue()`` does not get any values (or at least it does not return
234 # anything, accessing the values is done by the renderer)
235 # but rather tries to fetch the objects which were requested into the table.
236 # General behaviour is: Fetch the first X objects. If the user scrolls down
237 # to the last object another bunch of X objects is fetched (if possible)
238 def __init__(self, input, fontsize, *attrs):
239 wx.grid.PyGridTableBase.__init__(self)
240 self.input = input
241 self.iterator = ipipe.xiter(input)
242 self.items = []
243 self.attrs = [ipipe.upgradexattr(attr) for attr in attrs]
244 self._displayattrs = self.attrs[:]
245 self._displayattrset = set(self.attrs)
246 self.fontsize = fontsize
247 self._fetch(1)
248 self.timer = wx.Timer()
249 self.timer.Bind(wx.EVT_TIMER, self.refresh_content)
250
251 def GetAttr(self, *args):
252 attr = wx.grid.GridCellAttr()
253 attr.SetFont(wx.Font(self.fontsize, wx.TELETYPE, wx.NORMAL, wx.NORMAL))
254 return attr
255
256 def GetNumberRows(self):
257 return len(self.items)
258
259 def GetNumberCols(self):
260 return len(self._displayattrs)
261
262 def GetColLabelValue(self, col):
263 if col < len(self._displayattrs):
264 return self._displayattrs[col].name()
265 else:
266 return ""
267
268 def GetRowLabelValue(self, row):
269 return str(row)
270
271 def IsEmptyCell(self, row, col):
272 return False
273
274 def _append(self, item):
275 self.items.append(item)
276 # Nothing to do if the set of attributes has been fixed by the user
277 if not self.attrs:
278 for attr in ipipe.xattrs(item):
279 attr = ipipe.upgradexattr(attr)
280 if attr not in self._displayattrset:
281 self._displayattrs.append(attr)
282 self._displayattrset.add(attr)
283
284 def _fetch(self, count):
285 # Try to fill ``self.items`` with at least ``count`` objects.
286 have = len(self.items)
287 while self.iterator is not None and have < count:
288 try:
289 item = self.iterator.next()
290 except StopIteration:
291 self.iterator = None
292 break
293 except (KeyboardInterrupt, SystemExit):
294 raise
295 except Exception as exc:
296 have += 1
297 self._append(exc)
298 self.iterator = None
299 break
300 else:
301 have += 1
302 self._append(item)
303
304 def GetValue(self, row, col):
305 # some kind of dummy-function: does not return anything but "";
306 # (The value isn't use anyway)
307 # its main task is to trigger the fetch of new objects
308 sizing_needed = False
309 had_cols = len(self._displayattrs)
310 had_rows = len(self.items)
311 if row == had_rows - 1 and self.iterator is not None:
312 self._fetch(row + 20)
313 sizing_needed = True
314 have_rows = len(self.items)
315 have_cols = len(self._displayattrs)
316 if have_rows > had_rows:
317 msg = wx.grid.GridTableMessage(self, wx.grid.GRIDTABLE_NOTIFY_ROWS_APPENDED, have_rows - had_rows)
318 self.GetView().ProcessTableMessage(msg)
319 sizing_needed = True
320 if row >= have_rows:
321 return ""
322 if have_cols != had_cols:
323 msg = wx.grid.GridTableMessage(self, wx.grid.GRIDTABLE_NOTIFY_COLS_APPENDED, have_cols - had_cols)
324 self.GetView().ProcessTableMessage(msg)
325 sizing_needed = True
326 if sizing_needed:
327 self.GetView().AutoSizeColumns(False)
328 return ""
329
330 def SetValue(self, row, col, value):
331 pass
332
333 def refresh_content(self, event):
334 msg = wx.grid.GridTableMessage(self, wx.grid.GRIDTABLE_NOTIFY_ROWS_DELETED, 0, self.GetNumberRows())
335 self.GetView().ProcessTableMessage(msg)
336 self.iterator = ipipe.xiter(self.input)
337 self.items = []
338 self.attrs = [] # _append will calculate new displayattrs
339 self._fetch(1) # fetch one...
340 if self.items:
341 msg = wx.grid.GridTableMessage(self, wx.grid.GRIDTABLE_NOTIFY_ROWS_APPENDED, 1)
342 self.GetView().ProcessTableMessage(msg)
343 self.GetValue(0, 0) # and trigger "fetch next 20"
344 item = self.items[0]
345 self.GetView().AutoSizeColumns(False)
346 panel = self.GetView().GetParent()
347 nb = panel.GetParent()
348 current = nb.GetSelection()
349 if nb.GetPage(current) == panel:
350 self.GetView().set_footer(item)
351
352 class IGridGrid(wx.grid.Grid):
353 # The actual grid
354 # all methods for selecting/sorting/picking/... data are implemented here
355 def __init__(self, panel, input, *attrs):
356 wx.grid.Grid.__init__(self, panel)
357 fontsize = 9
358 self.input = input
359 self.table = IGridTable(self.input, fontsize, *attrs)
360 self.SetTable(self.table, True)
361 self.SetSelectionMode(wx.grid.Grid.wxGridSelectRows)
362 self.SetDefaultRenderer(IGridRenderer(self.table))
363 self.EnableEditing(False)
364 self.Bind(wx.EVT_KEY_DOWN, self.key_pressed)
365 self.Bind(wx.grid.EVT_GRID_CELL_LEFT_DCLICK, self.cell_doubleclicked)
366 self.Bind(wx.grid.EVT_GRID_CELL_LEFT_CLICK, self.cell_leftclicked)
367 self.Bind(wx.grid.EVT_GRID_LABEL_LEFT_DCLICK, self.label_doubleclicked)
368 self.Bind(wx.grid.EVT_GRID_LABEL_LEFT_CLICK, self.on_label_leftclick)
369 self.Bind(wx.grid.EVT_GRID_RANGE_SELECT, self._on_selected_range)
370 self.Bind(wx.grid.EVT_GRID_SELECT_CELL, self._on_selected_cell)
371 self.current_selection = set()
372 self.maxchars = 200
373
374 def on_label_leftclick(self, event):
375 event.Skip()
376
377 def error_output(self, text):
378 wx.Bell()
379 frame = self.GetParent().GetParent().GetParent()
380 frame.SetStatusText(str(text))
381
382 def _on_selected_range(self, event):
383 # Internal update to the selection tracking lists
384 if event.Selecting():
385 # adding to the list...
386 self.current_selection.update(xrange(event.GetTopRow(), event.GetBottomRow()+1))
387 else:
388 # removal from list
389 for index in xrange(event.GetTopRow(), event.GetBottomRow()+1):
390 self.current_selection.discard(index)
391 event.Skip()
392
393 def _on_selected_cell(self, event):
394 # Internal update to the selection tracking list
395 self.current_selection = set([event.GetRow()])
396 event.Skip()
397
398 def sort(self, key, reverse=False):
399 """
400 Sort the current list of items using the key function ``key``. If
401 ``reverse`` is true the sort order is reversed.
402 """
403 row = self.GetGridCursorRow()
404 col = self.GetGridCursorCol()
405 curitem = self.table.items[row] # Remember where the cursor is now
406 # Sort items
407 def realkey(item):
408 try:
409 return key(item)
410 except (KeyboardInterrupt, SystemExit):
411 raise
412 except Exception:
413 return None
414 try:
415 self.table.items = ipipe.deque(sorted(self.table.items, key=realkey, reverse=reverse))
416 except TypeError as exc:
417 self.error_output("Exception encountered: %s" % exc)
418 return
419 # Find out where the object under the cursor went
420 for (i, item) in enumerate(self.table.items):
421 if item is curitem:
422 self.SetGridCursor(i,col)
423 self.MakeCellVisible(i,col)
424 self.Refresh()
425
426 def sortattrasc(self):
427 """
428 Sort in ascending order; sorting criteria is the current attribute
429 """
430 col = self.GetGridCursorCol()
431 attr = self.table._displayattrs[col]
432 frame = self.GetParent().GetParent().GetParent()
433 if attr is ipipe.noitem:
434 self.error_output("no column under cursor")
435 return
436 frame.SetStatusText("sort by %s (ascending)" % attr.name())
437 def key(item):
438 try:
439 return attr.value(item)
440 except (KeyboardInterrupt, SystemExit):
441 raise
442 except Exception:
443 return None
444 self.sort(key)
445
446 def sortattrdesc(self):
447 """
448 Sort in descending order; sorting criteria is the current attribute
449 """
450 col = self.GetGridCursorCol()
451 attr = self.table._displayattrs[col]
452 frame = self.GetParent().GetParent().GetParent()
453 if attr is ipipe.noitem:
454 self.error_output("no column under cursor")
455 return
456 frame.SetStatusText("sort by %s (descending)" % attr.name())
457 def key(item):
458 try:
459 return attr.value(item)
460 except (KeyboardInterrupt, SystemExit):
461 raise
462 except Exception:
463 return None
464 self.sort(key, reverse=True)
465
466 def label_doubleclicked(self, event):
467 row = event.GetRow()
468 col = event.GetCol()
469 if col == -1:
470 self.enter(row)
471
472 def _getvalue(self, row, col):
473 """
474 Gets the text which is displayed at ``(row, col)``
475 """
476 try:
477 value = self.table._displayattrs[col].value(self.table.items[row])
478 (align, width, text) = ipipe.xformat(value, "cell", self.maxchars)
479 except IndexError:
480 raise IndexError
481 except Exception as exc:
482 (align, width, text) = ipipe.xformat(exc, "cell", self.maxchars)
483 return text
484
485 def searchexpression(self, searchexp, startrow=None, search_forward=True ):
486 """
487 Find by expression
488 """
489 frame = self.GetParent().GetParent().GetParent()
490 if searchexp:
491 if search_forward:
492 if not startrow:
493 row = self.GetGridCursorRow()+1
494 else:
495 row = startrow + 1
496 while True:
497 try:
498 foo = self.table.GetValue(row, 0)
499 item = self.table.items[row]
500 try:
501 globals = ipipe.getglobals(None)
502 if eval(searchexp, globals, ipipe.AttrNamespace(item)):
503 self.SetGridCursor(row, 0) # found something
504 self.MakeCellVisible(row, 0)
505 break
506 except (KeyboardInterrupt, SystemExit):
507 raise
508 except Exception as exc:
509 frame.SetStatusText(str(exc))
510 wx.Bell()
511 break # break on error
512 except IndexError:
513 return
514 row += 1
515 else:
516 if not startrow:
517 row = self.GetGridCursorRow() - 1
518 else:
519 row = startrow - 1
520 while True:
521 try:
522 foo = self.table.GetValue(row, 0)
523 item = self.table.items[row]
524 try:
525 globals = ipipe.getglobals(None)
526 if eval(searchexp, globals, ipipe.AttrNamespace(item)):
527 self.SetGridCursor(row, 0) # found something
528 self.MakeCellVisible(row, 0)
529 break
530 except (KeyboardInterrupt, SystemExit):
531 raise
532 except Exception as exc:
533 frame.SetStatusText(str(exc))
534 wx.Bell()
535 break # break on error
536 except IndexError:
537 return
538 row -= 1
539
540
541 def search(self, searchtext, startrow=None, startcol=None, search_forward=True):
542 """
543 search for ``searchtext``, starting in ``(startrow, startcol)``;
544 if ``search_forward`` is true the direction is "forward"
545 """
546 searchtext = searchtext.lower()
547 if search_forward:
548 if startrow is not None and startcol is not None:
549 row = startrow
550 else:
551 startcol = self.GetGridCursorCol() + 1
552 row = self.GetGridCursorRow()
553 if startcol >= self.GetNumberCols():
554 startcol = 0
555 row += 1
556 while True:
557 for col in xrange(startcol, self.table.GetNumberCols()):
558 try:
559 foo = self.table.GetValue(row, col)
560 text = self._getvalue(row, col)
561 if searchtext in text.string().lower():
562 self.SetGridCursor(row, col)
563 self.MakeCellVisible(row, col)
564 return
565 except IndexError:
566 return
567 startcol = 0
568 row += 1
569 else:
570 if startrow is not None and startcol is not None:
571 row = startrow
572 else:
573 startcol = self.GetGridCursorCol() - 1
574 row = self.GetGridCursorRow()
575 if startcol < 0:
576 startcol = self.GetNumberCols() - 1
577 row -= 1
578 while True:
579 for col in xrange(startcol, -1, -1):
580 try:
581 foo = self.table.GetValue(row, col)
582 text = self._getvalue(row, col)
583 if searchtext in text.string().lower():
584 self.SetGridCursor(row, col)
585 self.MakeCellVisible(row, col)
586 return
587 except IndexError:
588 return
589 startcol = self.table.GetNumberCols()-1
590 row -= 1
591
592 def key_pressed(self, event):
593 """
594 Maps pressed keys to functions
595 """
596 frame = self.GetParent().GetParent().GetParent()
597 frame.SetStatusText("")
598 sh = event.ShiftDown()
599 ctrl = event.ControlDown()
600
601 keycode = event.GetKeyCode()
602 if keycode == ord("P"):
603 row = self.GetGridCursorRow()
604 if sh:
605 col = self.GetGridCursorCol()
606 self.pickattr(row, col)
607 else:
608 self.pick(row)
609 elif keycode == ord("M"):
610 if ctrl:
611 col = self.GetGridCursorCol()
612 self.pickrowsattr(sorted(self.current_selection), col)
613 else:
614 self.pickrows(sorted(self.current_selection))
615 elif keycode in (wx.WXK_BACK, wx.WXK_DELETE, ord("X")) and not (ctrl or sh):
616 self.delete_current_notebook()
617 elif keycode in (ord("E"), ord("\r")):
618 row = self.GetGridCursorRow()
619 if sh:
620 col = self.GetGridCursorCol()
621 self.enterattr(row, col)
622 else:
623 self.enter(row)
624 elif keycode == ord("E") and ctrl:
625 row = self.GetGridCursorRow()
626 self.SetGridCursor(row, self.GetNumberCols()-1)
627 elif keycode == wx.WXK_HOME or (keycode == ord("A") and ctrl):
628 row = self.GetGridCursorRow()
629 self.SetGridCursor(row, 0)
630 elif keycode == ord("C") and sh:
631 col = self.GetGridCursorCol()
632 attr = self.table._displayattrs[col]
633 result = []
634 for i in xrange(self.GetNumberRows()):
635 result.append(self.table._displayattrs[col].value(self.table.items[i]))
636 self.quit(result)
637 elif keycode in (wx.WXK_ESCAPE, ord("Q")) and not (ctrl or sh):
638 self.quit()
639 elif keycode == ord("<"):
640 row = self.GetGridCursorRow()
641 col = self.GetGridCursorCol()
642 if not event.ShiftDown():
643 newcol = col - 1
644 if newcol >= 0:
645 self.SetGridCursor(row, col - 1)
646 else:
647 newcol = col + 1
648 if newcol < self.GetNumberCols():
649 self.SetGridCursor(row, col + 1)
650 elif keycode == ord("D"):
651 col = self.GetGridCursorCol()
652 row = self.GetGridCursorRow()
653 if not sh:
654 self.detail(row, col)
655 else:
656 self.detail_attr(row, col)
657 elif keycode == ord("F") and ctrl:
658 if sh:
659 frame.enter_searchexpression(event)
660 else:
661 frame.enter_searchtext(event)
662 elif keycode == wx.WXK_F3:
663 if sh:
664 frame.find_previous(event)
665 else:
666 frame.find_next(event)
667 elif keycode == ord("V"):
668 if sh:
669 self.sortattrdesc()
670 else:
671 self.sortattrasc()
672 elif keycode == wx.WXK_DOWN:
673 row = self.GetGridCursorRow()
674 try:
675 item = self.table.items[row+1]
676 except IndexError:
677 item = self.table.items[row]
678 self.set_footer(item)
679 event.Skip()
680 elif keycode == wx.WXK_UP:
681 row = self.GetGridCursorRow()
682 if row >= 1:
683 item = self.table.items[row-1]
684 else:
685 item = self.table.items[row]
686 self.set_footer(item)
687 event.Skip()
688 elif keycode == wx.WXK_RIGHT:
689 row = self.GetGridCursorRow()
690 item = self.table.items[row]
691 self.set_footer(item)
692 event.Skip()
693 elif keycode == wx.WXK_LEFT:
694 row = self.GetGridCursorRow()
695 item = self.table.items[row]
696 self.set_footer(item)
697 event.Skip()
698 elif keycode == ord("R") or keycode == wx.WXK_F5:
699 self.table.refresh_content(event)
700 elif keycode == ord("I"):
701 row = self.GetGridCursorRow()
702 if not sh:
703 self.pickinput(row)
704 else:
705 col = self.GetGridCursorCol()
706 self.pickinputattr(row, col)
707 else:
708 event.Skip()
709
710 def delete_current_notebook(self):
711 """
712 deletes the current notebook tab
713 """
714 panel = self.GetParent()
715 nb = panel.GetParent()
716 current = nb.GetSelection()
717 count = nb.GetPageCount()
718 if count > 1:
719 for i in xrange(count-1, current-1, -1):
720 nb.DeletePage(i)
721 nb.GetCurrentPage().grid.SetFocus()
722 else:
723 frame = nb.GetParent()
724 frame.SetStatusText("This is the last level!")
725
726 def _doenter(self, value, *attrs):
727 """
728 "enter" a special item resulting in a new notebook tab
729 """
730 panel = self.GetParent()
731 nb = panel.GetParent()
732 frame = nb.GetParent()
733 current = nb.GetSelection()
734 count = nb.GetPageCount()
735 try: # if we want to enter something non-iterable, e.g. a function
736 if current + 1 == count and value is not self.input: # we have an event in the last tab
737 frame._add_notebook(value, *attrs)
738 elif value != self.input: # we have to delete all tabs newer than [panel] first
739 for i in xrange(count-1, current, -1): # some tabs don't close if we don't close in *reverse* order
740 nb.DeletePage(i)
741 frame._add_notebook(value)
742 except TypeError as exc:
743 if exc.__class__.__module__ == "exceptions":
744 msg = "%s: %s" % (exc.__class__.__name__, exc)
745 else:
746 msg = "%s.%s: %s" % (exc.__class__.__module__, exc.__class__.__name__, exc)
747 frame.SetStatusText(msg)
748
749 def enterattr(self, row, col):
750 try:
751 attr = self.table._displayattrs[col]
752 value = attr.value(self.table.items[row])
753 except Exception as exc:
754 self.error_output(str(exc))
755 else:
756 self._doenter(value)
757
758 def set_footer(self, item):
759 frame = self.GetParent().GetParent().GetParent()
760 frame.SetStatusText(" ".join([str(text) for (style, text) in ipipe.xformat(item, "footer", 20)[2]]), 0)
761
762 def enter(self, row):
763 try:
764 value = self.table.items[row]
765 except Exception as exc:
766 self.error_output(str(exc))
767 else:
768 self._doenter(value)
769
770 def detail(self, row, col):
771 """
772 shows a detail-view of the current cell
773 """
774 try:
775 attr = self.table._displayattrs[col]
776 item = self.table.items[row]
777 except Exception as exc:
778 self.error_output(str(exc))
779 else:
780 attrs = [ipipe.AttributeDetail(item, attr) for attr in ipipe.xattrs(item, "detail")]
781 self._doenter(attrs)
782
783 def detail_attr(self, row, col):
784 try:
785 attr = self.table._displayattrs[col]
786 item = attr.value(self.table.items[row])
787 except Exception as exc:
788 self.error_output(str(exc))
789 else:
790 attrs = [ipipe.AttributeDetail(item, attr) for attr in ipipe.xattrs(item, "detail")]
791 self._doenter(attrs)
792
793 def quit(self, result=None):
794 """
795 quit
796 """
797 frame = self.GetParent().GetParent().GetParent()
798 if frame.helpdialog:
799 frame.helpdialog.Destroy()
800 app = frame.parent
801 if app is not None:
802 app.result = result
803 frame.Close()
804 frame.Destroy()
805
806 def cell_doubleclicked(self, event):
807 self.enterattr(event.GetRow(), event.GetCol())
808 event.Skip()
809
810 def cell_leftclicked(self, event):
811 row = event.GetRow()
812 item = self.table.items[row]
813 self.set_footer(item)
814 event.Skip()
815
816 def pick(self, row):
817 """
818 pick a single row and return to the IPython prompt
819 """
820 try:
821 value = self.table.items[row]
822 except Exception as exc:
823 self.error_output(str(exc))
824 else:
825 self.quit(value)
826
827 def pickinput(self, row):
828 try:
829 value = self.table.items[row]
830 except Exception as exc:
831 self.error_output(str(exc))
832 else:
833 api = ipapi.get()
834 api.set_next_input(str(value))
835 self.quit(value)
836
837 def pickinputattr(self, row, col):
838 try:
839 attr = self.table._displayattrs[col]
840 value = attr.value(self.table.items[row])
841 except Exception as exc:
842 self.error_output(str(exc))
843 else:
844 api = ipapi.get()
845 api.set_next_input(str(value))
846 self.quit(value)
847
848 def pickrows(self, rows):
849 """
850 pick multiple rows and return to the IPython prompt
851 """
852 try:
853 value = [self.table.items[row] for row in rows]
854 except Exception as exc:
855 self.error_output(str(exc))
856 else:
857 self.quit(value)
858
859 def pickrowsattr(self, rows, col):
860 """"
861 pick one column from multiple rows
862 """
863 values = []
864 try:
865 attr = self.table._displayattrs[col]
866 for row in rows:
867 try:
868 values.append(attr.value(self.table.items[row]))
869 except (SystemExit, KeyboardInterrupt):
870 raise
871 except Exception:
872 raise #pass
873 except Exception as exc:
874 self.error_output(str(exc))
875 else:
876 self.quit(values)
877
878 def pickattr(self, row, col):
879 try:
880 attr = self.table._displayattrs[col]
881 value = attr.value(self.table.items[row])
882 except Exception as exc:
883 self.error_output(str(exc))
884 else:
885 self.quit(value)
886
887
888 class IGridPanel(wx.Panel):
889 # Each IGridPanel contains an IGridGrid
890 def __init__(self, parent, input, *attrs):
891 wx.Panel.__init__(self, parent, -1)
892 self.grid = IGridGrid(self, input, *attrs)
893 self.grid.FitInside()
894 sizer = wx.BoxSizer(wx.VERTICAL)
895 sizer.Add(self.grid, proportion=1, flag=wx.EXPAND | wx.ALL, border=10)
896 self.SetSizer(sizer)
897 sizer.Fit(self)
898 sizer.SetSizeHints(self)
899
900
901 class IGridHTMLHelp(wx.Frame):
902 def __init__(self, parent, title, size):
903 wx.Frame.__init__(self, parent, -1, title, size=size)
904 html = wx.html.HtmlWindow(self)
905 if "gtk2" in wx.PlatformInfo:
906 html.SetStandardFonts()
907 html.SetPage(help)
908
909
910 class IGridFrame(wx.Frame):
911 maxtitlelen = 30
912
913 def __init__(self, parent, input):
914 title = " ".join([str(text) for (style, text) in ipipe.xformat(input, "header", 20)[2]])
915 wx.Frame.__init__(self, None, title=title, size=(640, 480))
916 self.menubar = wx.MenuBar()
917 self.menucounter = 100
918 self.m_help = wx.Menu()
919 self.m_search = wx.Menu()
920 self.m_sort = wx.Menu()
921 self.m_refresh = wx.Menu()
922 self.notebook = wx.Notebook(self, -1, style=0)
923 self.statusbar = self.CreateStatusBar(1, wx.ST_SIZEGRIP)
924 self.statusbar.SetFieldsCount(2)
925 self.SetStatusWidths([-1, 200])
926 self.parent = parent
927 self._add_notebook(input)
928 self.Bind(wx.EVT_CLOSE, self.OnCloseWindow)
929 self.makemenu(self.m_sort, "&Sort (asc)\tV", "Sort ascending", self.sortasc)
930 self.makemenu(self.m_sort, "Sort (&desc)\tShift-V", "Sort descending", self.sortdesc)
931 self.makemenu(self.m_help, "&Help\tF1", "Help", self.display_help)
932 # self.makemenu(self.m_help, "&Show help in browser", "Show help in browser", self.display_help_in_browser)
933 self.makemenu(self.m_search, "&Find text\tCTRL-F", "Find text", self.enter_searchtext)
934 self.makemenu(self.m_search, "Find by &expression\tCTRL-Shift-F", "Find by expression", self.enter_searchexpression)
935 self.makemenu(self.m_search, "Find &next\tF3", "Find next", self.find_next)
936 self.makemenu(self.m_search, "Find &previous\tShift-F3", "Find previous", self.find_previous)
937 self.makemenu(self.m_refresh, "&Refresh once \tF5", "Refresh once", self.refresh_once)
938 self.makemenu(self.m_refresh, "Refresh every &1s", "Refresh every second", self.refresh_every_second)
939 self.makemenu(self.m_refresh, "Refresh every &X seconds", "Refresh every X seconds", self.refresh_interval)
940 self.makemenu(self.m_refresh, "&Stop all refresh timers", "Stop refresh timers", self.stop_refresh)
941 self.menubar.Append(self.m_search, "&Find")
942 self.menubar.Append(self.m_sort, "&Sort")
943 self.menubar.Append(self.m_refresh, "&Refresh")
944 self.menubar.Append(self.m_help, "&Help")
945 self.SetMenuBar(self.menubar)
946 self.searchtext = ""
947 self.searchexpression = ""
948 self.helpdialog = None
949 self.refresh_interval = 1000
950 self.SetStatusText("Refreshing inactive", 1)
951
952 def refresh_once(self, event):
953 table = self.notebook.GetPage(self.notebook.GetSelection()).grid.table
954 table.refresh_content(event)
955
956 def refresh_interval(self, event):
957 table = self.notebook.GetPage(self.notebook.GetSelection()).grid.table
958 dlg = wx.TextEntryDialog(self, "Enter refresh interval (milliseconds):", "Refresh timer:", defaultValue=str(self.refresh_interval))
959 if dlg.ShowModal() == wx.ID_OK:
960 try:
961 milliseconds = int(dlg.GetValue())
962 except ValueError as exc:
963 self.SetStatusText(str(exc))
964 else:
965 table.timer.Start(milliseconds=milliseconds, oneShot=False)
966 self.SetStatusText("Refresh timer set to %s ms" % milliseconds)
967 self.SetStatusText("Refresh interval: %s ms" % milliseconds, 1)
968 self.refresh_interval = milliseconds
969 dlg.Destroy()
970
971 def stop_refresh(self, event):
972 for i in xrange(self.notebook.GetPageCount()):
973 nb = self.notebook.GetPage(i)
974 nb.grid.table.timer.Stop()
975 self.SetStatusText("Refreshing inactive", 1)
976
977 def refresh_every_second(self, event):
978 table = self.notebook.GetPage(self.notebook.GetSelection()).grid.table
979 table.timer.Start(milliseconds=1000, oneShot=False)
980 self.SetStatusText("Refresh interval: 1000 ms", 1)
981
982 def sortasc(self, event):
983 grid = self.notebook.GetPage(self.notebook.GetSelection()).grid
984 grid.sortattrasc()
985
986 def sortdesc(self, event):
987 grid = self.notebook.GetPage(self.notebook.GetSelection()).grid
988 grid.sortattrdesc()
989
990 def find_previous(self, event):
991 """
992 find previous occurrences
993 """
994 grid = self.notebook.GetPage(self.notebook.GetSelection()).grid
995 if self.searchtext:
996 row = grid.GetGridCursorRow()
997 col = grid.GetGridCursorCol()
998 self.SetStatusText('Search mode: text; looking for %s' % self.searchtext)
999 if col-1 >= 0:
1000 grid.search(self.searchtext, row, col-1, False)
1001 else:
1002 grid.search(self.searchtext, row-1, grid.table.GetNumberCols()-1, False)
1003 elif self.searchexpression:
1004 self.SetStatusText("Search mode: expression; looking for %s" % repr(self.searchexpression)[2:-1])
1005 grid.searchexpression(searchexp=self.searchexpression, search_forward=False)
1006 else:
1007 self.SetStatusText("No search yet: please enter search-text or -expression")
1008
1009 def find_next(self, event):
1010 """
1011 find the next occurrence
1012 """
1013 grid = self.notebook.GetPage(self.notebook.GetSelection()).grid
1014 if self.searchtext != "":
1015 row = grid.GetGridCursorRow()
1016 col = grid.GetGridCursorCol()
1017 self.SetStatusText('Search mode: text; looking for %s' % self.searchtext)
1018 if col+1 < grid.table.GetNumberCols():
1019 grid.search(self.searchtext, row, col+1)
1020 else:
1021 grid.search(self.searchtext, row+1, 0)
1022 elif self.searchexpression != "":
1023 self.SetStatusText('Search mode: expression; looking for %s' % repr(self.searchexpression)[2:-1])
1024 grid.searchexpression(searchexp=self.searchexpression)
1025 else:
1026 self.SetStatusText("No search yet: please enter search-text or -expression")
1027
1028 def display_help(self, event):
1029 """
1030 Display a help dialog
1031 """
1032 if self.helpdialog:
1033 self.helpdialog.Destroy()
1034 self.helpdialog = IGridHTMLHelp(None, title="Help", size=wx.Size(600,400))
1035 self.helpdialog.Show()
1036
1037 def display_help_in_browser(self, event):
1038 """
1039 Show the help-HTML in a browser (as a ``HtmlWindow`` does not understand
1040 CSS this looks better)
1041 """
1042 filename = urllib.pathname2url(os.path.abspath(os.path.join(os.path.dirname(__file__), "igrid_help.html")))
1043 if not filename.startswith("file"):
1044 filename = "file:" + filename
1045 webbrowser.open(filename, new=1, autoraise=True)
1046
1047 def enter_searchexpression(self, event):
1048 dlg = wx.TextEntryDialog(self, "Find:", "Find matching expression:", defaultValue=self.searchexpression)
1049 if dlg.ShowModal() == wx.ID_OK:
1050 self.searchexpression = dlg.GetValue()
1051 self.searchtext = ""
1052 self.SetStatusText('Search mode: expression; looking for %s' % repr(self.searchexpression)[2:-1])
1053 self.notebook.GetPage(self.notebook.GetSelection()).grid.searchexpression(self.searchexpression)
1054 dlg.Destroy()
1055
1056 def makemenu(self, menu, label, help, cmd):
1057 menu.Append(self.menucounter, label, help)
1058 self.Bind(wx.EVT_MENU, cmd, id=self.menucounter)
1059 self.menucounter += 1
1060
1061 def _add_notebook(self, input, *attrs):
1062 # Adds another notebook which has the starting object ``input``
1063 panel = IGridPanel(self.notebook, input, *attrs)
1064 text = str(ipipe.xformat(input, "header", self.maxtitlelen)[2])
1065 if len(text) >= self.maxtitlelen:
1066 text = text[:self.maxtitlelen].rstrip(".") + "..."
1067 self.notebook.AddPage(panel, text, True)
1068 panel.grid.SetFocus()
1069 self.Layout()
1070
1071 def OnCloseWindow(self, event):
1072 self.Destroy()
1073
1074 def enter_searchtext(self, event):
1075 # Displays a dialog asking for the searchtext
1076 dlg = wx.TextEntryDialog(self, "Find:", "Find in list", defaultValue=self.searchtext)
1077 if dlg.ShowModal() == wx.ID_OK:
1078 self.searchtext = dlg.GetValue()
1079 self.searchexpression = ""
1080 self.SetStatusText('Search mode: text; looking for %s' % self.searchtext)
1081 self.notebook.GetPage(self.notebook.GetSelection()).grid.search(self.searchtext)
1082 dlg.Destroy()
1083
1084
1085 class App(wx.App):
1086 def __init__(self, input):
1087 self.input = input
1088 self.result = None # Result to be returned to IPython. Set by quit().
1089 wx.App.__init__(self)
1090
1091 def OnInit(self):
1092 frame = IGridFrame(self, self.input)
1093 frame.Show()
1094 self.SetTopWindow(frame)
1095 frame.Raise()
1096 return True
1097
1098
1099 class igrid(ipipe.Display):
1100 """
1101 This is a wx-based display object that can be used instead of ``ibrowse``
1102 (which is curses-based) or ``idump`` (which simply does a print).
1103 """
1104
1105 if wx.VERSION < (2, 7):
1106 def display(self):
1107 try:
1108 # Try to create a "standalone" frame. If this works we're probably
1109 # running with -wthread.
1110 # Note that this sets the parent of the frame to None, but we can't
1111 # pass a result object back to the shell anyway.
1112 frame = IGridFrame(None, self.input)
1113 frame.Show()
1114 frame.Raise()
1115 except wx.PyNoAppError:
1116 # There's no wx application yet => create one.
1117 app = App(self.input)
1118 app.MainLoop()
1119 return app.result
1120 else:
1121 # With wx 2.7 it gets simpler.
1122 def display(self):
1123 app = App(self.input)
1124 app.MainLoop()
1125 return app.result
1126
@@ -1,45 +0,0 b''
1 body
2 {
3 background-color: #fff;
4 color: #000;
5 font-family: "Verdana", "Arial", "XHelvetica", "Helvetica", sans-serif;
6 padding: 20px 30px;
7 margin: 0px;
8 font-size: 11px;
9 }
10 h1
11 {
12 font-family: "Trebuchet MS", sans-serif;
13 font-size: 24px;
14 margin: -20px -30px 4px -30px;
15 padding: 6px 30px;
16 font-weight: normal;
17 border-bottom: 1px solid #000;
18 letter-spacing: 1px;
19 background-color: #666;
20 color: #fff;
21 }
22 h2
23 {
24 font-family: "Trebuchet MS", sans-serif;
25 font-size: 20px;
26 padding: 14px 0px 2px 0px;
27 margin: 0px;
28 font-weight: bold;
29 color: #333;
30 }
31 h3
32 {
33 font-family: "Trebuchet MS", sans-serif;
34 font-size: 14px;
35 padding: 12px 0px 2px 0px;
36 margin: 0px;
37 font-weight: bold;
38 color: #333;
39 }
40 p
41 {
42 line-height: 120%;
43 margin: 0px 0px 6px 0px;
44 padding: 0px;
45 }
@@ -1,86 +0,0 b''
1 <?xml version='1.0' encoding='iso-8859-1'?>
2 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
3 <html>
4 <head>
5 <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
6 <link rel="stylesheet" href="igrid_help.css" type="text/css" />
7 <title>igrid help</title>
8 </head>
9 <body>
10 <h1>igrid help</h1>
11
12
13 <h2>Commands</h2>
14
15
16 <h3>pick (P)</h3>
17 <p>Pick the whole row (object is available as "_")</p>
18
19 <h3>pickattr (Shift-P)</h3>
20 <p>Pick the attribute under the cursor</p>
21
22 <h3>pickallattrs (Shift-C)</h3>
23 <p>Pick' the complete column under the cursor (i.e. the attribute under the
24 cursor) from all currently fetched objects. These attributes will be returned
25 as a list.</p>
26
27 <h3>enter (E)</h3>
28 <p>Enter the object under the cursor. (what this mean depends on the object
29 itself, i.e. how it implements iteration). This opens a new browser 'level'.</p>
30
31 <h3>enterattr (Shift-E)</h3>
32 <p>Enter the attribute under the cursor.</p>
33
34 <h3>detail (D)</h3>
35 <p>Show a detail view of the object under the cursor. This shows the name,
36 type, doc string and value of the object attributes (and it might show more
37 attributes than in the list view, depending on the object).</p>
38
39 <h3>detailattr (Shift-D)</h3>
40 <p>Show a detail view of the attribute under the cursor.</p>
41
42 <h3>pickrows (M)</h3>
43 <p>Pick multiple selected rows (M)</p>
44
45 <h3>pickrowsattr (CTRL-M)</h3>
46 <p>From multiple selected rows pick the cells matching the attribute the cursor is in (CTRL-M)</p>
47
48 <h3>find (CTRL-F)</h3>
49 <p>Find text</p>
50
51 <h3>find_next (F3)</h3>
52 <p>Find next occurrence of the searchtext</p>
53
54 <h3>find_previous (Shift-F3)</h3>
55 <p>Find previous occurrence of the searchtext </p>
56
57 <h3>sortattrasc (V)</h3>
58 <p>Sort the objects (in ascending order) using the attribute under the cursor as the sort key.</p>
59
60 <h3>sortattrdesc (Shift-V)</h3>
61 <p>Sort the objects (in descending order) using the attribute under the cursor as the sort key.</p>
62
63 <h3>leave (Backspace, DEL, X)</h3>
64 <p>Close current tab (and all the tabs to the right of the current one).</h3>
65
66 <h3>quit (ESC,Q)</h3>
67 <p>Quit igrid and return to the IPython prompt.</p>
68
69
70 <h2>Navigation</h2>
71
72
73 <h3>Jump to the last column of the current row (END, CTRL-E, CTRL-Right)</h3>
74
75 <h3>Jump to the first column of the current row (HOME, CTRL-A, CTRL-Left)</h3>
76
77 <h3>Move the cursor one column to the left (&lt;)</h3>
78
79 <h3>Move the cursor one column to the right (&gt;)</h3>
80
81 <h3>Jump to the first row in the current column (CTRL-Up)</h3>
82
83 <h3>Jump to the last row in the current column (CTRL-Down)</h3>
84
85 </body>
86 </html>
This diff has been collapsed as it changes many lines, (2328 lines changed) Show them Hide them
@@ -1,2328 +0,0 b''
1 # -*- coding: iso-8859-1 -*-
2
3 """
4 ``ipipe`` provides classes to be used in an interactive Python session. Doing a
5 ``from ipipe import *`` is the preferred way to do this. The name of all
6 objects imported this way starts with ``i`` to minimize collisions.
7
8 ``ipipe`` supports "pipeline expressions", which is something resembling Unix
9 pipes. An example is::
10
11 >>> ienv | isort("key.lower()")
12
13 This gives a listing of all environment variables sorted by name.
14
15
16 There are three types of objects in a pipeline expression:
17
18 * ``Table``s: These objects produce items. Examples are ``ils`` (listing the
19 current directory, ``ienv`` (listing environment variables), ``ipwd`` (listing
20 user accounts) and ``igrp`` (listing user groups). A ``Table`` must be the
21 first object in a pipe expression.
22
23 * ``Pipe``s: These objects sit in the middle of a pipe expression. They
24 transform the input in some way (e.g. filtering or sorting it). Examples are:
25 ``ifilter`` (which filters the input pipe), ``isort`` (which sorts the input
26 pipe) and ``ieval`` (which evaluates a function or expression for each object
27 in the input pipe).
28
29 * ``Display``s: These objects can be put as the last object in a pipeline
30 expression. There are responsible for displaying the result of the pipeline
31 expression. If a pipeline expression doesn't end in a display object a default
32 display objects will be used. One example is ``ibrowse`` which is a ``curses``
33 based browser.
34
35
36 Adding support for pipeline expressions to your own objects can be done through
37 three extensions points (all of them optional):
38
39 * An object that will be displayed as a row by a ``Display`` object should
40 implement the method ``__xattrs__(self, mode)`` method or register an
41 implementation of the generic function ``xattrs``. For more info see ``xattrs``.
42
43 * When an object ``foo`` is displayed by a ``Display`` object, the generic
44 function ``xrepr`` is used.
45
46 * Objects that can be iterated by ``Pipe``s must iterable. For special cases,
47 where iteration for display is different than the normal iteration a special
48 implementation can be registered with the generic function ``xiter``. This
49 makes it possible to use dictionaries and modules in pipeline expressions,
50 for example::
51
52 >>> import sys
53 >>> sys | ifilter("isinstance(value, int)") | idump
54 key |value
55 api_version| 1012
56 dllhandle | 503316480
57 hexversion | 33817328
58 maxint |2147483647
59 maxunicode | 65535
60 >>> sys.modules | ifilter("_.value is not None") | isort("_.key.lower()")
61 ...
62
63 Note: The expression strings passed to ``ifilter()`` and ``isort()`` can
64 refer to the object to be filtered or sorted via the variable ``_`` and to any
65 of the attributes of the object, i.e.::
66
67 >>> sys.modules | ifilter("_.value is not None") | isort("_.key.lower()")
68
69 does the same as::
70
71 >>> sys.modules | ifilter("value is not None") | isort("key.lower()")
72
73 In addition to expression strings, it's possible to pass callables (taking
74 the object as an argument) to ``ifilter()``, ``isort()`` and ``ieval()``::
75
76 >>> sys | ifilter(lambda _:isinstance(_.value, int)) \
77 ... | ieval(lambda _: (_.key, hex(_.value))) | idump
78 0 |1
79 api_version|0x3f4
80 dllhandle |0x1e000000
81 hexversion |0x20402f0
82 maxint |0x7fffffff
83 maxunicode |0xffff
84 """
85
86 skip_doctest = True # ignore top-level docstring as a doctest.
87
88 import sys, os, os.path, stat, glob, new, csv, datetime, types
89 import itertools, mimetypes, StringIO
90
91 try: # Python 2.3 compatibility
92 import collections
93 except ImportError:
94 deque = list
95 else:
96 deque = collections.deque
97
98 try: # Python 2.3 compatibility
99 set
100 except NameError:
101 import sets
102 set = sets.Set
103
104 try: # Python 2.3 compatibility
105 sorted
106 except NameError:
107 def sorted(iterator, key=None, reverse=False):
108 items = list(iterator)
109 if key is not None:
110 items.sort(lambda i1, i2: cmp(key(i1), key(i2)))
111 else:
112 items.sort()
113 if reverse:
114 items.reverse()
115 return items
116
117 try: # Python 2.4 compatibility
118 GeneratorExit
119 except NameError:
120 GeneratorExit = SystemExit
121
122 try:
123 import pwd
124 except ImportError:
125 pwd = None
126
127 try:
128 import grp
129 except ImportError:
130 grp = None
131
132 from IPython.external import simplegeneric
133 from IPython.external import path
134
135 try:
136 import IPython.utils.io
137 from IPython.utils import generics
138 except ImportError:
139 Term = None
140 generics = None
141
142 from IPython.core import ipapi
143
144
145 __all__ = [
146 "ifile", "ils", "iglob", "iwalk", "ipwdentry", "ipwd", "igrpentry", "igrp",
147 "icsv", "ix", "ichain", "isort", "ifilter", "ieval", "ienum",
148 "ienv", "ihist", "ialias", "icap", "idump", "iless"
149 ]
150
151
152 os.stat_float_times(True) # enable microseconds
153
154
155 class AttrNamespace(object):
156 """
157 Helper class that is used for providing a namespace for evaluating
158 expressions containing attribute names of an object.
159 """
160 def __init__(self, wrapped):
161 self.wrapped = wrapped
162
163 def __getitem__(self, name):
164 if name == "_":
165 return self.wrapped
166 try:
167 return getattr(self.wrapped, name)
168 except AttributeError:
169 raise KeyError(name)
170
171 # Python 2.3 compatibility
172 # use eval workaround to find out which names are used in the
173 # eval string and put them into the locals. This works for most
174 # normal uses case, bizarre ones like accessing the locals()
175 # will fail
176 try:
177 eval("_", None, AttrNamespace(None))
178 except TypeError:
179 real_eval = eval
180 def eval(codestring, _globals, _locals):
181 """
182 eval(source[, globals[, locals]]) -> value
183
184 Evaluate the source in the context of globals and locals.
185 The source may be a string representing a Python expression
186 or a code object as returned by compile().
187 The globals must be a dictionary and locals can be any mappping.
188
189 This function is a workaround for the shortcomings of
190 Python 2.3's eval.
191 """
192
193 if isinstance(codestring, basestring):
194 code = compile(codestring, "_eval", "eval")
195 else:
196 code = codestring
197 newlocals = {}
198 for name in code.co_names:
199 try:
200 newlocals[name] = _locals[name]
201 except KeyError:
202 pass
203 return real_eval(code, _globals, newlocals)
204
205
206 noitem = object()
207
208
209 def item(iterator, index, default=noitem):
210 """
211 Return the ``index``th item from the iterator ``iterator``.
212 ``index`` must be an integer (negative integers are relative to the
213 end (i.e. the last items produced by the iterator)).
214
215 If ``default`` is given, this will be the default value when
216 the iterator doesn't contain an item at this position. Otherwise an
217 ``IndexError`` will be raised.
218
219 Note that using this function will partially or totally exhaust the
220 iterator.
221 """
222 i = index
223 if i>=0:
224 for item in iterator:
225 if not i:
226 return item
227 i -= 1
228 else:
229 i = -index
230 cache = deque()
231 for item in iterator:
232 cache.append(item)
233 if len(cache)>i:
234 cache.popleft()
235 if len(cache)==i:
236 return cache.popleft()
237 if default is noitem:
238 raise IndexError(index)
239 else:
240 return default
241
242
243 def getglobals(g):
244 """
245 Return the global namespace that is used for expression strings in
246 ``ifilter`` and others. This is ``g`` or (if ``g`` is ``None``) IPython's
247 user namespace.
248 """
249 if g is None:
250 if ipapi is not None:
251 api = ipapi.get()
252 if api is not None:
253 return api.user_ns
254 return globals()
255 return g
256
257
258 class Descriptor(object):
259 """
260 A ``Descriptor`` object is used for describing the attributes of objects.
261 """
262 def __hash__(self):
263 return hash(self.__class__) ^ hash(self.key())
264
265 def __eq__(self, other):
266 return self.__class__ is other.__class__ and self.key() == other.key()
267
268 def __ne__(self, other):
269 return self.__class__ is not other.__class__ or self.key() != other.key()
270
271 def key(self):
272 pass
273
274 def name(self):
275 """
276 Return the name of this attribute for display by a ``Display`` object
277 (e.g. as a column title).
278 """
279 key = self.key()
280 if key is None:
281 return "_"
282 return str(key)
283
284 def attrtype(self, obj):
285 """
286 Return the type of this attribute (i.e. something like "attribute" or
287 "method").
288 """
289
290 def valuetype(self, obj):
291 """
292 Return the type of this attribute value of the object ``obj``.
293 """
294
295 def value(self, obj):
296 """
297 Return the value of this attribute of the object ``obj``.
298 """
299
300 def doc(self, obj):
301 """
302 Return the documentation for this attribute.
303 """
304
305 def shortdoc(self, obj):
306 """
307 Return a short documentation for this attribute (defaulting to the
308 first line).
309 """
310 doc = self.doc(obj)
311 if doc is not None:
312 doc = doc.strip().splitlines()[0].strip()
313 return doc
314
315 def iter(self, obj):
316 """
317 Return an iterator for this attribute of the object ``obj``.
318 """
319 return xiter(self.value(obj))
320
321
322 class SelfDescriptor(Descriptor):
323 """
324 A ``SelfDescriptor`` describes the object itself.
325 """
326 def key(self):
327 return None
328
329 def attrtype(self, obj):
330 return "self"
331
332 def valuetype(self, obj):
333 return type(obj)
334
335 def value(self, obj):
336 return obj
337
338 def __repr__(self):
339 return "Self"
340
341 selfdescriptor = SelfDescriptor() # there's no need for more than one
342
343
344 class AttributeDescriptor(Descriptor):
345 """
346 An ``AttributeDescriptor`` describes a simple attribute of an object.
347 """
348 __slots__ = ("_name", "_doc")
349
350 def __init__(self, name, doc=None):
351 self._name = name
352 self._doc = doc
353
354 def key(self):
355 return self._name
356
357 def doc(self, obj):
358 return self._doc
359
360 def attrtype(self, obj):
361 return "attr"
362
363 def valuetype(self, obj):
364 return type(getattr(obj, self._name))
365
366 def value(self, obj):
367 return getattr(obj, self._name)
368
369 def __repr__(self):
370 if self._doc is None:
371 return "Attribute(%r)" % self._name
372 else:
373 return "Attribute(%r, %r)" % (self._name, self._doc)
374
375
376 class IndexDescriptor(Descriptor):
377 """
378 An ``IndexDescriptor`` describes an "attribute" of an object that is fetched
379 via ``__getitem__``.
380 """
381 __slots__ = ("_index",)
382
383 def __init__(self, index):
384 self._index = index
385
386 def key(self):
387 return self._index
388
389 def attrtype(self, obj):
390 return "item"
391
392 def valuetype(self, obj):
393 return type(obj[self._index])
394
395 def value(self, obj):
396 return obj[self._index]
397
398 def __repr__(self):
399 return "Index(%r)" % self._index
400
401
402 class MethodDescriptor(Descriptor):
403 """
404 A ``MethodDescriptor`` describes a method of an object that can be called
405 without argument. Note that this method shouldn't change the object.
406 """
407 __slots__ = ("_name", "_doc")
408
409 def __init__(self, name, doc=None):
410 self._name = name
411 self._doc = doc
412
413 def key(self):
414 return self._name
415
416 def doc(self, obj):
417 if self._doc is None:
418 return getattr(obj, self._name).__doc__
419 return self._doc
420
421 def attrtype(self, obj):
422 return "method"
423
424 def valuetype(self, obj):
425 return type(self.value(obj))
426
427 def value(self, obj):
428 return getattr(obj, self._name)()
429
430 def __repr__(self):
431 if self._doc is None:
432 return "Method(%r)" % self._name
433 else:
434 return "Method(%r, %r)" % (self._name, self._doc)
435
436
437 class IterAttributeDescriptor(Descriptor):
438 """
439 An ``IterAttributeDescriptor`` works like an ``AttributeDescriptor`` but
440 doesn't return an attribute values (because this value might be e.g. a large
441 list).
442 """
443 __slots__ = ("_name", "_doc")
444
445 def __init__(self, name, doc=None):
446 self._name = name
447 self._doc = doc
448
449 def key(self):
450 return self._name
451
452 def doc(self, obj):
453 return self._doc
454
455 def attrtype(self, obj):
456 return "iter"
457
458 def valuetype(self, obj):
459 return noitem
460
461 def value(self, obj):
462 return noitem
463
464 def iter(self, obj):
465 return xiter(getattr(obj, self._name))
466
467 def __repr__(self):
468 if self._doc is None:
469 return "IterAttribute(%r)" % self._name
470 else:
471 return "IterAttribute(%r, %r)" % (self._name, self._doc)
472
473
474 class IterMethodDescriptor(Descriptor):
475 """
476 An ``IterMethodDescriptor`` works like an ``MethodDescriptor`` but doesn't
477 return an attribute values (because this value might be e.g. a large list).
478 """
479 __slots__ = ("_name", "_doc")
480
481 def __init__(self, name, doc=None):
482 self._name = name
483 self._doc = doc
484
485 def key(self):
486 return self._name
487
488 def doc(self, obj):
489 if self._doc is None:
490 return getattr(obj, self._name).__doc__
491 return self._doc
492
493 def attrtype(self, obj):
494 return "itermethod"
495
496 def valuetype(self, obj):
497 return noitem
498
499 def value(self, obj):
500 return noitem
501
502 def iter(self, obj):
503 return xiter(getattr(obj, self._name)())
504
505 def __repr__(self):
506 if self._doc is None:
507 return "IterMethod(%r)" % self._name
508 else:
509 return "IterMethod(%r, %r)" % (self._name, self._doc)
510
511
512 class FunctionDescriptor(Descriptor):
513 """
514 A ``FunctionDescriptor`` turns a function into a descriptor. The function
515 will be called with the object to get the type and value of the attribute.
516 """
517 __slots__ = ("_function", "_name", "_doc")
518
519 def __init__(self, function, name=None, doc=None):
520 self._function = function
521 self._name = name
522 self._doc = doc
523
524 def key(self):
525 return self._function
526
527 def name(self):
528 if self._name is not None:
529 return self._name
530 return getattr(self._function, "__xname__", self._function.__name__)
531
532 def doc(self, obj):
533 if self._doc is None:
534 return self._function.__doc__
535 return self._doc
536
537 def attrtype(self, obj):
538 return "function"
539
540 def valuetype(self, obj):
541 return type(self._function(obj))
542
543 def value(self, obj):
544 return self._function(obj)
545
546 def __repr__(self):
547 if self._doc is None:
548 return "Function(%r)" % self._name
549 else:
550 return "Function(%r, %r)" % (self._name, self._doc)
551
552
553 class Table(object):
554 """
555 A ``Table`` is an object that produces items (just like a normal Python
556 iterator/generator does) and can be used as the first object in a pipeline
557 expression. The displayhook will open the default browser for such an object
558 (instead of simply printing the ``repr()`` result).
559 """
560
561 # We want to support ``foo`` and ``foo()`` in pipeline expression:
562 # So we implement the required operators (``|`` and ``+``) in the metaclass,
563 # instantiate the class and forward the operator to the instance
564 class __metaclass__(type):
565 def __iter__(self):
566 return iter(self())
567
568 def __or__(self, other):
569 return self() | other
570
571 def __add__(self, other):
572 return self() + other
573
574 def __radd__(self, other):
575 return other + self()
576
577 def __getitem__(self, index):
578 return self()[index]
579
580 def __getitem__(self, index):
581 return item(self, index)
582
583 def __contains__(self, item):
584 for haveitem in self:
585 if item == haveitem:
586 return True
587 return False
588
589 def __or__(self, other):
590 # autoinstantiate right hand side
591 if isinstance(other, type) and issubclass(other, (Table, Display)):
592 other = other()
593 # treat simple strings and functions as ``ieval`` instances
594 elif not isinstance(other, Display) and not isinstance(other, Table):
595 other = ieval(other)
596 # forward operations to the right hand side
597 return other.__ror__(self)
598
599 def __add__(self, other):
600 # autoinstantiate right hand side
601 if isinstance(other, type) and issubclass(other, Table):
602 other = other()
603 return ichain(self, other)
604
605 def __radd__(self, other):
606 # autoinstantiate left hand side
607 if isinstance(other, type) and issubclass(other, Table):
608 other = other()
609 return ichain(other, self)
610
611
612 class Pipe(Table):
613 """
614 A ``Pipe`` is an object that can be used in a pipeline expression. It
615 processes the objects it gets from its input ``Table``/``Pipe``. Note that
616 a ``Pipe`` object can't be used as the first object in a pipeline
617 expression, as it doesn't produces items itself.
618 """
619 class __metaclass__(Table.__metaclass__):
620 def __ror__(self, input):
621 return input | self()
622
623 def __ror__(self, input):
624 # autoinstantiate left hand side
625 if isinstance(input, type) and issubclass(input, Table):
626 input = input()
627 self.input = input
628 return self
629
630
631 def xrepr(item, mode="default"):
632 """
633 Generic function that adds color output and different display modes to ``repr``.
634
635 The result of an ``xrepr`` call is iterable and consists of ``(style, string)``
636 tuples. The ``style`` in this tuple must be a ``Style`` object from the
637 ``astring`` module. To reconfigure the output the first yielded tuple can be
638 a ``(aligment, full)`` tuple instead of a ``(style, string)`` tuple.
639 ``alignment`` can be -1 for left aligned, 0 for centered and 1 for right
640 aligned (the default is left alignment). ``full`` is a boolean that specifies
641 whether the complete output must be displayed or the ``Display`` object is
642 allowed to stop output after enough text has been produced (e.g. a syntax
643 highlighted text line would use ``True``, but for a large data structure
644 (i.e. a nested list, tuple or dictionary) ``False`` would be used).
645 The default is full output.
646
647 There are four different possible values for ``mode`` depending on where
648 the ``Display`` object will display ``item``:
649
650 ``"header"``
651 ``item`` will be displayed in a header line (this is used by ``ibrowse``).
652
653 ``"footer"``
654 ``item`` will be displayed in a footer line (this is used by ``ibrowse``).
655
656 ``"cell"``
657 ``item`` will be displayed in a table cell/list.
658
659 ``"default"``
660 default mode. If an ``xrepr`` implementation recursively outputs objects,
661 ``"default"`` must be passed in the recursive calls to ``xrepr``.
662
663 If no implementation is registered for ``item``, ``xrepr`` will try the
664 ``__xrepr__`` method on ``item``. If ``item`` doesn't have an ``__xrepr__``
665 method it falls back to ``repr``/``__repr__`` for all modes.
666 """
667 try:
668 func = item.__xrepr__
669 except AttributeError:
670 yield (astyle.style_default, repr(item))
671 else:
672 try:
673 for x in func(mode):
674 yield x
675 except (KeyboardInterrupt, SystemExit, GeneratorExit):
676 raise
677 except Exception:
678 yield (astyle.style_default, repr(item))
679 xrepr = simplegeneric.generic(xrepr)
680
681
682 def xrepr_none(self, mode="default"):
683 yield (astyle.style_type_none, repr(self))
684 xrepr.when_object(None)(xrepr_none)
685
686
687 def xrepr_noitem(self, mode="default"):
688 yield (2, True)
689 yield (astyle.style_nodata, "<?>")
690 xrepr.when_object(noitem)(xrepr_noitem)
691
692
693 def xrepr_bool(self, mode="default"):
694 yield (astyle.style_type_bool, repr(self))
695 xrepr.when_type(bool)(xrepr_bool)
696
697
698 def xrepr_str(self, mode="default"):
699 if mode == "cell":
700 yield (astyle.style_default, repr(self.expandtabs(tab))[1:-1])
701 else:
702 yield (astyle.style_default, repr(self))
703 xrepr.when_type(str)(xrepr_str)
704
705
706 def xrepr_unicode(self, mode="default"):
707 if mode == "cell":
708 yield (astyle.style_default, repr(self.expandtabs(tab))[2:-1])
709 else:
710 yield (astyle.style_default, repr(self))
711 xrepr.when_type(unicode)(xrepr_unicode)
712
713
714 def xrepr_number(self, mode="default"):
715 yield (1, True)
716 yield (astyle.style_type_number, repr(self))
717 xrepr.when_type(int)(xrepr_number)
718 xrepr.when_type(long)(xrepr_number)
719 xrepr.when_type(float)(xrepr_number)
720
721
722 def xrepr_complex(self, mode="default"):
723 yield (astyle.style_type_number, repr(self))
724 xrepr.when_type(complex)(xrepr_number)
725
726
727 def xrepr_datetime(self, mode="default"):
728 if mode == "cell":
729 # Don't use strftime() here, as this requires year >= 1900
730 yield (astyle.style_type_datetime,
731 "%04d-%02d-%02d %02d:%02d:%02d.%06d" % \
732 (self.year, self.month, self.day,
733 self.hour, self.minute, self.second,
734 self.microsecond),
735 )
736 else:
737 yield (astyle.style_type_datetime, repr(self))
738 xrepr.when_type(datetime.datetime)(xrepr_datetime)
739
740
741 def xrepr_date(self, mode="default"):
742 if mode == "cell":
743 yield (astyle.style_type_datetime,
744 "%04d-%02d-%02d" % (self.year, self.month, self.day))
745 else:
746 yield (astyle.style_type_datetime, repr(self))
747 xrepr.when_type(datetime.date)(xrepr_date)
748
749
750 def xrepr_time(self, mode="default"):
751 if mode == "cell":
752 yield (astyle.style_type_datetime,
753 "%02d:%02d:%02d.%06d" % \
754 (self.hour, self.minute, self.second, self.microsecond))
755 else:
756 yield (astyle.style_type_datetime, repr(self))
757 xrepr.when_type(datetime.time)(xrepr_time)
758
759
760 def xrepr_timedelta(self, mode="default"):
761 yield (astyle.style_type_datetime, repr(self))
762 xrepr.when_type(datetime.timedelta)(xrepr_timedelta)
763
764
765 def xrepr_type(self, mode="default"):
766 if self.__module__ == "__builtin__":
767 yield (astyle.style_type_type, self.__name__)
768 else:
769 yield (astyle.style_type_type, "%s.%s" % (self.__module__, self.__name__))
770 xrepr.when_type(type)(xrepr_type)
771
772
773 def xrepr_exception(self, mode="default"):
774 if self.__class__.__module__ == "exceptions":
775 classname = self.__class__.__name__
776 else:
777 classname = "%s.%s" % \
778 (self.__class__.__module__, self.__class__.__name__)
779 if mode == "header" or mode == "footer":
780 yield (astyle.style_error, "%s: %s" % (classname, self))
781 else:
782 yield (astyle.style_error, classname)
783 xrepr.when_type(Exception)(xrepr_exception)
784
785
786 def xrepr_listtuple(self, mode="default"):
787 if mode == "header" or mode == "footer":
788 if self.__class__.__module__ == "__builtin__":
789 classname = self.__class__.__name__
790 else:
791 classname = "%s.%s" % \
792 (self.__class__.__module__,self.__class__.__name__)
793 yield (astyle.style_default,
794 "<%s object with %d items at 0x%x>" % \
795 (classname, len(self), id(self)))
796 else:
797 yield (-1, False)
798 if isinstance(self, list):
799 yield (astyle.style_default, "[")
800 end = "]"
801 else:
802 yield (astyle.style_default, "(")
803 end = ")"
804 for (i, subself) in enumerate(self):
805 if i:
806 yield (astyle.style_default, ", ")
807 for part in xrepr(subself, "default"):
808 yield part
809 yield (astyle.style_default, end)
810 xrepr.when_type(list)(xrepr_listtuple)
811 xrepr.when_type(tuple)(xrepr_listtuple)
812
813
814 def xrepr_dict(self, mode="default"):
815 if mode == "header" or mode == "footer":
816 if self.__class__.__module__ == "__builtin__":
817 classname = self.__class__.__name__
818 else:
819 classname = "%s.%s" % \
820 (self.__class__.__module__,self.__class__.__name__)
821 yield (astyle.style_default,
822 "<%s object with %d items at 0x%x>" % \
823 (classname, len(self), id(self)))
824 else:
825 yield (-1, False)
826 if isinstance(self, dict):
827 yield (astyle.style_default, "{")
828 end = "}"
829 else:
830 yield (astyle.style_default, "dictproxy((")
831 end = "})"
832 for (i, (key, value)) in enumerate(self.iteritems()):
833 if i:
834 yield (astyle.style_default, ", ")
835 for part in xrepr(key, "default"):
836 yield part
837 yield (astyle.style_default, ": ")
838 for part in xrepr(value, "default"):
839 yield part
840 yield (astyle.style_default, end)
841 xrepr.when_type(dict)(xrepr_dict)
842 xrepr.when_type(types.DictProxyType)(xrepr_dict)
843
844
845 def upgradexattr(attr):
846 """
847 Convert an attribute descriptor string to a real descriptor object.
848
849 If attr already is a descriptor object return it unmodified. A
850 ``SelfDescriptor`` will be returned if ``attr`` is ``None``. ``"foo"``
851 returns an ``AttributeDescriptor`` for the attribute named ``"foo"``.
852 ``"foo()"`` returns a ``MethodDescriptor`` for the method named ``"foo"``.
853 ``"-foo"`` will return an ``IterAttributeDescriptor`` for the attribute
854 named ``"foo"`` and ``"-foo()"`` will return an ``IterMethodDescriptor``
855 for the method named ``"foo"``. Furthermore integers will return the appropriate
856 ``IndexDescriptor`` and callables will return a ``FunctionDescriptor``.
857 """
858 if attr is None:
859 return selfdescriptor
860 elif isinstance(attr, Descriptor):
861 return attr
862 elif isinstance(attr, basestring):
863 if attr.endswith("()"):
864 if attr.startswith("-"):
865 return IterMethodDescriptor(attr[1:-2])
866 else:
867 return MethodDescriptor(attr[:-2])
868 else:
869 if attr.startswith("-"):
870 return IterAttributeDescriptor(attr[1:])
871 else:
872 return AttributeDescriptor(attr)
873 elif isinstance(attr, (int, long)):
874 return IndexDescriptor(attr)
875 elif callable(attr):
876 return FunctionDescriptor(attr)
877 else:
878 raise TypeError("can't handle descriptor %r" % attr)
879
880
881 def xattrs(item, mode="default"):
882 """
883 Generic function that returns an iterable of attribute descriptors
884 to be used for displaying the attributes ob the object ``item`` in display
885 mode ``mode``.
886
887 There are two possible modes:
888
889 ``"detail"``
890 The ``Display`` object wants to display a detailed list of the object
891 attributes.
892
893 ``"default"``
894 The ``Display`` object wants to display the object in a list view.
895
896 If no implementation is registered for the object ``item`` ``xattrs`` falls
897 back to trying the ``__xattrs__`` method of the object. If this doesn't
898 exist either, ``dir(item)`` is used for ``"detail"`` mode and ``(None,)``
899 for ``"default"`` mode.
900
901 The implementation must yield attribute descriptors (see the class
902 ``Descriptor`` for more info). The ``__xattrs__`` method may also return
903 attribute descriptor strings (and ``None``) which will be converted to real
904 descriptors by ``upgradexattr()``.
905 """
906 try:
907 func = item.__xattrs__
908 except AttributeError:
909 if mode == "detail":
910 for attrname in dir(item):
911 yield AttributeDescriptor(attrname)
912 else:
913 yield selfdescriptor
914 else:
915 for attr in func(mode):
916 yield upgradexattr(attr)
917 xattrs = simplegeneric.generic(xattrs)
918
919
920 def xattrs_complex(self, mode="default"):
921 if mode == "detail":
922 return (AttributeDescriptor("real"), AttributeDescriptor("imag"))
923 return (selfdescriptor,)
924 xattrs.when_type(complex)(xattrs_complex)
925
926
927 def _isdict(item):
928 try:
929 itermeth = item.__class__.__iter__
930 except (AttributeError, TypeError):
931 return False
932 return itermeth is dict.__iter__ or itermeth is types.DictProxyType.__iter__
933
934
935 def _isstr(item):
936 if not isinstance(item, basestring):
937 return False
938 try:
939 itermeth = item.__class__.__iter__
940 except AttributeError:
941 return True
942 return False # ``__iter__`` has been redefined
943
944
945 def xiter(item):
946 """
947 Generic function that implements iteration for pipeline expression. If no
948 implementation is registered for ``item`` ``xiter`` falls back to ``iter``.
949 """
950 try:
951 func = item.__xiter__
952 except AttributeError:
953 if _isdict(item):
954 def items(item):
955 fields = ("key", "value")
956 for (key, value) in item.iteritems():
957 yield Fields(fields, key=key, value=value)
958 return items(item)
959 elif isinstance(item, new.module):
960 def items(item):
961 fields = ("key", "value")
962 for key in sorted(item.__dict__):
963 yield Fields(fields, key=key, value=getattr(item, key))
964 return items(item)
965 elif _isstr(item):
966 if not item:
967 raise ValueError("can't enter empty string")
968 lines = item.splitlines()
969 if len(lines) == 1:
970 def iterone(item):
971 yield item
972 return iterone(item)
973 else:
974 return iter(lines)
975 return iter(item)
976 else:
977 return iter(func()) # iter() just to be safe
978 xiter = simplegeneric.generic(xiter)
979
980
981 class ichain(Pipe):
982 """
983 Chains multiple ``Table``s into one.
984 """
985
986 def __init__(self, *iters):
987 self.iters = iters
988
989 def __iter__(self):
990 return itertools.chain(*self.iters)
991
992 def __xrepr__(self, mode="default"):
993 if mode == "header" or mode == "footer":
994 for (i, item) in enumerate(self.iters):
995 if i:
996 yield (astyle.style_default, "+")
997 if isinstance(item, Pipe):
998 yield (astyle.style_default, "(")
999 for part in xrepr(item, mode):
1000 yield part
1001 if isinstance(item, Pipe):
1002 yield (astyle.style_default, ")")
1003 else:
1004 yield (astyle.style_default, repr(self))
1005
1006 def __repr__(self):
1007 args = ", ".join([repr(it) for it in self.iters])
1008 return "%s.%s(%s)" % \
1009 (self.__class__.__module__, self.__class__.__name__, args)
1010
1011
1012 class ifile(path.path):
1013 """
1014 file (or directory) object.
1015 """
1016
1017 def getmode(self):
1018 return self.stat().st_mode
1019 mode = property(getmode, None, None, "Access mode")
1020
1021 def gettype(self):
1022 data = [
1023 (stat.S_ISREG, "file"),
1024 (stat.S_ISDIR, "dir"),
1025 (stat.S_ISCHR, "chardev"),
1026 (stat.S_ISBLK, "blockdev"),
1027 (stat.S_ISFIFO, "fifo"),
1028 (stat.S_ISLNK, "symlink"),
1029 (stat.S_ISSOCK,"socket"),
1030 ]
1031 lstat = self.lstat()
1032 if lstat is not None:
1033 types = set([text for (func, text) in data if func(lstat.st_mode)])
1034 else:
1035 types = set()
1036 m = self.mode
1037 types.update([text for (func, text) in data if func(m)])
1038 return ", ".join(types)
1039 type = property(gettype, None, None, "file type (file, directory, link, etc.)")
1040
1041 def getmodestr(self):
1042 m = self.mode
1043 data = [
1044 (stat.S_IRUSR, "-r"),
1045 (stat.S_IWUSR, "-w"),
1046 (stat.S_IXUSR, "-x"),
1047 (stat.S_IRGRP, "-r"),
1048 (stat.S_IWGRP, "-w"),
1049 (stat.S_IXGRP, "-x"),
1050 (stat.S_IROTH, "-r"),
1051 (stat.S_IWOTH, "-w"),
1052 (stat.S_IXOTH, "-x"),
1053 ]
1054 return "".join([text[bool(m&bit)] for (bit, text) in data])
1055
1056 modestr = property(getmodestr, None, None, "Access mode as string")
1057
1058 def getblocks(self):
1059 return self.stat().st_blocks
1060 blocks = property(getblocks, None, None, "File size in blocks")
1061
1062 def getblksize(self):
1063 return self.stat().st_blksize
1064 blksize = property(getblksize, None, None, "Filesystem block size")
1065
1066 def getdev(self):
1067 return self.stat().st_dev
1068 dev = property(getdev)
1069
1070 def getnlink(self):
1071 return self.stat().st_nlink
1072 nlink = property(getnlink, None, None, "Number of links")
1073
1074 def getuid(self):
1075 return self.stat().st_uid
1076 uid = property(getuid, None, None, "User id of file owner")
1077
1078 def getgid(self):
1079 return self.stat().st_gid
1080 gid = property(getgid, None, None, "Group id of file owner")
1081
1082 def getowner(self):
1083 stat = self.stat()
1084 try:
1085 return pwd.getpwuid(stat.st_uid).pw_name
1086 except KeyError:
1087 return stat.st_uid
1088 owner = property(getowner, None, None, "Owner name (or id)")
1089
1090 def getgroup(self):
1091 stat = self.stat()
1092 try:
1093 return grp.getgrgid(stat.st_gid).gr_name
1094 except KeyError:
1095 return stat.st_gid
1096 group = property(getgroup, None, None, "Group name (or id)")
1097
1098 def getadate(self):
1099 return datetime.datetime.utcfromtimestamp(self.atime)
1100 adate = property(getadate, None, None, "Access date")
1101
1102 def getcdate(self):
1103 return datetime.datetime.utcfromtimestamp(self.ctime)
1104 cdate = property(getcdate, None, None, "Creation date")
1105
1106 def getmdate(self):
1107 return datetime.datetime.utcfromtimestamp(self.mtime)
1108 mdate = property(getmdate, None, None, "Modification date")
1109
1110 def mimetype(self):
1111 """
1112 Return MIME type guessed from the extension.
1113 """
1114 return mimetypes.guess_type(self.basename())[0]
1115
1116 def encoding(self):
1117 """
1118 Return guessed compression (like "compress" or "gzip").
1119 """
1120 return mimetypes.guess_type(self.basename())[1]
1121
1122 def __repr__(self):
1123 return "ifile(%s)" % path._base.__repr__(self)
1124
1125 if sys.platform == "win32":
1126 defaultattrs = (None, "type", "size", "modestr", "mdate")
1127 else:
1128 defaultattrs = (None, "type", "size", "modestr", "owner", "group", "mdate")
1129
1130 def __xattrs__(self, mode="default"):
1131 if mode == "detail":
1132 return (
1133 "name",
1134 "basename()",
1135 "abspath()",
1136 "realpath()",
1137 "type",
1138 "mode",
1139 "modestr",
1140 "stat()",
1141 "lstat()",
1142 "uid",
1143 "gid",
1144 "owner",
1145 "group",
1146 "dev",
1147 "nlink",
1148 "ctime",
1149 "mtime",
1150 "atime",
1151 "cdate",
1152 "mdate",
1153 "adate",
1154 "size",
1155 "blocks",
1156 "blksize",
1157 "isdir()",
1158 "islink()",
1159 "mimetype()",
1160 "encoding()",
1161 "-listdir()",
1162 "-dirs()",
1163 "-files()",
1164 "-walk()",
1165 "-walkdirs()",
1166 "-walkfiles()",
1167 )
1168 else:
1169 return self.defaultattrs
1170
1171
1172 def xiter_ifile(self):
1173 if self.isdir():
1174 yield (self / os.pardir).abspath()
1175 for child in sorted(self.listdir()):
1176 yield child
1177 else:
1178 f = self.open("rb")
1179 for line in f:
1180 yield line
1181 f.close()
1182 xiter.when_type(ifile)(xiter_ifile)
1183
1184
1185 # We need to implement ``xrepr`` for ``ifile`` as a generic function, because
1186 # otherwise ``xrepr_str`` would kick in.
1187 def xrepr_ifile(self, mode="default"):
1188 try:
1189 if self.isdir():
1190 name = "idir"
1191 style = astyle.style_dir
1192 else:
1193 name = "ifile"
1194 style = astyle.style_file
1195 except IOError:
1196 name = "ifile"
1197 style = astyle.style_default
1198 if mode in ("cell", "header", "footer"):
1199 abspath = repr(path._base(self.normpath()))
1200 if abspath.startswith("u"):
1201 abspath = abspath[2:-1]
1202 else:
1203 abspath = abspath[1:-1]
1204 if mode == "cell":
1205 yield (style, abspath)
1206 else:
1207 yield (style, "%s(%s)" % (name, abspath))
1208 else:
1209 yield (style, repr(self))
1210 xrepr.when_type(ifile)(xrepr_ifile)
1211
1212
1213 class ils(Table):
1214 """
1215 List the current (or a specified) directory.
1216
1217 Examples::
1218
1219 >>> ils
1220 <class 'IPython.extensions.ipipe.ils'>
1221 >>> ils("/usr/local/lib/python2.4")
1222 IPython.extensions.ipipe.ils('/usr/local/lib/python2.4')
1223 >>> ils("~")
1224 IPython.extensions.ipipe.ils('/home/fperez')
1225 # all-random
1226 """
1227 def __init__(self, base=os.curdir, dirs=True, files=True):
1228 self.base = os.path.expanduser(base)
1229 self.dirs = dirs
1230 self.files = files
1231
1232 def __iter__(self):
1233 base = ifile(self.base)
1234 yield (base / os.pardir).abspath()
1235 for child in sorted(base.listdir()):
1236 if self.dirs:
1237 if self.files:
1238 yield child
1239 else:
1240 if child.isdir():
1241 yield child
1242 elif self.files:
1243 if not child.isdir():
1244 yield child
1245
1246 def __xrepr__(self, mode="default"):
1247 return xrepr(ifile(self.base), mode)
1248
1249 def __repr__(self):
1250 return "%s.%s(%r)" % \
1251 (self.__class__.__module__, self.__class__.__name__, self.base)
1252
1253
1254 class iglob(Table):
1255 """
1256 List all files and directories matching a specified pattern.
1257 (See ``glob.glob()`` for more info.).
1258
1259 Examples::
1260
1261 >>> iglob("*.py")
1262 IPython.extensions.ipipe.iglob('*.py')
1263 """
1264 def __init__(self, glob):
1265 self.glob = glob
1266
1267 def __iter__(self):
1268 for name in glob.glob(self.glob):
1269 yield ifile(name)
1270
1271 def __xrepr__(self, mode="default"):
1272 if mode == "header" or mode == "footer" or mode == "cell":
1273 yield (astyle.style_default,
1274 "%s(%r)" % (self.__class__.__name__, self.glob))
1275 else:
1276 yield (astyle.style_default, repr(self))
1277
1278 def __repr__(self):
1279 return "%s.%s(%r)" % \
1280 (self.__class__.__module__, self.__class__.__name__, self.glob)
1281
1282
1283 class iwalk(Table):
1284 """
1285 List all files and directories in a directory and it's subdirectory::
1286
1287 >>> iwalk
1288 <class 'IPython.extensions.ipipe.iwalk'>
1289 >>> iwalk("/usr/lib")
1290 IPython.extensions.ipipe.iwalk('/usr/lib')
1291 >>> iwalk("~")
1292 IPython.extensions.ipipe.iwalk('/home/fperez') # random
1293
1294 """
1295 def __init__(self, base=os.curdir, dirs=True, files=True):
1296 self.base = os.path.expanduser(base)
1297 self.dirs = dirs
1298 self.files = files
1299
1300 def __iter__(self):
1301 for (dirpath, dirnames, filenames) in os.walk(self.base):
1302 if self.dirs:
1303 for name in sorted(dirnames):
1304 yield ifile(os.path.join(dirpath, name))
1305 if self.files:
1306 for name in sorted(filenames):
1307 yield ifile(os.path.join(dirpath, name))
1308
1309 def __xrepr__(self, mode="default"):
1310 if mode == "header" or mode == "footer" or mode == "cell":
1311 yield (astyle.style_default,
1312 "%s(%r)" % (self.__class__.__name__, self.base))
1313 else:
1314 yield (astyle.style_default, repr(self))
1315
1316 def __repr__(self):
1317 return "%s.%s(%r)" % \
1318 (self.__class__.__module__, self.__class__.__name__, self.base)
1319
1320
1321 class ipwdentry(object):
1322 """
1323 ``ipwdentry`` objects encapsulate entries in the Unix user account and
1324 password database.
1325 """
1326 def __init__(self, id):
1327 self._id = id
1328 self._entry = None
1329
1330 def __eq__(self, other):
1331 return self.__class__ is other.__class__ and self._id == other._id
1332
1333 def __ne__(self, other):
1334 return self.__class__ is not other.__class__ or self._id != other._id
1335
1336 def _getentry(self):
1337 if self._entry is None:
1338 if isinstance(self._id, basestring):
1339 self._entry = pwd.getpwnam(self._id)
1340 else:
1341 self._entry = pwd.getpwuid(self._id)
1342 return self._entry
1343
1344 def getname(self):
1345 if isinstance(self._id, basestring):
1346 return self._id
1347 else:
1348 return self._getentry().pw_name
1349 name = property(getname, None, None, "User name")
1350
1351 def getpasswd(self):
1352 return self._getentry().pw_passwd
1353 passwd = property(getpasswd, None, None, "Password")
1354
1355 def getuid(self):
1356 if isinstance(self._id, basestring):
1357 return self._getentry().pw_uid
1358 else:
1359 return self._id
1360 uid = property(getuid, None, None, "User id")
1361
1362 def getgid(self):
1363 return self._getentry().pw_gid
1364 gid = property(getgid, None, None, "Primary group id")
1365
1366 def getgroup(self):
1367 return igrpentry(self.gid)
1368 group = property(getgroup, None, None, "Group")
1369
1370 def getgecos(self):
1371 return self._getentry().pw_gecos
1372 gecos = property(getgecos, None, None, "Information (e.g. full user name)")
1373
1374 def getdir(self):
1375 return self._getentry().pw_dir
1376 dir = property(getdir, None, None, "$HOME directory")
1377
1378 def getshell(self):
1379 return self._getentry().pw_shell
1380 shell = property(getshell, None, None, "Login shell")
1381
1382 def __xattrs__(self, mode="default"):
1383 return ("name", "passwd", "uid", "gid", "gecos", "dir", "shell")
1384
1385 def __repr__(self):
1386 return "%s.%s(%r)" % \
1387 (self.__class__.__module__, self.__class__.__name__, self._id)
1388
1389
1390 class ipwd(Table):
1391 """
1392 List all entries in the Unix user account and password database.
1393
1394 Example::
1395
1396 >>> ipwd | isort("uid")
1397 <IPython.extensions.ipipe.isort key='uid' reverse=False at 0x849efec>
1398 # random
1399 """
1400 def __iter__(self):
1401 for entry in pwd.getpwall():
1402 yield ipwdentry(entry.pw_name)
1403
1404 def __xrepr__(self, mode="default"):
1405 if mode == "header" or mode == "footer" or mode == "cell":
1406 yield (astyle.style_default, "%s()" % self.__class__.__name__)
1407 else:
1408 yield (astyle.style_default, repr(self))
1409
1410
1411 class igrpentry(object):
1412 """
1413 ``igrpentry`` objects encapsulate entries in the Unix group database.
1414 """
1415 def __init__(self, id):
1416 self._id = id
1417 self._entry = None
1418
1419 def __eq__(self, other):
1420 return self.__class__ is other.__class__ and self._id == other._id
1421
1422 def __ne__(self, other):
1423 return self.__class__ is not other.__class__ or self._id != other._id
1424
1425 def _getentry(self):
1426 if self._entry is None:
1427 if isinstance(self._id, basestring):
1428 self._entry = grp.getgrnam(self._id)
1429 else:
1430 self._entry = grp.getgrgid(self._id)
1431 return self._entry
1432
1433 def getname(self):
1434 if isinstance(self._id, basestring):
1435 return self._id
1436 else:
1437 return self._getentry().gr_name
1438 name = property(getname, None, None, "Group name")
1439
1440 def getpasswd(self):
1441 return self._getentry().gr_passwd
1442 passwd = property(getpasswd, None, None, "Password")
1443
1444 def getgid(self):
1445 if isinstance(self._id, basestring):
1446 return self._getentry().gr_gid
1447 else:
1448 return self._id
1449 gid = property(getgid, None, None, "Group id")
1450
1451 def getmem(self):
1452 return self._getentry().gr_mem
1453 mem = property(getmem, None, None, "Members")
1454
1455 def __xattrs__(self, mode="default"):
1456 return ("name", "passwd", "gid", "mem")
1457
1458 def __xrepr__(self, mode="default"):
1459 if mode == "header" or mode == "footer" or mode == "cell":
1460 yield (astyle.style_default, "group ")
1461 try:
1462 yield (astyle.style_default, self.name)
1463 except KeyError:
1464 if isinstance(self._id, basestring):
1465 yield (astyle.style_default, self.name_id)
1466 else:
1467 yield (astyle.style_type_number, str(self._id))
1468 else:
1469 yield (astyle.style_default, repr(self))
1470
1471 def __iter__(self):
1472 for member in self.mem:
1473 yield ipwdentry(member)
1474
1475 def __repr__(self):
1476 return "%s.%s(%r)" % \
1477 (self.__class__.__module__, self.__class__.__name__, self._id)
1478
1479
1480 class igrp(Table):
1481 """
1482 This ``Table`` lists all entries in the Unix group database.
1483 """
1484 def __iter__(self):
1485 for entry in grp.getgrall():
1486 yield igrpentry(entry.gr_name)
1487
1488 def __xrepr__(self, mode="default"):
1489 if mode == "header" or mode == "footer":
1490 yield (astyle.style_default, "%s()" % self.__class__.__name__)
1491 else:
1492 yield (astyle.style_default, repr(self))
1493
1494
1495 class Fields(object):
1496 def __init__(self, fieldnames, **fields):
1497 self.__fieldnames = [upgradexattr(fieldname) for fieldname in fieldnames]
1498 for (key, value) in fields.iteritems():
1499 setattr(self, key, value)
1500
1501 def __xattrs__(self, mode="default"):
1502 return self.__fieldnames
1503
1504 def __xrepr__(self, mode="default"):
1505 yield (-1, False)
1506 if mode == "header" or mode == "cell":
1507 yield (astyle.style_default, self.__class__.__name__)
1508 yield (astyle.style_default, "(")
1509 for (i, f) in enumerate(self.__fieldnames):
1510 if i:
1511 yield (astyle.style_default, ", ")
1512 yield (astyle.style_default, f.name())
1513 yield (astyle.style_default, "=")
1514 for part in xrepr(getattr(self, f), "default"):
1515 yield part
1516 yield (astyle.style_default, ")")
1517 elif mode == "footer":
1518 yield (astyle.style_default, self.__class__.__name__)
1519 yield (astyle.style_default, "(")
1520 for (i, f) in enumerate(self.__fieldnames):
1521 if i:
1522 yield (astyle.style_default, ", ")
1523 yield (astyle.style_default, f.name())
1524 yield (astyle.style_default, ")")
1525 else:
1526 yield (astyle.style_default, repr(self))
1527
1528
1529 class FieldTable(Table, list):
1530 def __init__(self, *fields):
1531 Table.__init__(self)
1532 list.__init__(self)
1533 self.fields = fields
1534
1535 def add(self, **fields):
1536 self.append(Fields(self.fields, **fields))
1537
1538 def __xrepr__(self, mode="default"):
1539 yield (-1, False)
1540 if mode == "header" or mode == "footer":
1541 yield (astyle.style_default, self.__class__.__name__)
1542 yield (astyle.style_default, "(")
1543 for (i, f) in enumerate(self.__fieldnames):
1544 if i:
1545 yield (astyle.style_default, ", ")
1546 yield (astyle.style_default, f)
1547 yield (astyle.style_default, ")")
1548 else:
1549 yield (astyle.style_default, repr(self))
1550
1551 def __repr__(self):
1552 return "<%s.%s object with fields=%r at 0x%x>" % \
1553 (self.__class__.__module__, self.__class__.__name__,
1554 ", ".join(map(repr, self.fields)), id(self))
1555
1556
1557 class List(list):
1558 def __xattrs__(self, mode="default"):
1559 return xrange(len(self))
1560
1561 def __xrepr__(self, mode="default"):
1562 yield (-1, False)
1563 if mode == "header" or mode == "cell" or mode == "footer" or mode == "default":
1564 yield (astyle.style_default, self.__class__.__name__)
1565 yield (astyle.style_default, "(")
1566 for (i, item) in enumerate(self):
1567 if i:
1568 yield (astyle.style_default, ", ")
1569 for part in xrepr(item, "default"):
1570 yield part
1571 yield (astyle.style_default, ")")
1572 else:
1573 yield (astyle.style_default, repr(self))
1574
1575
1576 class ienv(Table):
1577 """
1578 List environment variables.
1579
1580 Example::
1581
1582 >>> ienv
1583 <class 'IPython.extensions.ipipe.ienv'>
1584 """
1585
1586 def __iter__(self):
1587 fields = ("key", "value")
1588 for (key, value) in os.environ.iteritems():
1589 yield Fields(fields, key=key, value=value)
1590
1591 def __xrepr__(self, mode="default"):
1592 if mode == "header" or mode == "cell":
1593 yield (astyle.style_default, "%s()" % self.__class__.__name__)
1594 else:
1595 yield (astyle.style_default, repr(self))
1596
1597
1598 class ihist(Table):
1599 """
1600 IPython input history
1601
1602 Example::
1603
1604 >>> ihist
1605 <class 'IPython.extensions.ipipe.ihist'>
1606 >>> ihist(True) # raw mode
1607 <IPython.extensions.ipipe.ihist object at 0x849602c> # random
1608 """
1609 def __init__(self, raw=True):
1610 self.raw = raw
1611
1612 def __iter__(self):
1613 api = ipapi.get()
1614 if self.raw:
1615 for line in api.input_hist_raw:
1616 yield line.rstrip("\n")
1617 else:
1618 for line in api.input_hist:
1619 yield line.rstrip("\n")
1620
1621
1622 class Alias(object):
1623 """
1624 Entry in the alias table
1625 """
1626 def __init__(self, name, args, command):
1627 self.name = name
1628 self.args = args
1629 self.command = command
1630
1631 def __xattrs__(self, mode="default"):
1632 return ("name", "args", "command")
1633
1634
1635 class ialias(Table):
1636 """
1637 IPython alias list
1638
1639 Example::
1640
1641 >>> ialias
1642 <class 'IPython.extensions.ipipe.ialias'>
1643 """
1644 def __iter__(self):
1645 api = ipapi.get()
1646
1647 for (name, (args, command)) in api.alias_manager.alias_table.iteritems():
1648 yield Alias(name, args, command)
1649
1650
1651 class icsv(Pipe):
1652 """
1653 This ``Pipe`` turns the input (with must be a pipe outputting lines
1654 or an ``ifile``) into lines of CVS columns.
1655 """
1656 def __init__(self, **csvargs):
1657 """
1658 Create an ``icsv`` object. ``cvsargs`` will be passed through as
1659 keyword arguments to ``cvs.reader()``.
1660 """
1661 self.csvargs = csvargs
1662
1663 def __iter__(self):
1664 input = self.input
1665 if isinstance(input, ifile):
1666 input = input.open("rb")
1667 reader = csv.reader(input, **self.csvargs)
1668 for line in reader:
1669 yield List(line)
1670
1671 def __xrepr__(self, mode="default"):
1672 yield (-1, False)
1673 if mode == "header" or mode == "footer":
1674 input = getattr(self, "input", None)
1675 if input is not None:
1676 for part in xrepr(input, mode):
1677 yield part
1678 yield (astyle.style_default, " | ")
1679 yield (astyle.style_default, "%s(" % self.__class__.__name__)
1680 for (i, (name, value)) in enumerate(self.csvargs.iteritems()):
1681 if i:
1682 yield (astyle.style_default, ", ")
1683 yield (astyle.style_default, name)
1684 yield (astyle.style_default, "=")
1685 for part in xrepr(value, "default"):
1686 yield part
1687 yield (astyle.style_default, ")")
1688 else:
1689 yield (astyle.style_default, repr(self))
1690
1691 def __repr__(self):
1692 args = ", ".join(["%s=%r" % item for item in self.csvargs.iteritems()])
1693 return "<%s.%s %s at 0x%x>" % \
1694 (self.__class__.__module__, self.__class__.__name__, args, id(self))
1695
1696
1697 class ix(Table):
1698 """
1699 Execute a system command and list its output as lines
1700 (similar to ``os.popen()``).
1701
1702 Examples::
1703
1704 >>> ix("ps x")
1705 IPython.extensions.ipipe.ix('ps x')
1706
1707 >>> ix("find .") | ifile
1708 <IPython.extensions.ipipe.ieval expr=<class 'IPython.extensions.ipipe.ifile'> at 0x8509d2c>
1709 # random
1710 """
1711 def __init__(self, cmd):
1712 self.cmd = cmd
1713 self._pipeout = None
1714
1715 def __iter__(self):
1716 (_pipein, self._pipeout) = os.popen4(self.cmd)
1717 _pipein.close()
1718 for l in self._pipeout:
1719 yield l.rstrip("\r\n")
1720 self._pipeout.close()
1721 self._pipeout = None
1722
1723 def __del__(self):
1724 if self._pipeout is not None and not self._pipeout.closed:
1725 self._pipeout.close()
1726 self._pipeout = None
1727
1728 def __xrepr__(self, mode="default"):
1729 if mode == "header" or mode == "footer":
1730 yield (astyle.style_default,
1731 "%s(%r)" % (self.__class__.__name__, self.cmd))
1732 else:
1733 yield (astyle.style_default, repr(self))
1734
1735 def __repr__(self):
1736 return "%s.%s(%r)" % \
1737 (self.__class__.__module__, self.__class__.__name__, self.cmd)
1738
1739
1740 class ifilter(Pipe):
1741 """
1742 Filter an input pipe. Only objects where an expression evaluates to true
1743 (and doesn't raise an exception) are listed.
1744
1745 Examples::
1746
1747 >>> ils | ifilter("_.isfile() and size>1000")
1748 >>> igrp | ifilter("len(mem)")
1749 >>> sys.modules | ifilter(lambda _:_.value is not None)
1750 # all-random
1751 """
1752
1753 def __init__(self, expr, globals=None, errors="raiseifallfail"):
1754 """
1755 Create an ``ifilter`` object. ``expr`` can be a callable or a string
1756 containing an expression. ``globals`` will be used as the global
1757 namespace for calling string expressions (defaulting to IPython's
1758 user namespace). ``errors`` specifies how exception during evaluation
1759 of ``expr`` are handled:
1760
1761 ``"drop"``
1762 drop all items that have errors;
1763
1764 ``"keep"``
1765 keep all items that have errors;
1766
1767 ``"keeperror"``
1768 keep the exception of all items that have errors;
1769
1770 ``"raise"``
1771 raise the exception;
1772
1773 ``"raiseifallfail"``
1774 raise the first exception if all items have errors; otherwise drop
1775 those with errors (this is the default).
1776 """
1777 self.expr = expr
1778 self.globals = globals
1779 self.errors = errors
1780
1781 def __iter__(self):
1782 if callable(self.expr):
1783 test = self.expr
1784 else:
1785 g = getglobals(self.globals)
1786 expr = compile(self.expr, "ipipe-expression", "eval")
1787 def test(item):
1788 return eval(expr, g, AttrNamespace(item))
1789
1790 ok = 0
1791 exc_info = None
1792 for item in xiter(self.input):
1793 try:
1794 if test(item):
1795 yield item
1796 ok += 1
1797 except (KeyboardInterrupt, SystemExit):
1798 raise
1799 except Exception as exc:
1800 if self.errors == "drop":
1801 pass # Ignore errors
1802 elif self.errors == "keep":
1803 yield item
1804 elif self.errors == "keeperror":
1805 yield exc
1806 elif self.errors == "raise":
1807 raise
1808 elif self.errors == "raiseifallfail":
1809 if exc_info is None:
1810 exc_info = sys.exc_info()
1811 if not ok and exc_info is not None:
1812 raise exc_info[0], exc_info[1], exc_info[2]
1813
1814 def __xrepr__(self, mode="default"):
1815 if mode == "header" or mode == "footer":
1816 input = getattr(self, "input", None)
1817 if input is not None:
1818 for part in xrepr(input, mode):
1819 yield part
1820 yield (astyle.style_default, " | ")
1821 yield (astyle.style_default, "%s(" % self.__class__.__name__)
1822 for part in xrepr(self.expr, "default"):
1823 yield part
1824 yield (astyle.style_default, ")")
1825 else:
1826 yield (astyle.style_default, repr(self))
1827
1828 def __repr__(self):
1829 return "<%s.%s expr=%r at 0x%x>" % \
1830 (self.__class__.__module__, self.__class__.__name__,
1831 self.expr, id(self))
1832
1833
1834 class ieval(Pipe):
1835 """
1836 Evaluate an expression for each object in the input pipe.
1837
1838 Examples::
1839
1840 >>> ils | ieval("_.abspath()")
1841 # random
1842 >>> sys.path | ieval(ifile)
1843 # random
1844 """
1845
1846 def __init__(self, expr, globals=None, errors="raiseifallfail"):
1847 """
1848 Create an ``ieval`` object. ``expr`` can be a callable or a string
1849 containing an expression. For the meaning of ``globals`` and
1850 ``errors`` see ``ifilter``.
1851 """
1852 self.expr = expr
1853 self.globals = globals
1854 self.errors = errors
1855
1856 def __iter__(self):
1857 if callable(self.expr):
1858 do = self.expr
1859 else:
1860 g = getglobals(self.globals)
1861 expr = compile(self.expr, "ipipe-expression", "eval")
1862 def do(item):
1863 return eval(expr, g, AttrNamespace(item))
1864
1865 ok = 0
1866 exc_info = None
1867 for item in xiter(self.input):
1868 try:
1869 yield do(item)
1870 except (KeyboardInterrupt, SystemExit):
1871 raise
1872 except Exception as exc:
1873 if self.errors == "drop":
1874 pass # Ignore errors
1875 elif self.errors == "keep":
1876 yield item
1877 elif self.errors == "keeperror":
1878 yield exc
1879 elif self.errors == "raise":
1880 raise
1881 elif self.errors == "raiseifallfail":
1882 if exc_info is None:
1883 exc_info = sys.exc_info()
1884 if not ok and exc_info is not None:
1885 raise exc_info[0], exc_info[1], exc_info[2]
1886
1887 def __xrepr__(self, mode="default"):
1888 if mode == "header" or mode == "footer":
1889 input = getattr(self, "input", None)
1890 if input is not None:
1891 for part in xrepr(input, mode):
1892 yield part
1893 yield (astyle.style_default, " | ")
1894 yield (astyle.style_default, "%s(" % self.__class__.__name__)
1895 for part in xrepr(self.expr, "default"):
1896 yield part
1897 yield (astyle.style_default, ")")
1898 else:
1899 yield (astyle.style_default, repr(self))
1900
1901 def __repr__(self):
1902 return "<%s.%s expr=%r at 0x%x>" % \
1903 (self.__class__.__module__, self.__class__.__name__,
1904 self.expr, id(self))
1905
1906
1907 class ienum(Pipe):
1908 """
1909 Enumerate the input pipe (i.e. wrap each input object in an object
1910 with ``index`` and ``object`` attributes).
1911
1912 Examples::
1913
1914 >>> xrange(20) | ieval("_,_*_") | ienum | ifilter("index % 2 == 0") | ieval("object")
1915 """
1916 skip_doctest = True
1917
1918 def __iter__(self):
1919 fields = ("index", "object")
1920 for (index, object) in enumerate(xiter(self.input)):
1921 yield Fields(fields, index=index, object=object)
1922
1923
1924 class isort(Pipe):
1925 """
1926 Sorts the input pipe.
1927
1928 Examples::
1929
1930 >>> ils | isort("size")
1931 <IPython.extensions.ipipe.isort key='size' reverse=False at 0x849ec2c>
1932 >>> ils | isort("_.isdir(), _.lower()", reverse=True)
1933 <IPython.extensions.ipipe.isort key='_.isdir(), _.lower()' reverse=True at 0x849eacc>
1934 # all-random
1935 """
1936
1937 def __init__(self, key=None, globals=None, reverse=False):
1938 """
1939 Create an ``isort`` object. ``key`` can be a callable or a string
1940 containing an expression (or ``None`` in which case the items
1941 themselves will be sorted). If ``reverse`` is true the sort order
1942 will be reversed. For the meaning of ``globals`` see ``ifilter``.
1943 """
1944 self.key = key
1945 self.globals = globals
1946 self.reverse = reverse
1947
1948 def __iter__(self):
1949 if self.key is None:
1950 items = sorted(xiter(self.input), reverse=self.reverse)
1951 elif callable(self.key):
1952 items = sorted(xiter(self.input), key=self.key, reverse=self.reverse)
1953 else:
1954 g = getglobals(self.globals)
1955 key = compile(self.key, "ipipe-expression", "eval")
1956 def realkey(item):
1957 return eval(key, g, AttrNamespace(item))
1958 items = sorted(xiter(self.input), key=realkey, reverse=self.reverse)
1959 for item in items:
1960 yield item
1961
1962 def __xrepr__(self, mode="default"):
1963 if mode == "header" or mode == "footer":
1964 input = getattr(self, "input", None)
1965 if input is not None:
1966 for part in xrepr(input, mode):
1967 yield part
1968 yield (astyle.style_default, " | ")
1969 yield (astyle.style_default, "%s(" % self.__class__.__name__)
1970 for part in xrepr(self.key, "default"):
1971 yield part
1972 if self.reverse:
1973 yield (astyle.style_default, ", ")
1974 for part in xrepr(True, "default"):
1975 yield part
1976 yield (astyle.style_default, ")")
1977 else:
1978 yield (astyle.style_default, repr(self))
1979
1980 def __repr__(self):
1981 return "<%s.%s key=%r reverse=%r at 0x%x>" % \
1982 (self.__class__.__module__, self.__class__.__name__,
1983 self.key, self.reverse, id(self))
1984
1985
1986 tab = 3 # for expandtabs()
1987
1988 def _format(field):
1989 if isinstance(field, str):
1990 text = repr(field.expandtabs(tab))[1:-1]
1991 elif isinstance(field, unicode):
1992 text = repr(field.expandtabs(tab))[2:-1]
1993 elif isinstance(field, datetime.datetime):
1994 # Don't use strftime() here, as this requires year >= 1900
1995 text = "%04d-%02d-%02d %02d:%02d:%02d.%06d" % \
1996 (field.year, field.month, field.day,
1997 field.hour, field.minute, field.second, field.microsecond)
1998 elif isinstance(field, datetime.date):
1999 text = "%04d-%02d-%02d" % (field.year, field.month, field.day)
2000 else:
2001 text = repr(field)
2002 return text
2003
2004
2005 class Display(object):
2006 class __metaclass__(type):
2007 def __ror__(self, input):
2008 return input | self()
2009
2010 def __init__(self, input=None):
2011 self.input = input
2012
2013 def __ror__(self, input):
2014 self.input = input
2015 return self
2016
2017 def display(self):
2018 pass
2019
2020
2021 class iless(Display):
2022 cmd = "less --quit-if-one-screen --LONG-PROMPT --LINE-NUMBERS --chop-long-lines --shift=8 --RAW-CONTROL-CHARS"
2023
2024 def display(self):
2025 try:
2026 pager = os.popen(self.cmd, "w")
2027 try:
2028 for item in xiter(self.input):
2029 first = False
2030 for attr in xattrs(item, "default"):
2031 if first:
2032 first = False
2033 else:
2034 pager.write(" ")
2035 attr = upgradexattr(attr)
2036 if not isinstance(attr, SelfDescriptor):
2037 pager.write(attr.name())
2038 pager.write("=")
2039 pager.write(str(attr.value(item)))
2040 pager.write("\n")
2041 finally:
2042 pager.close()
2043 except Exception as exc:
2044 print "%s: %s" % (exc.__class__.__name__, str(exc))
2045
2046
2047 class _RedirectIO(object):
2048 def __init__(self,*args,**kwargs):
2049 """
2050 Map the system output streams to self.
2051 """
2052 self.stream = StringIO.StringIO()
2053 self.stdout = sys.stdout
2054 sys.stdout = self
2055 self.stderr = sys.stderr
2056 sys.stderr = self
2057
2058 def write(self, text):
2059 """
2060 Write both to screen and to self.
2061 """
2062 self.stream.write(text)
2063 self.stdout.write(text)
2064 if "\n" in text:
2065 self.stdout.flush()
2066
2067 def writelines(self, lines):
2068 """
2069 Write lines both to screen and to self.
2070 """
2071 self.stream.writelines(lines)
2072 self.stdout.writelines(lines)
2073 self.stdout.flush()
2074
2075 def restore(self):
2076 """
2077 Restore the default system streams.
2078 """
2079 self.stdout.flush()
2080 self.stderr.flush()
2081 sys.stdout = self.stdout
2082 sys.stderr = self.stderr
2083
2084
2085 class icap(Table):
2086 """
2087 Execute a python string and capture any output to stderr/stdout.
2088
2089 Examples::
2090
2091 >>> import time
2092 >>> icap("for i in range(10): print i, time.sleep(0.1)")
2093
2094 """
2095 skip_doctest = True
2096
2097 def __init__(self, expr, globals=None):
2098 self.expr = expr
2099 self.globals = globals
2100 log = _RedirectIO()
2101 try:
2102 exec(expr, getglobals(globals))
2103 finally:
2104 log.restore()
2105 self.stream = log.stream
2106
2107 def __iter__(self):
2108 self.stream.seek(0)
2109 for line in self.stream:
2110 yield line.rstrip("\r\n")
2111
2112 def __xrepr__(self, mode="default"):
2113 if mode == "header" or mode == "footer":
2114 yield (astyle.style_default,
2115 "%s(%r)" % (self.__class__.__name__, self.expr))
2116 else:
2117 yield (astyle.style_default, repr(self))
2118
2119 def __repr__(self):
2120 return "%s.%s(%r)" % \
2121 (self.__class__.__module__, self.__class__.__name__, self.expr)
2122
2123
2124 def xformat(value, mode, maxlength):
2125 align = None
2126 full = True
2127 width = 0
2128 text = astyle.Text()
2129 for (style, part) in xrepr(value, mode):
2130 # only consider the first result
2131 if align is None:
2132 if isinstance(style, int):
2133 # (style, text) really is (alignment, stop)
2134 align = style
2135 full = part
2136 continue
2137 else:
2138 align = -1
2139 full = True
2140 if not isinstance(style, int):
2141 text.append((style, part))
2142 width += len(part)
2143 if width >= maxlength and not full:
2144 text.append((astyle.style_ellisis, "..."))
2145 width += 3
2146 break
2147 if align is None: # default to left alignment
2148 align = -1
2149 return (align, width, text)
2150
2151
2152
2153 import astyle
2154
2155 class idump(Display):
2156 # The approximate maximum length of a column entry
2157 maxattrlength = 200
2158
2159 # Style for column names
2160 style_header = astyle.Style.fromstr("white:black:bold")
2161
2162 def __init__(self, input=None, *attrs):
2163 Display.__init__(self, input)
2164 self.attrs = [upgradexattr(attr) for attr in attrs]
2165 self.headerpadchar = " "
2166 self.headersepchar = "|"
2167 self.datapadchar = " "
2168 self.datasepchar = "|"
2169
2170 def display(self):
2171 stream = Term.cout
2172 allattrs = []
2173 attrset = set()
2174 colwidths = {}
2175 rows = []
2176 for item in xiter(self.input):
2177 row = {}
2178 attrs = self.attrs
2179 if not attrs:
2180 attrs = xattrs(item, "default")
2181 for attr in attrs:
2182 if attr not in attrset:
2183 allattrs.append(attr)
2184 attrset.add(attr)
2185 colwidths[attr] = len(attr.name())
2186 try:
2187 value = attr.value(item)
2188 except (KeyboardInterrupt, SystemExit):
2189 raise
2190 except Exception as exc:
2191 value = exc
2192 (align, width, text) = xformat(value, "cell", self.maxattrlength)
2193 colwidths[attr] = max(colwidths[attr], width)
2194 # remember alignment, length and colored parts
2195 row[attr] = (align, width, text)
2196 rows.append(row)
2197
2198 stream.write("\n")
2199 for (i, attr) in enumerate(allattrs):
2200 attrname = attr.name()
2201 self.style_header(attrname).write(stream)
2202 spc = colwidths[attr] - len(attrname)
2203 if i < len(colwidths)-1:
2204 stream.write(self.headerpadchar*spc)
2205 stream.write(self.headersepchar)
2206 stream.write("\n")
2207
2208 for row in rows:
2209 for (i, attr) in enumerate(allattrs):
2210 (align, width, text) = row[attr]
2211 spc = colwidths[attr] - width
2212 if align == -1:
2213 text.write(stream)
2214 if i < len(colwidths)-1:
2215 stream.write(self.datapadchar*spc)
2216 elif align == 0:
2217 spc = colwidths[attr] - width
2218 spc1 = spc//2
2219 spc2 = spc-spc1
2220 stream.write(self.datapadchar*spc1)
2221 text.write(stream)
2222 if i < len(colwidths)-1:
2223 stream.write(self.datapadchar*spc2)
2224 else:
2225 stream.write(self.datapadchar*spc)
2226 text.write(stream)
2227 if i < len(colwidths)-1:
2228 stream.write(self.datasepchar)
2229 stream.write("\n")
2230
2231
2232 class AttributeDetail(Table):
2233 """
2234 ``AttributeDetail`` objects are use for displaying a detailed list of object
2235 attributes.
2236 """
2237 def __init__(self, object, descriptor):
2238 self.object = object
2239 self.descriptor = descriptor
2240
2241 def __iter__(self):
2242 return self.descriptor.iter(self.object)
2243
2244 def name(self):
2245 return self.descriptor.name()
2246
2247 def attrtype(self):
2248 return self.descriptor.attrtype(self.object)
2249
2250 def valuetype(self):
2251 return self.descriptor.valuetype(self.object)
2252
2253 def doc(self):
2254 return self.descriptor.doc(self.object)
2255
2256 def shortdoc(self):
2257 return self.descriptor.shortdoc(self.object)
2258
2259 def value(self):
2260 return self.descriptor.value(self.object)
2261
2262 def __xattrs__(self, mode="default"):
2263 attrs = ("name()", "attrtype()", "valuetype()", "value()", "shortdoc()")
2264 if mode == "detail":
2265 attrs += ("doc()",)
2266 return attrs
2267
2268 def __xrepr__(self, mode="default"):
2269 yield (-1, True)
2270 valuetype = self.valuetype()
2271 if valuetype is not noitem:
2272 for part in xrepr(valuetype):
2273 yield part
2274 yield (astyle.style_default, " ")
2275 yield (astyle.style_default, self.attrtype())
2276 yield (astyle.style_default, " ")
2277 yield (astyle.style_default, self.name())
2278 yield (astyle.style_default, " of ")
2279 for part in xrepr(self.object):
2280 yield part
2281
2282
2283 try:
2284 from ibrowse import ibrowse
2285 except ImportError:
2286 # No curses (probably Windows) => try igrid
2287 try:
2288 from igrid import igrid
2289 except ImportError:
2290 # no wx either => use ``idump`` as the default display.
2291 defaultdisplay = idump
2292 else:
2293 defaultdisplay = igrid
2294 __all__.append("igrid")
2295 else:
2296 defaultdisplay = ibrowse
2297 __all__.append("ibrowse")
2298
2299
2300 # If we're running under IPython, register our objects with IPython's
2301 # generic function ``result_display``, else install a displayhook
2302 # directly as sys.displayhook
2303 if generics is not None:
2304 def display_display(obj):
2305 return obj.display()
2306 generics.result_display.when_type(Display)(display_display)
2307
2308 def display_tableobject(obj):
2309 return display_display(defaultdisplay(obj))
2310 generics.result_display.when_type(Table)(display_tableobject)
2311
2312 def display_tableclass(obj):
2313 return display_tableobject(obj())
2314 generics.result_display.when_type(Table.__metaclass__)(display_tableclass)
2315 else:
2316 def installdisplayhook():
2317 _originalhook = sys.displayhook
2318 def displayhook(obj):
2319 if isinstance(obj, type) and issubclass(obj, Table):
2320 obj = obj()
2321 if isinstance(obj, Table):
2322 obj = defaultdisplay(obj)
2323 if isinstance(obj, Display):
2324 return obj.display()
2325 else:
2326 _originalhook(obj)
2327 sys.displayhook = displayhook
2328 installdisplayhook()
@@ -1,28 +0,0 b''
1 #!/usr/bin/env python
2 # encoding: utf-8
3 """
4 A backwards compatibility layer for IPython.iplib.
5
6 Previously, IPython had an IPython.iplib module. IPython.iplib has been moved
7 to IPython.core.iplib and is being refactored. This new module is provided
8 for backwards compatability. We strongly encourage everyone to start using
9 the new code in IPython.core.iplib.
10 """
11
12 #-----------------------------------------------------------------------------
13 # Copyright (C) 2008-2011 The IPython Development Team
14 #
15 # Distributed under the terms of the BSD License. The full license is in
16 # the file COPYING, distributed as part of this software.
17 #-----------------------------------------------------------------------------
18
19 from warnings import warn
20
21 msg = """
22 This module (IPython.iplib) has been moved to a new location
23 (IPython.core.iplib) and is being refactored. Please update your code
24 to use the new IPython.core.iplib module"""
25
26 warn(msg, category=DeprecationWarning, stacklevel=1)
27
28 from IPython.core.iplib import *
This diff has been collapsed as it changes many lines, (669 lines changed) Show them Hide them
@@ -1,669 +0,0 b''
1 """ Module with physical constants for use with ipython, profile
2 "physics".
3
4 Definition of Fundamental Physical Constants, CODATA Recommended Values
5
6 Source, Peter J. Mohr and Barry N. Taylor,
7 CODATA Recommended Values of the Fundamental
8 Physical Constants, 1998
9
10 Website: physics.nist.gov/constants
11 """
12 # License: BSD-like
13 # Copyright: Gael Varoquaux (gael.varoquaux@normalesup.org)
14
15 # inspired by maxima's physconst.mac by Cliff Yapp
16
17 #from math import * # math MUST be imported BEFORE PhysicalQInteractive
18 from IPython.extensions.PhysicalQInteractive import PhysicalQuantityInteractive
19
20 # Math constants:
21
22 # Pi mathematical constants
23 pi = 3.141592653589793238462643383279502884197169399375105820974944592
24
25 # Universal Constants
26 #-------------------------------------------------------------------------
27
28 c = PhysicalQuantityInteractive(299792458 , 'm/s')
29 c.__doc__ = """speed of light in vacuum"""
30 c.__doc__ = "speed of light in vacuum"
31
32 u_0 = PhysicalQuantityInteractive(4*pi*1E-7 , 'N/(A**2)')
33 u_0.__doc__ = """magnetic constant"""
34 mu_0 = PhysicalQuantityInteractive(4*pi*1E-7 , 'N/(A**2)')
35
36 epsilon_0 = PhysicalQuantityInteractive(8.854187817E-12 , 'F/m')
37 epsilon_0.__doc__ = """electric constant """
38
39 Z_0 = PhysicalQuantityInteractive(376.730313461 , 'ohm')
40 Z_0.__doc__ = """characteristic impedance of vacuum """
41
42 G = PhysicalQuantityInteractive(6.673E-11 , 'm**3/(kg*s**2)')
43 G.__doc__ = """Newtonian constant of gravitation """
44
45
46 h = PhysicalQuantityInteractive(6.62606876E-34 , 'J*s')
47 h.__doc__ = """Planck constant """
48
49
50 h_eV = PhysicalQuantityInteractive(4.13566727E-15 , 'eV*s')
51 h_eV.__doc__ = """Planck constant in eVs """
52
53
54 h_bar = PhysicalQuantityInteractive(1.054571596E-34 , 'J*s')
55 h_bar.__doc__ = """Hbar"""
56
57
58 h_bar_eV = PhysicalQuantityInteractive(6.58211889E-16 , 'eV*s')
59 h_bar_eV.__doc__ = """Hbar in eV"""
60
61
62 P_m = PhysicalQuantityInteractive(2.1767E-8 , 'kg')
63 P_m.__doc__ = """Planck mass"""
64
65
66 P_l = PhysicalQuantityInteractive(1.6160E-35 , 'm')
67 P_l.__doc__ = """Planck length """
68
69
70 P_t = PhysicalQuantityInteractive(5.3906E-44 , 's')
71 P_t.__doc__ = """Planck time """
72
73 # Electromagnetic Constants
74 #------------------------------------------------------------------------
75
76 _e = PhysicalQuantityInteractive(1.602176462E-19 , 'C')
77 _e.__doc__ = """elementary charge"""
78 q = _e
79
80
81 capitalphi_0 = PhysicalQuantityInteractive(2.067833636E-15 , 'Wb')
82 capitalphi_0.__doc__ = """magnetic flux quantum """
83 mfq_0 = PhysicalQuantityInteractive(2.067833636E-15 , 'Wb')
84
85
86 G_0 = PhysicalQuantityInteractive(7.748091696E-5 , 'S')
87 G_0.__doc__ = """conductance quantum """
88
89
90 K_J = PhysicalQuantityInteractive(483597.898E9 , 'Hz/V')
91 K_J.__doc__ = """Josephson constant"""
92
93
94 R_K = PhysicalQuantityInteractive(25812.807572 , 'ohm')
95 R_K.__doc__ = """von Klitzing constant"""
96
97
98 u_B = PhysicalQuantityInteractive(927.400899E-26 , 'J/T')
99 u_B.__doc__ = """Bohr magneton"""
100
101 ueVT_B = PhysicalQuantityInteractive(5.788381749E-5 , 'eV/T')
102 ueVT_B.__doc__ = """Bohr magneton in eV T-1"""
103
104
105 u_N = PhysicalQuantityInteractive(5.05078317E-27 , 'J/T')
106 u_N.__doc__ = """nuclear magneton """
107
108 ueVT_N = PhysicalQuantityInteractive(3.152451238E-8 , 'eV/T')
109 ueVT_N.__doc__ = """nuclear magneton in eV T-1 """
110
111 # Atomic and Nuclear Constants
112 # General
113 #-------------------------------------------------------------------------
114 # fine-structure constant
115 alpha = 7.297352533E-3
116
117
118 Ry = PhysicalQuantityInteractive(10973731.568549 , '1/m')
119 Ry.__doc__ = """Rydberg constant """
120 Ry_INF = PhysicalQuantityInteractive(10973731.568549 , '1/m')
121
122
123 a_0 = PhysicalQuantityInteractive(0.5291772083E-10 , 'm')
124 a_0.__doc__ = """Bohr radius """
125
126
127 E_h = PhysicalQuantityInteractive(4.35974381E-18 , 'J')
128 E_h.__doc__ = """Hartree energy """
129
130 Eev_h = PhysicalQuantityInteractive(27.2113834 , 'eV')
131 Eev_h.__doc__ = """Hartree energy in eV """
132
133
134 qcir2 = PhysicalQuantityInteractive(3.636947516E-4 , 'm**2/s')
135 qcir2.__doc__ = """quantum of circulation h/(2me) """
136
137 qcir = PhysicalQuantityInteractive(7.273895032E-4 , 'm**2/s')
138 qcir.__doc__ = """quantum of circulation h/(me) """
139
140 # Electroweak
141 #-------------------------------------------------------------------------
142
143 Fcc = PhysicalQuantityInteractive(1.16639E-5 , '1/GeV**2')
144 Fcc.__doc__ = """Fermi coupling constant """
145 # weak mixing angled W (on-shell scheme)
146 wma_W = 0.2224
147
148 # Electron, e-
149 #-------------------------------------------------------------------------
150
151 m_e = PhysicalQuantityInteractive(9.10938188E-31 , 'kg')
152 m_e.__doc__ = """electron mass """
153
154 m_e_u = PhysicalQuantityInteractive(5.485799110E-4 , 'amu')
155 m_e_u.__doc__ = """electron mass (electron relative atomic mass times amu)"""
156
157 me_J = PhysicalQuantityInteractive(8.18710414E-14 , 'J')
158 me_J.__doc__ = """electron mass - energy equivalent """
159
160 me_MeV = PhysicalQuantityInteractive(0.510998902 , 'MeV')
161 me_MeV.__doc__ = """electron mass - energy equivalent in MeV"""
162
163 # electron-muon mass ratio
164 memu = 4.83633210E-3
165
166 # electron-tau mass ratio
167 metau = 2.87555E-4
168
169 # electron-proton mass ratio
170 memp = 5.446170232E-4
171
172 # electron-neutron mass ratio
173 memn = 5.438673462E-4
174
175 # electron-deuteron mass ratio
176 memd = 2.7244371170E-4
177
178 # electron to alpha particle mass ratio
179 memalpha = 1.3709335611E-4
180
181
182 echargeemass = PhysicalQuantityInteractive(-1.758820174E11 , 'C/kg')
183 echargeemass.__doc__ = """electron charge to mass quotient """
184
185
186 Molar_e = PhysicalQuantityInteractive(5.485799110E-7 , 'kg/mol')
187 Molar_e.__doc__ = """electron molar mass """
188
189
190 lambdaC = PhysicalQuantityInteractive(2.426310215E-12 , 'm')
191 lambdaC.__doc__ = """Compton wavelength """
192
193
194 r_e = PhysicalQuantityInteractive(2.817940285E-15 , 'm')
195 r_e.__doc__ = """classical electron radius """
196
197
198 sigma_e = PhysicalQuantityInteractive(0.665245854E-28 , 'm**2')
199 sigma_e.__doc__ = """Thomson cross section """
200
201
202 u_e = PhysicalQuantityInteractive(-928.476362E-26 , 'J/T')
203 u_e.__doc__ = """electron magnetic moment """
204
205 # electron magnetic moment to Bohr magneton ratio
206 ueuB = -1.0011596521869
207
208 # electron magnetic moment to nuclear magneton ratio
209 ueuN = -1838.2819660
210
211 # electron magnetic moment anomaly |ue|/uB - 1
212 a_e = 1.1596521869E-3
213
214 # electron g-factor
215 g_e = -2.0023193043737
216
217 # electron-muon magnetic moment ratio
218 ueuu = 206.7669720
219
220 # electron-proton magnetic moment ratio
221 ueup = -658.2106875
222
223 # electron to shielded proton magnetic moment ratio (H2O, sphere, 25 C)
224 ueusp = -658.2275954
225
226 # electron-neutron magnetic moment ratio
227 ueun = 960.92050
228
229 # electron-deuteron magnetic moment ratio
230 ueud = -2143.923498
231
232 # electron to shielded helione magnetic moment ratio (gas, sphere, 25 C)
233 ueush = 864.058255
234
235
236 gamma_e = PhysicalQuantityInteractive(1.760859794E11 , '1/(s*T)')
237 gamma_e.__doc__ = """electron gyromagnetic ratio """
238
239 # Muon, u-
240 #-------------------------------------------------------------------------
241
242 m_u = PhysicalQuantityInteractive(1.88353109E-28 , 'kg')
243 m_u.__doc__ = """muon mass """
244
245 mu_u = PhysicalQuantityInteractive(0.1134289168 , 'amu')
246 mu_u.__doc__ = """muon mass in muon relative atomic mass times amu """
247
248
249 muc2_J = PhysicalQuantityInteractive(1.69283332E-11 , 'J')
250 muc2_J.__doc__ = """energy equivalent """
251
252 muc2_MeV = PhysicalQuantityInteractive(105.6583568 , 'MeV')
253 muc2_MeV.__doc__ = """energy equivalent in MeV """
254
255 # muon-electron mass ratio
256 mume = 206.7682657
257
258 # muon-tau mass ratio
259 mum = 5.94572E-2
260
261 # muon-proton mass ratio
262 mump = 0.1126095173
263
264 # muon-neutron mass ratio
265 mumn = 0.1124545079
266
267
268 Molar_u = PhysicalQuantityInteractive(0.1134289168E-3 , 'kg/mol')
269 Molar_u.__doc__ = """muon molar mass """
270
271
272 lambda_C_u = PhysicalQuantityInteractive(11.73444197E-15 , 'm')
273 lambda_C_u.__doc__ = """muon Compton wavelength """
274
275
276 uu = PhysicalQuantityInteractive(-4.49044813E-26 , 'J/T')
277 uu.__doc__ = """muon magnetic moment """
278
279 # ratio of muon magnetic moment to Bohr magneton ratio
280 uuuB = -4.84197085E-3
281
282 # ratio of muon magnetic moment to nuclear magneton ratio
283 uuuN = -8.89059770
284
285 # muon magnetic moment anomaly |uu|/(e /2mu) - 1
286 a_u = 1.16591602E-3
287
288 # muon g-factor -2(1 + au)
289 g_u = -2.0023318320
290
291 # muon-proton magnetic moment ratio
292 uuup = -3.18334539
293
294 # Tau, tau-
295 #-------------------------------------------------------------------------
296
297 m_tau = PhysicalQuantityInteractive(3.16788E-27 , 'kg')
298 m_tau.__doc__ = """tau mass """
299
300 mu_tau = PhysicalQuantityInteractive(1.90774 , 'amu')
301 mu_tau.__doc__ = """tau mass (tau relative atomic mass times amu) """
302
303
304 mtauc2_J = PhysicalQuantityInteractive(2.84715E-10 , 'J')
305 mtauc2_J.__doc__ = """tau mass energy equivalent """
306
307
308 mtauc2_MeV = PhysicalQuantityInteractive(1777.05 , 'MeV')
309 mtauc2_MeV.__doc__ = """tau mass energy equivalent in MeV """
310
311 # tau-electron mass ratio
312 mtaume = 3477.60
313
314 # tau-muon mass ratio
315 mtaumu = 16.8188
316
317 # tau-proton mass ratio
318 mtaump = 1.89396
319
320 # tau-neutron mass ratio
321 mtaumn = 1.89135
322
323
324 Molar_tau = PhysicalQuantityInteractive(1.90774E-3 , 'kg/mol')
325 Molar_tau.__doc__ = """tau molar mass """
326
327
328 lambda_C_tau = PhysicalQuantityInteractive(0.69770E-15 , 'm')
329 lambda_C_tau.__doc__ = """tau Compton wavelength """
330
331 # Proton, p
332 #-------------------------------------------------------------------------
333
334 m_p = PhysicalQuantityInteractive(1.67262158E-27 , 'kg')
335 m_p.__doc__ = """proton mass """
336
337 mu_p = PhysicalQuantityInteractive(1.00727646688 , 'amu')
338 mu_p.__doc__ = """proton mass (proton relative atomic mass times amu) """
339
340
341 mpc2_J = PhysicalQuantityInteractive(1.50327731E-10 , 'J')
342 mpc2_J.__doc__ = """energy equivalent """
343
344 mpc2_MeV = PhysicalQuantityInteractive(938.271998 , 'MeV')
345 mpc2_MeV.__doc__ = """energy equivalent in MeV """
346
347 # proton-electron mass ratio
348 mpme = 1836.1526675
349
350 # proton-muon mass ratio
351 mpmu = 8.88024408
352
353 # proton-tau mass ratio
354 mpmtau = 0.527994
355
356 # proton-neutron mass ratio
357 mpmn = 0.99862347855
358
359
360 emp = PhysicalQuantityInteractive(9.57883408E7 , 'C/kg')
361 emp.__doc__ = """proton charge to mass quotient """
362
363
364 Molar_p = PhysicalQuantityInteractive(1.00727646688E-3 , 'kg/mol')
365 Molar_p.__doc__ = """proton molar mass """
366
367
368 lambda_C_p = PhysicalQuantityInteractive(1.321409847E-15 , 'm')
369 lambda_C_p.__doc__ = """proton Compton wavelength h/mpc """
370
371
372 up = PhysicalQuantityInteractive(1.410606633E-26 , 'J/T')
373 up.__doc__ = """proton magnetic moment """
374
375 # proton magnetic moment to Bohr magneton ratio
376 upuB = 1.521032203E-3
377
378 # proton magnetic moment to nuclear magneton ratio
379 upuN = 2.792847337
380
381 # proton g-factor 2up/uN
382 g_p = 5.585694675
383
384 # proton-neutron magnetic moment ratio
385 upun = -1.45989805
386
387
388 usp = PhysicalQuantityInteractive(1.410570399E-26 , 'J/T')
389 usp.__doc__ = """shielded proton magnetic moment (H2O, sphere, 25 C)"""
390
391 # shielded proton magnetic moment to Bohr magneton ratio
392 uspuB = 1.520993132E-3
393
394 # shielded proton magnetic moment to nuclear magneton ratio
395 uspuN = 2.792775597
396
397 # proton magnetic shielding correction 1 - u p/up (H2O, sphere, 25 C)
398 spc = 25.687E-6
399
400
401 gamma_p = PhysicalQuantityInteractive(2.67522212E8 , '1/(s*T)')
402 gamma_p.__doc__ = """proton gyromagnetic ratio """
403
404
405 gamma_sp = PhysicalQuantityInteractive(2.67515341E8 , '1/(s*T)')
406 gamma_sp.__doc__ = """shielded proton gyromagnetic ratio (H2O, sphere, 25 C)"""
407
408 # Neutron, n
409 #-------------------------------------------------------------------------
410
411 m_n = PhysicalQuantityInteractive(1.67492716E-27 , 'kg')
412 m_n.__doc__ = """neutron mass """
413
414 mu_n = PhysicalQuantityInteractive(1.00866491578 , 'amu')
415 mu_n.__doc__ = """neutron mass (neutron relative atomic mass times amu) """
416
417
418 mnc2_J = PhysicalQuantityInteractive(1.50534946E-10 , 'J')
419 mnc2_J.__doc__ = """neutron mass energy equivalent """
420
421
422 mnc2_MeV = PhysicalQuantityInteractive(939.565330 , 'MeV')
423 mnc2_MeV.__doc__ = """neutron mass energy equivalent in MeV """
424
425 # neutron-electron mass ratio
426 mnme = 1838.6836550
427
428 # neutron-muon mass ratio
429 mnmu = 8.89248478
430
431 # neutron-tau mass ratio
432 mnm = 0.528722
433
434 # neutron-proton mass ratio
435 mnmp = 1.00137841887
436
437
438 Molar_n = PhysicalQuantityInteractive(1.00866491578E-3 , 'kg/mol')
439 Molar_n.__doc__ = """neutron molar mass """
440
441
442 lambda_C_n = PhysicalQuantityInteractive(1.319590898E-15 , 'm')
443 lambda_C_n.__doc__ = """neutron Compton wavelength"""
444
445
446 un = PhysicalQuantityInteractive(-0.96623640E-26 , 'J/T')
447 un.__doc__ = """neutron magnetic moment """
448
449 # neutron magnetic moment to Bohr magneton ratio
450 unuB = -1.04187563E-3
451
452 # neutron magnetic moment to nuclear magneton ratio
453 unuN = -1.91304272
454
455 # neutron g-factor
456 g_n = -3.82608545
457
458 # neutron-electron magnetic moment ratio
459 unue = 1.04066882E-3
460
461 # neutron-proton magnetic moment ratio
462 unup = -0.68497934
463
464 # neutron to shielded proton magnetic moment ratio (H2O, sphere, 25 C)
465 unusp = -0.68499694
466
467
468 gamma_n = PhysicalQuantityInteractive(1.83247188E8 , '1/(s*T)')
469 gamma_n.__doc__ = """neutron gyromagnetic ratio """
470
471 # Deuteron, d
472 #-------------------------------------------------------------------------
473
474 m_d = PhysicalQuantityInteractive(3.34358309E-27 , 'kg')
475 m_d.__doc__ = """deuteron mass """
476
477
478 mu_d = PhysicalQuantityInteractive(2.01355321271 , 'amu')
479 mu_d.__doc__ = """deuteron mass (deuteron relative atomic mass times amu) """
480
481
482 mdc2_J = PhysicalQuantityInteractive(3.00506262E-10 , 'J')
483 mdc2_J.__doc__ = """deuteron mass energy equivalent """
484
485
486 mdc2_eV = PhysicalQuantityInteractive(1875.612762 , 'MeV')
487 mdc2_eV.__doc__ = """deuteron mass energy equivalent in MeV """
488
489 # deuteron-electron mass ratio
490 mdme = 3670.4829550
491
492 # deuteron-proton mass ratio
493 mdmp = 1.99900750083
494
495
496 Molar_d = PhysicalQuantityInteractive(2.01355321271E-3 , 'kg/mol')
497 Molar_d.__doc__ = """deuteron molar mass """
498
499
500 ud = PhysicalQuantityInteractive(0.433073457E-26 , 'J/T')
501 ud.__doc__ = """deuteron magnetic moment """
502
503 # deuteron magnetic moment to Bohr magneton ratio
504 uduB = 0.4669754556E-3
505
506 # deuteron magnetic moment to nuclear magneton ratio
507 uduN = 0.8574382284
508
509 # deuteron-electron magnetic moment ratio
510 udue = -4.664345537E-4
511
512 # deuteron-proton magnetic moment ratio
513 udup = 0.3070122083
514
515 # deuteron-neutron magnetic moment ratio
516 udun = -0.44820652
517
518 # Helion, h
519 #-------------------------------------------------------------------------
520
521 m_h = PhysicalQuantityInteractive(5.00641174E-27 , 'kg')
522 m_h.__doc__ = """helion mass """
523
524
525 mu_h = PhysicalQuantityInteractive(3.01493223469 , 'amu')
526 mu_h.__doc__ = """helion mass (helion relative atomic mass times amu) """
527
528
529 mhc2_J = PhysicalQuantityInteractive(4.49953848E-10 , 'J')
530 mhc2_J.__doc__ = """helion mass energy equivalent """
531
532 mhc2_MeV = PhysicalQuantityInteractive(2808.39132 , 'MeV')
533 mhc2_MeV.__doc__ = """helion mass energy equivalent in MeV """
534
535 # helion-electron mass ratio
536 mhme = 5495.885238
537
538 # helion-proton mass ratio
539 mhmp = 2.99315265850
540
541
542 Molar_h = PhysicalQuantityInteractive(3.01493223469E-3 , 'kg/mol')
543 Molar_h.__doc__ = """helion molar mass """
544
545
546 ush = PhysicalQuantityInteractive(-1.074552967E-26 , 'J/T')
547 ush.__doc__ = """shielded helion magnetic moment (gas, sphere, 25 C)"""
548
549 # shielded helion magnetic moment to Bohr magneton ratio
550 ushuB = -1.158671474E-3
551
552 # shielded helion magnetic moment to nuclear magneton ratio
553 ushuN = -2.127497718
554
555 # shielded helion to proton magnetic moment ratio (gas, sphere, 25 C)
556 ushup = -0.761766563
557
558 # shielded helion to shielded proton magnetic moment ratio (gas/H2O, spheres, 25 C)
559 ushusp = -0.7617861313
560
561
562 gamma_h = PhysicalQuantityInteractive(2.037894764E8 , '1/(s*T)')
563 gamma_h.__doc__ = """shielded helion gyromagnetic (gas, sphere, 25 C) """
564
565 # Alpha particle,
566 #-------------------------------------------------------------------------
567
568 m_alpha = PhysicalQuantityInteractive(6.64465598E-27 , 'kg')
569 m_alpha.__doc__ = """alpha particle mass """
570
571 mu_alpha = PhysicalQuantityInteractive(4.0015061747 , 'amu')
572 mu_alpha.__doc__ = """alpha particle mass (alpha particle relative atomic mass times amu) """
573
574
575 malphac2_J = PhysicalQuantityInteractive(5.97191897E-10 , 'J')
576 malphac2_J.__doc__ = """alpha particle mass energy equivalent """
577
578
579 malphac2_MeV = PhysicalQuantityInteractive(3727.37904 , 'MeV')
580 malphac2_MeV.__doc__ = """alpha particle mass energy equivalent in MeV """
581
582 # alpha particle to electron mass ratio
583 malphame = 7294.299508
584
585 # alpha particle to proton mass ratio
586 malphamp = 3.9725996846
587
588
589 Molar_alpha = PhysicalQuantityInteractive(4.0015061747E-3 , 'kg/mol')
590 Molar_alpha.__doc__ = """alpha particle molar mass"""
591
592 # PHYSICO-CHEMICAL
593 #-------------------------------------------------------------------------
594
595 N_A = PhysicalQuantityInteractive(6.02214199E23 , '1/mol')
596 N_A.__doc__ = """Avogadro constant """
597 L = PhysicalQuantityInteractive(6.02214199E23 , '1/mol')
598
599
600 m_u = PhysicalQuantityInteractive(1.66053873E-27 , 'kg')
601 m_u.__doc__ = """atomic mass constant mu = 112m(12C) = 1 u = 10E-3 kg mol-1/NA"""
602 # atomic mass constant mu = 112m(12C) = 1 u = 10E-3 kg mol-1/NA
603 amu = m_u
604
605
606 muc2_J = PhysicalQuantityInteractive(1.49241778E-10 , 'J')
607 muc2_J.__doc__ = """energy equivalent of the atomic mass constant"""
608
609
610 muc2_MeV = PhysicalQuantityInteractive(931.494013 , 'MeV')
611 muc2_MeV.__doc__ = """energy equivalent of the atomic mass constant in MeV """
612
613
614 F = PhysicalQuantityInteractive(96485.3415 , 'C/mol')
615 F.__doc__ = """Faraday constant"""
616
617
618 N_Ah = PhysicalQuantityInteractive(3.990312689E-10 , 'J*s/mol')
619 N_Ah.__doc__ = """molar Planck constant """
620
621
622 R = PhysicalQuantityInteractive(8.314472 , 'J/(mol*K)')
623 R.__doc__ = """molar gas constant """
624
625
626 k_J = PhysicalQuantityInteractive(1.3806503E-23 , 'J/K')
627 k_J.__doc__ = """Boltzmann constant """
628
629
630 k_eV = PhysicalQuantityInteractive(8.617342E-5 , 'eV/K')
631 k_eV.__doc__ = """Boltzmann constant in eV """
632
633
634 n_0 = PhysicalQuantityInteractive(2.6867775E25 , '1/m**3')
635 n_0.__doc__ = """Loschmidt constant NA/Vm """
636
637
638 Vm_1 = PhysicalQuantityInteractive(22.413996E-3 , 'm**3/mol')
639 Vm_1.__doc__ = """molar volume of ideal gas RT/p T = 273.15 K, p = 101.325 kPa """
640
641 Vm_2 = PhysicalQuantityInteractive(22.710981E-3 , 'm**3/mol')
642 Vm_2.__doc__ = """molar volume of ideal gas RT/p T = 273.15 K, p = 100 kPa """
643
644 # Sackur-Tetrode constant (absolute entropy constant) 52 + ln_(2 mukT1/h2)3/2kT1/p0
645 # T1 = 1 K, p0 = 100 kPa
646 S_0R_1 = -1.1517048
647 # T1 = 1 K, p0 = 101.325 kPa
648 S_0R_2 = -1.1648678
649
650
651 sigma = PhysicalQuantityInteractive(5.670400E-8 , 'W/(m**2*K**4)')
652 sigma.__doc__ = """Stefan-Boltzmann constant """
653
654
655 c_1 = PhysicalQuantityInteractive(3.74177107E-16 , 'W*m**2')
656 c_1.__doc__ = """first radiation constant"""
657
658
659 c_1L = PhysicalQuantityInteractive(1.191042722E-16 , 'W*m**2/sr')
660 c_1L.__doc__ = """first radiation constant for spectral radiance"""
661
662
663 c_2 = PhysicalQuantityInteractive(1.4387752E-2 , 'm*K')
664 c_2.__doc__ = """second radiation constant"""
665
666
667 b = PhysicalQuantityInteractive(2.8977686E-3 , 'm*K')
668 b.__doc__ = """Wien displacement law constant b = maxT = c2/4.965 114231... """
669
@@ -1,62 +0,0 b''
1 """ Set default options for IPython.
2
3 Just import this module to get reasonable defaults for everything.
4
5 These configurations used to be performed in ipythonrc (or ipythonrc.ini).
6 Therefore importing this in your config files makes ipython basically
7 ignore your ipythonrc. This is *not* imported by default, you need to import
8 this manually in one of your config files.
9
10 You can further override these defaults in e.g. your ipy_user_config.py,
11 ipy_profile_PROFILENAME etc.
12
13 """
14
15 import IPython.utils.rlineimpl as readline
16 from IPython.core import ipapi
17 ip = ipapi.get()
18
19 o = ip.options
20
21 o.colors = "Linux"
22 o.color_info=1
23 o.confirm_exit=1
24 o.pprint=1
25 o.multi_line_specials=1
26 o.xmode="Context"
27
28
29 o.prompt_in1='In [\#]: '
30 o.prompt_in2 =' .\D.: '
31 o.prompt_out = 'Out[\#]: '
32 o.prompts_pad_left=1
33
34 o.autoindent = 1
35
36 o.readline_remove_delims="-/~"
37 o.readline_merge_completions=1
38
39 o.readline = 1
40
41 rlopts = """\
42 tab: complete
43 "\C-l": possible-completions
44 set show-all-if-ambiguous on
45 "\C-o": tab-insert
46 "\M-i": " "
47 "\M-o": "\d\d\d\d"
48 "\M-I": "\d\d\d\d"
49 "\C-r": reverse-search-history
50 "\C-s": forward-search-history
51 "\C-p": history-search-backward
52 "\C-n": history-search-forward
53 "\e[A": history-search-backward
54 "\e[B": history-search-forward
55 "\C-k": kill-line
56 "\C-u": unix-line-discard"""
57
58 if readline.have_readline:
59 for cmd in rlopts.split('\n'):
60 readline.parse_and_bind(cmd)
61
62
@@ -1,80 +0,0 b''
1 import os,sys
2
3 import ipy_rehashdir,glob
4 from ipy_rehashdir import selflaunch, PyLauncher
5
6 def pylaunchers():
7 """Create launchers for python scripts in cwd and store them in alias table
8
9 This is useful if you want to invoke .py scripts from ipykit session,
10 just adding .py files in PATH does not work without file association.
11
12 .ipy files will be run like macros.
13
14 """
15 fs = glob.glob('*.py') + glob.glob('*.ipy')
16 for f in fs:
17 l = PyLauncher(f)
18 n = os.path.splitext(f)[0]
19 ip.define_alias(n, l)
20 ip.magic('store '+n)
21
22
23 def exta_imports():
24 # add some modules that you'd want to be bundled in the ipykit
25 # library zip file here. Do this if you get ImportErrors from scripts you
26 # try to launch with 'py' or pylaunchers. In theory you could include
27 # the whole stdlib here for full script coverage
28
29 # note that this is never run, it's just here for py2exe
30 import distutils.dir_util
31
32 def kitroot():
33 return os.environ.get('IPYKITROOT', None)
34
35 def main():
36
37 if not kitroot():
38 print "Can't configure ipykit, IPYKITROOT should be set."
39 return
40
41 os.environ["PATH"] = os.environ["PATH"] + ";" + kitroot() + "\\bin;"
42 ip.push("pylaunchers")
43 cmds = ip.db.get('syscmdlist', None)
44 if cmds is None:
45 ip.magic('rehashx')
46 cmds = ip.db.get('syscmdlist', [])
47 #print cmds
48 if 'sc1' in cmds:
49 print "Default editor: Sc1"
50 import ipy_editors
51 ipy_editors.scite('sc1')
52
53 # for icp, imv, imkdir, etc.
54 import ipy_fsops
55
56 greeting = """\n\n === Welcome to ipykit ===
57
58 %quickref - learn quickly about IPython.
59
60 """
61
62 def ipython_firstrun(ip):
63
64 print "First run of ipykit - configuring"
65
66 ip.define_alias('py',selflaunch)
67 ip.define_alias('d','dir /w /og /on')
68 ip.magic('store py')
69 ip.magic('store d')
70
71 bins = kitroot() +'/bin'
72
73 print greeting
74
75 def init_ipython(ipy):
76 global ip
77 ip = ipy
78 main()
79
80
@@ -1,62 +0,0 b''
1 """ Legacy stuff
2
3 Various stuff that are there for historical / familiarity reasons.
4
5 This is automatically imported by default profile, though not other profiles
6 (e.g. 'sh' profile).
7
8 Stuff that is considered obsolete / redundant is gradually moved here.
9
10 """
11
12 from IPython.core import ipapi
13 ip = ipapi.get()
14
15 import os,sys
16
17 from IPython.utils.genutils import *
18
19 # use rehashx
20
21 def magic_rehash(self, parameter_s = ''):
22 """Update the alias table with all entries in $PATH.
23
24 This version does no checks on execute permissions or whether the
25 contents of $PATH are truly files (instead of directories or something
26 else). For such a safer (but slower) version, use %rehashx."""
27
28 # This function (and rehashx) manipulate the alias_table directly
29 # rather than calling magic_alias, for speed reasons. A rehash on a
30 # typical Linux box involves several thousand entries, so efficiency
31 # here is a top concern.
32
33 path = filter(os.path.isdir,os.environ.get('PATH','').split(os.pathsep))
34 alias_table = self.shell.alias_table
35 for pdir in path:
36 for ff in os.listdir(pdir):
37 # each entry in the alias table must be (N,name), where
38 # N is the number of positional arguments of the alias.
39 alias_table[ff] = (0,ff)
40 # Make sure the alias table doesn't contain keywords or builtins
41 self.shell.alias_table_validate()
42 # Call again init_auto_alias() so we get 'rm -i' and other modified
43 # aliases since %rehash will probably clobber them
44 self.shell.init_auto_alias()
45
46 ip.define_magic("rehash", magic_rehash)
47
48 # Exit
49 def magic_Quit(self, parameter_s=''):
50 """Exit IPython without confirmation (like %Exit)."""
51
52 self.shell.ask_exit()
53
54 ip.define_magic("Quit", magic_Quit)
55
56
57 # make it autocallable fn if you really need it
58 def magic_p(self, parameter_s=''):
59 """Just a short alias for Python's 'print'."""
60 exec 'print ' + parameter_s in self.shell.user_ns
61
62 ip.define_magic("p", magic_p)
@@ -1,47 +0,0 b''
1 # -*- coding: utf-8 -*-
2 """
3 Add %p4 magic for pythonic p4 (Perforce) usage.
4 """
5
6 from IPython.core import ipapi
7 ip = ipapi.get()
8
9 import os,sys,marshal
10
11 import ipy_stock_completers
12
13 def p4_f(self, parameter_s=''):
14 cmd = 'p4 -G ' + parameter_s
15 fobj = os.popen(cmd)
16 out = []
17 while 1:
18 try:
19 out.append(marshal.load(fobj))
20 except EOFError:
21 break
22
23 return out
24
25 def p4d(fname):
26 return os.popen('p4 where ' + fname).read().split()[0]
27
28 ip.push("p4d")
29
30 ip.define_magic('p4', p4_f)
31
32 p4_commands = """\
33 add admin annotate branch branches change changes changelist
34 changelists client clients counter counters delete depot depots
35 describe diff diff2 dirs edit filelog files fix fixes flush fstat
36 group groups have help info integrate integrated job jobs jobspec
37 label labels labelsync lock logger login logout monitor obliterate
38 opened passwd print protect rename reopen resolve resolved revert
39 review reviews set submit sync tag tickets triggers typemap unlock
40 user users verify workspace workspaces where"""
41
42 def p4_completer(self,event):
43 return ipy_stock_completers.vcs_completer(p4_commands, event)
44
45 ip.set_hook('complete_command', p4_completer, str_key = '%p4')
46 ip.set_hook('complete_command', p4_completer, str_key = 'p4')
47
@@ -1,4 +0,0 b''
1 """ Config file for 'default' profile """
2
3 # get various stuff that are there for historical / familiarity reasons
4 import ipy_legacy No newline at end of file
@@ -1,24 +0,0 b''
1 """ IPython 'numpy' profile, to preload NumPy.
2
3 This profile loads the math/cmath modules as well as all of numpy.
4
5 It exposes numpy via the 'np' shorthand as well for convenience.
6 """
7
8 from IPython.core import ipapi
9 import ipy_defaults
10
11 def main():
12 ip = ipapi.get()
13
14 try:
15 ip.ex("import math,cmath")
16 ip.ex("import numpy")
17 ip.ex("import numpy as np")
18
19 ip.ex("from numpy import *")
20
21 except ImportError:
22 print "Unable to start NumPy profile, is numpy installed?"
23
24 main()
@@ -1,29 +0,0 b''
1 """ IPython 'scipy' profile, preloads NumPy and SciPy.
2
3 This profile loads the math/cmath modules as well as all of numpy and scipy.
4
5 It exposes numpy and scipy via the 'np' and 'sp' shorthands as well for
6 convenience.
7 """
8
9 from IPython.core import ipapi
10 import ipy_defaults
11
12 def main():
13 ip = ipapi.get()
14
15 try:
16 ip.ex("import math,cmath")
17 ip.ex("import numpy")
18 ip.ex("import scipy")
19
20 ip.ex("import numpy as np")
21 ip.ex("import scipy as sp")
22
23 ip.ex("from numpy import *")
24 ip.ex("from scipy import *")
25
26 except ImportError:
27 print "Unable to start scipy profile, are numpy and scipy installed?"
28
29 main()
@@ -1,268 +0,0 b''
1 """Shell mode for IPython.
2
3 Start ipython in shell mode by invoking "ipython -p sh"
4
5 (the old version, "ipython -p pysh" still works but this is the more "modern"
6 shell mode and is recommended for users who don't care about pysh-mode
7 compatibility)
8 """
9
10 from IPython.core import ipapi
11 from IPython.core.error import TryNext
12 import os,re,textwrap
13
14 # The import below effectively obsoletes your old-style ipythonrc[.ini],
15 # so consider yourself warned!
16
17 import ipy_defaults
18
19 def main():
20 ip = ipapi.get()
21 o = ip.options
22 # autocall to "full" mode (smart mode is default, I like full mode)
23
24 o.autocall = 2
25
26 # Jason Orendorff's path class is handy to have in user namespace
27 # if you are doing shell-like stuff
28 try:
29 ip.ex("from IPython.external.path import path" )
30 except ImportError:
31 pass
32
33 # beefed up %env is handy in shell mode
34 import envpersist
35
36 # To see where mycmd resides (in path/aliases), do %which mycmd
37 import ipy_which
38
39 # tab completers for hg, svn, ...
40 import ipy_app_completers
41
42 # To make executables foo and bar in mybin usable without PATH change, do:
43 # %rehashdir c:/mybin
44 # %store foo
45 # %store bar
46 import ipy_rehashdir
47
48 # does not work without subprocess module!
49 #import ipy_signals
50
51 ip.ex('import os')
52 ip.ex("def up(): os.chdir('..')")
53 ip.user_ns['LA'] = LastArgFinder()
54
55 # You can assign to _prompt_title variable
56 # to provide some extra information for prompt
57 # (e.g. the current mode, host/username...)
58
59 ip.user_ns['_prompt_title'] = ''
60
61 # Nice prompt
62 o.prompt_in1= r'\C_Green${_prompt_title}\C_LightBlue[\C_LightCyan\Y2\C_LightBlue]\C_Green|\#> '
63 o.prompt_in2= r'\C_Green|\C_LightGreen\D\C_Green> '
64 o.prompt_out= '<\#> '
65
66 from IPython.core import release
67
68 import sys
69 # Non-chatty banner
70 o.banner = "IPython %s [on Py %s]\n" % (release.version,sys.version.split(None,1)[0])
71
72
73 ip.default_option('cd','-q')
74 ip.default_option('macro', '-r')
75 # If you only rarely want to execute the things you %edit...
76 #ip.default_option('edit','-x')
77
78
79 o.prompts_pad_left="1"
80 # Remove all blank lines in between prompts, like a normal shell.
81 o.separate_in="0"
82 o.separate_out="0"
83 o.separate_out2="0"
84
85 # now alias all syscommands
86
87 db = ip.db
88
89 syscmds = db.get("syscmdlist",[] )
90 if not syscmds:
91 print textwrap.dedent("""
92 System command list not initialized, probably the first run...
93 running %rehashx to refresh the command list. Run %rehashx
94 again to refresh command list (after installing new software etc.)
95 """)
96 ip.magic('rehashx')
97 syscmds = db.get("syscmdlist")
98
99 # lowcase aliases on win32 only
100 if os.name == 'posix':
101 mapper = lambda s:s
102 else:
103 def mapper(s): return s.lower()
104
105 for cmd in syscmds:
106 # print "sys",cmd #dbg
107 noext, ext = os.path.splitext(cmd)
108 if ext.lower() == '.exe':
109 cmd = noext
110
111 key = mapper(cmd)
112 if key not in ip.alias_manager.alias_table:
113 # Dots will be removed from alias names, since ipython
114 # assumes names with dots to be python code
115
116 ip.define_alias(key.replace('.',''), cmd)
117
118 # win32 is crippled w/o cygwin, try to help it a little bit
119 if sys.platform == 'win32':
120 if 'cygwin' in os.environ['PATH'].lower():
121 # use the colors of cygwin ls (recommended)
122 ip.define_alias('d', 'ls -F --color=auto')
123 else:
124 # get icp, imv, imkdir, igrep, irm,...
125 ip.load('ipy_fsops')
126
127 # and the next best thing to real 'ls -F'
128 ip.define_alias('d','dir /w /og /on')
129
130 ip.set_hook('input_prefilter', slash_prefilter_f)
131 extend_shell_behavior(ip)
132
133 class LastArgFinder:
134 """ Allow $LA to work as "last argument of previous command", like $! in bash
135
136 To call this in normal IPython code, do LA()
137 """
138 def __call__(self, hist_idx = None):
139 ip = ipapi.get()
140 if hist_idx is None:
141 return str(self)
142 return ip.input_hist_raw[hist_idx].strip().split()[-1]
143 def __str__(self):
144 ip = ipapi.get()
145 for cmd in reversed(ip.input_hist_raw):
146 parts = cmd.strip().split()
147 if len(parts) < 2 or parts[-1] in ['$LA', 'LA()']:
148 continue
149 return parts[-1]
150 return ""
151
152 def slash_prefilter_f(self,line):
153 """ ./foo, ~/foo and /bin/foo now run foo as system command
154
155 Removes the need for doing !./foo, !~/foo or !/bin/foo
156 """
157 from IPython.utils import genutils
158 if re.match('(?:[.~]|/[a-zA-Z_0-9]+)/', line):
159 return "get_ipython().system(" + genutils.make_quoted_expr(line)+")"
160 raise TryNext
161
162 # XXX You do not need to understand the next function!
163 # This should probably be moved out of profile
164
165 def extend_shell_behavior(ip):
166
167 # Instead of making signature a global variable tie it to IPSHELL.
168 # In future if it is required to distinguish between different
169 # shells we can assign a signature per shell basis
170 ip.__sig__ = 0xa005
171 # mark the IPSHELL with this signature
172 ip.user_ns['__builtins__'].__dict__['__sig__'] = ip.__sig__
173
174 from IPython.external.Itpl import ItplNS
175 from IPython.utils.genutils import shell
176 # utility to expand user variables via Itpl
177 # xxx do something sensible with depth?
178 ip.var_expand = lambda cmd, lvars=None, depth=2: \
179 str(ItplNS(cmd, ip.user_ns, get_locals()))
180
181 def get_locals():
182 """ Substituting a variable through Itpl deep inside the IPSHELL stack
183 requires the knowledge of all the variables in scope upto the last
184 IPSHELL frame. This routine simply merges all the local variables
185 on the IPSHELL stack without worrying about their scope rules
186 """
187 import sys
188 # note lambda expression constitues a function call
189 # hence fno should be incremented by one
190 getsig = lambda fno: sys._getframe(fno+1).f_globals \
191 ['__builtins__'].__dict__['__sig__']
192 getlvars = lambda fno: sys._getframe(fno+1).f_locals
193 # trackback until we enter the IPSHELL
194 frame_no = 1
195 sig = ip.__sig__
196 fsig = ~sig
197 while fsig != sig :
198 try:
199 fsig = getsig(frame_no)
200 except (AttributeError, KeyError):
201 frame_no += 1
202 except ValueError:
203 # stack is depleted
204 # call did not originate from IPSHELL
205 return {}
206 first_frame = frame_no
207 # walk further back until we exit from IPSHELL or deplete stack
208 try:
209 while(sig == getsig(frame_no+1)):
210 frame_no += 1
211 except (AttributeError, KeyError, ValueError):
212 pass
213 # merge the locals from top down hence overriding
214 # any re-definitions of variables, functions etc.
215 lvars = {}
216 for fno in range(frame_no, first_frame-1, -1):
217 lvars.update(getlvars(fno))
218 #print '\n'*5, first_frame, frame_no, '\n', lvars, '\n'*5 #dbg
219 return lvars
220
221 def _runlines(lines):
222 """Run a string of one or more lines of source.
223
224 This method is capable of running a string containing multiple source
225 lines, as if they had been entered at the IPython prompt. Since it
226 exposes IPython's processing machinery, the given strings can contain
227 magic calls (%magic), special shell access (!cmd), etc."""
228
229 # We must start with a clean buffer, in case this is run from an
230 # interactive IPython session (via a magic, for example).
231 ip.resetbuffer()
232 lines = lines.split('\n')
233 more = 0
234 command = ''
235 for line in lines:
236 # skip blank lines so we don't mess up the prompt counter, but do
237 # NOT skip even a blank line if we are in a code block (more is
238 # true)
239 # if command is not empty trim the line
240 if command != '' :
241 line = line.strip()
242 # add the broken line to the command
243 if line and line[-1] == '\\' :
244 command += line[0:-1] + ' '
245 more = True
246 continue
247 else :
248 # add the last (current) line to the command
249 command += line
250 if command or more:
251 # push to raw history, so hist line numbers stay in sync
252 ip.input_hist_raw.append("# " + command + "\n")
253
254 more = ip.push_line(ip.prefilter(command,more))
255 command = ''
256 # IPython's runsource returns None if there was an error
257 # compiling the code. This allows us to stop processing right
258 # away, so the user gets the error message at the right place.
259 if more is None:
260 break
261 # final newline in case the input didn't have it, so that the code
262 # actually does get executed
263 if more:
264 ip.push_line('\n')
265
266 ip.runlines = _runlines
267
268 main()
@@ -1,324 +0,0 b''
1 # -*- coding: utf-8 -*-
2 """ An ipython profile for zope and plone.
3
4 Some ideas stolen from http://www.tomster.org.
5
6
7 Authors
8 -------
9 - Stefan Eletzhofer <stefan.eletzhofer@inquant.de>
10 """
11
12 # File: ipy_profile_zope.py
13 #
14 # Copyright (c) InQuant GmbH
15 #
16 #
17 # Distributed under the terms of the BSD License. The full license is in
18 # the file COPYING, distributed as part of this software.
19
20 from IPython.core import ipapi
21 from IPython.core import release
22 from types import StringType
23 import sys
24 import os
25 import textwrap
26
27 sys_oldstdin = sys.stdin
28
29 # The import below effectively obsoletes your old-style ipythonrc[.ini],
30 # so consider yourself warned!
31 # import ipy_defaults
32
33 _marker = []
34 def shasattr(obj, attr, acquire=False):
35 """ See Archetypes/utils.py
36 """
37 if not acquire:
38 obj = obj.aq_base
39 return getattr(obj, attr, _marker) is not _marker
40
41 class ZopeDebug(object):
42 def __init__(self):
43
44 self.instancehome = os.environ.get( "INSTANCE_HOME" )
45
46 configfile = os.environ.get( "CONFIG_FILE" )
47 if configfile is None and self.instancehome is not None:
48 configfile = os.path.join( self.instancehome, "etc", "zope.conf" )
49
50 if configfile is None:
51 raise RuntimeError( "CONFIG_FILE env not set" )
52
53 print "CONFIG_FILE=", configfile
54 print "INSTANCE_HOME=", self.instancehome
55
56 self.configfile = configfile
57
58 try:
59 from Zope2 import configure
60 except ImportError:
61 from Zope import configure
62
63 configure( configfile )
64
65 try:
66 import Zope2
67 app = Zope2.app()
68 except ImportError:
69 import Zope
70 app = Zope.app()
71
72 from Testing.makerequest import makerequest
73 self.app = makerequest( app )
74
75 try:
76 self._make_permissive()
77 print "Permissive security installed"
78 except:
79 print "Permissive security NOT installed"
80
81 self._pwd = self.portal or self.app
82
83 try:
84 from zope.component import getSiteManager
85 from zope.component import getGlobalSiteManager
86 from zope.app.component.hooks import setSite
87
88 if self.portal is not None:
89 setSite( self.portal )
90
91 gsm = getGlobalSiteManager()
92 sm = getSiteManager()
93
94 if sm is gsm:
95 print "ERROR SETTING SITE!"
96 except:
97 pass
98
99
100 @property
101 def utils(self):
102 class Utils(object):
103 commit = self.commit
104 sync = self.sync
105 objectInfo = self.objectInfo
106 ls = self.ls
107 pwd = self.pwd
108 cd = self.cd
109 su = self.su
110 getCatalogInfo = self.getCatalogInfo
111
112 @property
113 def cwd(self):
114 return self.pwd()
115
116 return Utils()
117
118 @property
119 def namespace(self):
120 return dict( utils=self.utils, app=self.app, portal=self.portal )
121
122 @property
123 def portal(self):
124 portals = self.app.objectValues( "Plone Site" )
125 if len(portals):
126 return portals[0]
127 else:
128 raise KeyError( "No Plone Site found.")
129
130 def pwd(self):
131 return self._pwd
132
133 def _make_permissive(self):
134 """
135 Make a permissive security manager with all rights. Hell,
136 we're developers, aren't we? Security is for whimps. :)
137 """
138 from Products.CMFCore.tests.base.security import PermissiveSecurityPolicy
139 import AccessControl
140 from AccessControl.SecurityManagement import newSecurityManager
141 from AccessControl.SecurityManager import setSecurityPolicy
142
143 _policy = PermissiveSecurityPolicy()
144 self.oldpolicy = setSecurityPolicy(_policy)
145 newSecurityManager(None, AccessControl.User.system)
146
147 def su(self, username):
148 """ Change to named user.
149 """
150 # TODO Make it easy to change back to permissive security.
151 user = self.portal.acl_users.getUser(username)
152 if not user:
153 print "Can't find %s in %s" % (username, self.portal.acl_users)
154 return
155
156 from AccessControl import ZopeSecurityPolicy
157 import AccessControl
158 from AccessControl.SecurityManagement import newSecurityManager, getSecurityManager
159 from AccessControl.SecurityManager import setSecurityPolicy
160
161 _policy = ZopeSecurityPolicy
162 self.oldpolicy = setSecurityPolicy(_policy)
163 wrapped_user = user.__of__(self.portal.acl_users)
164 newSecurityManager(None, user)
165 print 'User changed.'
166 return getSecurityManager().getUser()
167
168 def getCatalogInfo(self, obj=None, catalog='portal_catalog', query=None, sort_on='created', sort_order='reverse' ):
169 """ Inspect portal_catalog. Pass an object or object id for a
170 default query on that object, or pass an explicit query.
171 """
172 if obj and query:
173 print "Ignoring %s, using query." % obj
174
175 catalog = self.portal.get(catalog)
176 if not catalog:
177 return 'No catalog'
178
179 indexes = catalog._catalog.indexes
180 if not query:
181 if type(obj) is StringType:
182 cwd = self.pwd()
183 obj = cwd.unrestrictedTraverse( obj )
184 # If the default in the signature is mutable, its value will
185 # persist across invocations.
186 query = {}
187 if indexes.get('path'):
188 from string import join
189 path = join(obj.getPhysicalPath(), '/')
190 query.update({'path': path})
191 if indexes.get('getID'):
192 query.update({'getID': obj.id, })
193 if indexes.get('UID') and shasattr(obj, 'UID'):
194 query.update({'UID': obj.UID(), })
195 if indexes.get(sort_on):
196 query.update({'sort_on': sort_on, 'sort_order': sort_order})
197 if not query:
198 return 'Empty query'
199 results = catalog(**query)
200
201 result_info = []
202 for r in results:
203 rid = r.getRID()
204 if rid:
205 result_info.append(
206 {'path': catalog.getpath(rid),
207 'metadata': catalog.getMetadataForRID(rid),
208 'indexes': catalog.getIndexDataForRID(rid), }
209 )
210 else:
211 result_info.append({'missing': rid})
212
213 if len(result_info) == 1:
214 return result_info[0]
215 return result_info
216
217 def commit(self):
218 """
219 Commit the transaction.
220 """
221 try:
222 import transaction
223 transaction.get().commit()
224 except ImportError:
225 get_transaction().commit()
226
227 def sync(self):
228 """
229 Sync the app's view of the zodb.
230 """
231 self.app._p_jar.sync()
232
233 def objectInfo( self, o ):
234 """
235 Return a descriptive string of an object
236 """
237 Title = ""
238 t = getattr( o, 'Title', None )
239 if t:
240 Title = t()
241 return {'id': o.getId(),
242 'Title': Title,
243 'portal_type': getattr( o, 'portal_type', o.meta_type),
244 'folderish': o.isPrincipiaFolderish
245 }
246
247 def cd( self, path ):
248 """
249 Change current dir to a specific folder.
250
251 cd( ".." )
252 cd( "/plone/Members/admin" )
253 cd( portal.Members.admin )
254 etc.
255 """
256 if type(path) is not StringType:
257 path = '/'.join(path.getPhysicalPath())
258 cwd = self.pwd()
259 x = cwd.unrestrictedTraverse( path )
260 if x is None:
261 raise KeyError( "Can't cd to %s" % path )
262
263 print "%s -> %s" % ( self.pwd().getId(), x.getId() )
264 self._pwd = x
265
266 def ls( self, x=None ):
267 """
268 List object(s)
269 """
270 if type(x) is StringType:
271 cwd = self.pwd()
272 x = cwd.unrestrictedTraverse( x )
273 if x is None:
274 x = self.pwd()
275 if x.isPrincipiaFolderish:
276 return [self.objectInfo(o) for id, o in x.objectItems()]
277 else:
278 return self.objectInfo( x )
279
280 zope_debug = None
281
282 def ipy_set_trace():
283 from IPython.core import debugger
284 debugger.Pdb().set_trace()
285
286 def main():
287 global zope_debug
288 ip = ipapi.get()
289 o = ip.options
290 # autocall to "full" mode (smart mode is default, I like full mode)
291
292 SOFTWARE_HOME = os.environ.get( "SOFTWARE_HOME" )
293 sys.path.append( SOFTWARE_HOME )
294 print "SOFTWARE_HOME=%s\n" % SOFTWARE_HOME
295
296 zope_debug = ZopeDebug()
297
298 # <HACK ALERT>
299 import pdb;
300 pdb.set_trace = ipy_set_trace
301 # </HACK ALERT>
302
303 # I like my banner minimal.
304 o.banner = "ZOPE Py %s IPy %s\n" % (sys.version.split('\n')[0],release.version)
305
306 print textwrap.dedent("""\
307 ZOPE mode iPython shell.
308
309 Bound names:
310 app
311 portal
312 utils.{ %s }
313
314 Uses the $SOFTWARE_HOME and $CONFIG_FILE environment
315 variables.
316 """ % ( ",".join([ x for x in dir(zope_debug.utils) if not x.startswith("_") ] ) ) )
317
318
319 sys.stdin = sys_oldstdin
320 ip.user_ns.update( zope_debug.namespace )
321
322
323 main()
324 # vim: set ft=python ts=4 sw=4 expandtab :
@@ -1,219 +0,0 b''
1 """Traits-aware tab completion.
2
3 This module provides a custom tab-completer that intelligently hides the names
4 that the enthought.traits library (http://code.enthought.com/traits)
5 automatically adds to all objects that inherit from its base HasTraits class.
6
7
8 Activation
9 ==========
10
11 To use this, put in your ~/.ipython/ipy_user_conf.py file:
12
13 from ipy_traits_completer import activate
14 activate([complete_threshold])
15
16 The optional complete_threshold argument is the minimal length of text you need
17 to type for tab-completion to list names that are automatically generated by
18 traits. The default value is 3. Note that at runtime, you can change this
19 value simply by doing:
20
21 import ipy_traits_completer
22 ipy_traits_completer.COMPLETE_THRESHOLD = 4
23
24
25 Usage
26 =====
27
28 The system works as follows. If t is an empty object that HasTraits, then
29 (assuming the threshold is at the default value of 3):
30
31 In [7]: t.ed<TAB>
32
33 doesn't show anything at all, but:
34
35 In [7]: t.edi<TAB>
36 t.edit_traits t.editable_traits
37
38 shows these two names that come from traits. This allows you to complete on
39 the traits-specific names by typing at least 3 letters from them (or whatever
40 you set your threshold to), but to otherwise not see them in normal completion.
41
42
43 Notes
44 =====
45
46 - This requires Python 2.4 to work (I use sets). I don't think anyone is
47 using traits with 2.3 anyway, so that's OK.
48
49 - Imports from enthought.traits are deferred until an object with a class that
50 looks like it subclasses from HasTraits comes along. This test is done by
51 looking at the name of the class and its superclasses.
52 """
53
54 #############################################################################
55 # IPython imports
56 from IPython.core.error import TryNext
57 from IPython.core.ipapi import get as ipget
58 from IPython.utils.dir2 import dir2
59 try:
60 set
61 except:
62 from sets import Set as set
63
64 #############################################################################
65 # Module constants
66
67 # The completion threshold
68 # This is currently implemented as a module global, since this sytem isn't
69 # likely to be modified at runtime by multiple instances. If needed in the
70 # future, we can always make it local to the completer as a function attribute.
71 COMPLETE_THRESHOLD = 3
72
73 # Set of names that Traits automatically adds to ANY traits-inheriting object.
74 # These are the names we'll filter out.
75 TRAIT_NAMES = None
76 def get_trait_names():
77 global TRAIT_NAMES
78 from enthought.traits.api import HasTraits
79 if TRAIT_NAMES is None:
80 TRAIT_NAMES = set( dir2(HasTraits()) ) - set( dir2(object()) )
81 else:
82 return TRAIT_NAMES
83
84 #############################################################################
85 # Code begins
86
87 def looks_like_isinstance(obj, classname):
88 """ Return True if the object has a class or superclass with the given class
89 name.
90
91 Ignores old-style classes.
92 """
93 from types import InstanceType
94
95 t = type(obj)
96 if t is InstanceType:
97 # Old-style classes.
98 return False
99 elif t.__name__ == classname:
100 return True
101 for klass in t.__mro__:
102 if klass.__name__ == classname:
103 return True
104 return False
105
106 def trait_completer(self,event):
107 """A custom IPython tab-completer that is traits-aware.
108
109 It tries to hide the internal traits attributes, and reveal them only when
110 it can reasonably guess that the user really is after one of them.
111 """
112
113 #print '\nevent is:',event # dbg
114 symbol_parts = event.symbol.split('.')
115 base = '.'.join(symbol_parts[:-1])
116 #print 'base:',base # dbg
117
118 oinfo = self._ofind(base)
119 if not oinfo['found']:
120 raise TryNext
121
122 obj = oinfo['obj']
123 # OK, we got the object. See if it's traits, else punt
124 if not looks_like_isinstance(obj, 'HasTraits'):
125 raise TryNext
126
127 # Defer import until here so as not to require Traits until we get something
128 # that looks like it might be a HasTraits instance.
129 from enthought.traits.api import HasTraits
130 if not isinstance(obj, HasTraits):
131 raise TryNext
132
133 # it's a traits object, don't show the tr* attributes unless the completion
134 # begins with 'tr'
135 attrs = dir2(obj)
136 # Now, filter out the attributes that start with the user's request
137 attr_start = symbol_parts[-1]
138 if attr_start:
139 attrs = [a for a in attrs if a.startswith(attr_start)]
140
141 # Let's also respect the user's readline_omit__names setting:
142 omit__names = ipget().options.readline_omit__names
143 if omit__names == 1:
144 attrs = [a for a in attrs if not a.startswith('__')]
145 elif omit__names == 2:
146 attrs = [a for a in attrs if not a.startswith('_')]
147
148 #print '\nastart:<%r>' % attr_start # dbg
149
150 if len(attr_start)<COMPLETE_THRESHOLD:
151 attrs = list(set(attrs) - get_trait_names())
152
153 # The base of the completion, so we can form the final results list
154 bdot = base+'.'
155
156 tcomp = [bdot+a for a in attrs]
157 #print 'tcomp:',tcomp
158 return tcomp
159
160 def activate(complete_threshold = COMPLETE_THRESHOLD):
161 """Activate the Traits completer.
162
163 :Keywords:
164 complete_threshold : int
165 The minimum number of letters that a user must type in order to
166 activate completion of traits-private names."""
167
168 if not (isinstance(complete_threshold,int) and
169 complete_threshold>0):
170 e='complete_threshold must be a positive integer, not %r' % \
171 complete_threshold
172 raise ValueError(e)
173
174 # Set the module global
175 global COMPLETE_THRESHOLD
176 COMPLETE_THRESHOLD = complete_threshold
177
178 # Activate the traits aware completer
179 ip = ipget()
180 ip.set_hook('complete_command', trait_completer, re_key = '.*')
181
182
183 #############################################################################
184 if __name__ == '__main__':
185 # Testing/debugging
186 from enthought.traits.api import HasTraits
187
188 # A sorted list of the names we'll filter out
189 TNL = list(get_trait_names())
190 TNL.sort()
191
192 # Make a few objects for testing
193 class TClean(HasTraits): pass
194 class Bunch(object): pass
195 # A clean traits object
196 t = TClean()
197 # A nested object containing t
198 f = Bunch()
199 f.t = t
200 # And a naked new-style object
201 o = object()
202
203 ip = ipget().IP
204
205 # A few simplistic tests
206
207 # Reset the threshold to the default, in case the test is running inside an
208 # instance of ipython that changed it
209 import ipy_traits_completer
210 ipy_traits_completer.COMPLETE_THRESHOLD = 3
211
212 assert ip.complete('t.ed') ==[]
213
214 # For some bizarre reason, these fail on the first time I run them, but not
215 # afterwards. Traits does some really weird stuff at object instantiation
216 # time...
217 ta = ip.complete('t.edi')
218 assert ta == ['t.edit_traits', 't.editable_traits']
219 print 'Tests OK'
@@ -1,245 +0,0 b''
1 """ Integration with gvim, by Erich Heine
2
3 Provides a %vim magic command, and reuses the same vim session. Uses
4 unix domain sockets for communication between vim and IPython. ipy.vim is
5 available in doc/examples of the IPython distribution.
6
7 Slightly touched up email announcement (and description how to use it) by
8 Erich Heine is here:
9
10 Ive recently been playing with ipython, and like it quite a bit. I did
11 however discover a bit of frustration, namely with editor interaction.
12 I am a gvim user, and using the command edit on a new file causes
13 ipython to try and run that file as soon as the text editor opens
14 up. The -x command of course fixes this, but its still a bit annoying,
15 switching windows to do a run file, then back to the text
16 editor. Being a heavy tab user in gvim, another annoyance is not being
17 able to specify weather a new tab is how I choose to open the file.
18
19 Not being one to shirk my open source duties (and seeing this as a
20 good excuse to poke around ipython internals), Ive created a script
21 for having gvim and ipython work very nicely together. Ive attached
22 both to this email (hoping of course that the mailing list allows such
23 things).
24
25 There are 2 files:
26
27 ipy_vimserver.py -- this file contains the ipython stuff
28 ipy.vim -- this file contains the gvim stuff
29
30 In combination they allow for a few functionalities:
31
32 #1. the vim magic command. This is a fancy wrapper around the edit
33 magic, that allows for a new option, -t, which opens the text in a new
34 gvim tab. Otherwise it works the same as edit -x. (it internally
35 calls edit -x). This magic command also juggles vim server management,
36 so when it is called when there is not a gvim running, it creates a
37 new gvim instance, named after the ipython session name. Once such a
38 gvim instance is running, it will be used for subsequent uses of the
39 vim command.
40
41 #2. ipython - gvim interaction. Once a file has been opened with the
42 vim magic (and a session set up, see below), pressing the F5 key in
43 vim will cause the calling ipython instance to execute run
44 filename.py. (if you typo like I do, this is very useful)
45
46 #3. ipython server - this is a thread wich listens on a unix domain
47 socket, and runs commands sent to that socket.
48
49 Note, this only works on POSIX systems, that allow for AF_UNIX type
50 sockets. It has only been tested on linux (a fairly recent debian
51 testing distro).
52
53 To install it put, the ipserver.py in your favorite locaion for
54 sourcing ipython scripts. I put the ipy.vim in
55 ~/.vim/after/ftplugin/python/.
56
57 To use (this can be scripted im sure, but i usually have 2 or 3
58 ipythons and corresponding gvims open):
59
60 import ipy_vimserver
61 ipy_vimserver.setup('sessionname')
62
63 (Editors note - you can probably add these to your ipy_user_conf.py)
64
65 Then use ipython as you normally would, until you need to edit
66 something. Instead of edit, use the vim magic. Thats it!
67
68 """
69
70 from IPython.core import ipapi
71 from IPython.core.error import TryNext
72
73 import socket, select
74 import os, threading, subprocess
75 import re
76
77 try:
78 ERRCONDS = select.POLLHUP|select.POLLERR
79 except AttributeError:
80 raise ImportError("Vim server not supported on this platform - select "
81 "missing necessary POLLHUP/POLLERR functionality")
82
83 SERVER = None
84 ip = ipapi.get()
85
86 # this listens to a unix domain socket in a separate thread, so that comms
87 # between a vim instance and ipython can happen in a fun and productive way
88 class IpyServer(threading.Thread):
89 def __init__(self, sname):
90 super(IpyServer, self).__init__()
91 self.keep_running = True
92 self.__sname = sname
93 self.socket = socket.socket(socket.AF_UNIX)
94 self.poller = select.poll()
95 self.current_conns = dict()
96 self.setDaemon(True)
97
98 def listen(self):
99 self.socket.bind(self.__sname)
100 self.socket.listen(1)
101
102 def __handle_error(self, socket):
103 if socket == self.socket.fileno():
104 self.keep_running = False
105 for a in self.current_conns.values():
106 a.close()
107 return False
108 else:
109 y = self.current_conns[socket]
110 del self.current_conns[socket]
111 y.close()
112 self.poller.unregister(socket)
113
114 def serve_me(self):
115 self.listen()
116 self.poller.register(self.socket,select.POLLIN|ERRCONDS)
117
118 while self.keep_running:
119 try:
120 avail = self.poller.poll(1)
121 except:
122 continue
123
124 if not avail: continue
125
126 for sock, conds in avail:
127 if conds & (ERRCONDS):
128 if self.__handle_error(sock): continue
129 else: break
130
131 if sock == self.socket.fileno():
132 y = self.socket.accept()[0]
133 self.poller.register(y, select.POLLIN|ERRCONDS)
134 self.current_conns[y.fileno()] = y
135 else: y = self.current_conns.get(sock)
136
137 self.handle_request(y)
138
139 os.remove(self.__sname)
140
141 run = serve_me
142
143 def stop(self):
144 self.keep_running = False
145
146 def handle_request(self,sock):
147 sock.settimeout(1)
148 while self.keep_running:
149 try:
150 x = sock.recv(4096)
151 except socket.timeout:
152 pass
153 else:
154 break
155 self.do_it(x)
156
157 def do_it(self, data):
158 data = data.split('\n')
159 cmds = list()
160 for line in data:
161 cmds.append(line)
162 ip.runlines(cmds)
163
164
165 # try to help ensure that the unix domain socket is cleaned up proper
166 def shutdown_server(self):
167 if SERVER:
168 SERVER.stop()
169 SERVER.join(3)
170 raise TryNext
171
172 ip.set_hook('shutdown_hook', shutdown_server, 10)
173
174 # this fun function exists to make setup easier for all, and makes the
175 # vimhook function ready for instance specific communication
176 def setup(sessionname='',socketdir=os.path.expanduser('~/.ipython/')):
177 global SERVER
178
179 if sessionname:
180 session = sessionname
181 elif os.environ.get('IPY_SESSION'):
182 session = os.environ.get('IPY_SESSION')
183 else:
184 session = 'IPYS'
185 vimhook.vimserver=session
186 vimhook.ipyserver = os.path.join(socketdir, session)
187 if not SERVER:
188 SERVER = IpyServer(vimhook.ipyserver)
189 SERVER.start()
190
191
192
193 # calls gvim, with all ops happening on the correct gvim instance for this
194 # ipython instance. it then calls edit -x (since gvim will return right away)
195 # things of note: it sets up a special environment, so that the ipy.vim script
196 # can connect back to the ipython instance and do fun things, like run the file
197 def vimhook(self, fname, line):
198 env = os.environ.copy()
199 vserver = vimhook.vimserver.upper()
200 check = subprocess.Popen('gvim --serverlist', stdout = subprocess.PIPE,
201 shell=True)
202 check.wait()
203 cval = [l for l in check.stdout.readlines() if vserver in l]
204
205 if cval:
206 vimargs = '--remote%s' % (vimhook.extras,)
207 else:
208 vimargs = ''
209 vimhook.extras = ''
210
211 env['IPY_SESSION'] = vimhook.vimserver
212 env['IPY_SERVER'] = vimhook.ipyserver
213
214 if line is None: line = ''
215 else: line = '+' + line
216 vim_cmd = 'gvim --servername %s %s %s %s' % (vimhook.vimserver, vimargs,
217 line, fname)
218 subprocess.call(vim_cmd, env=env, shell=True)
219
220
221 #default values to keep it sane...
222 vimhook.vimserver = ''
223 vimhook.ipyserver = ''
224
225 ip.set_hook('editor',vimhook)
226
227 # this is set up so more vim specific commands can be added, instead of just
228 # the current -t. all thats required is a compiled regex, a call to do_arg(pat)
229 # and the logic to deal with the new feature
230 newtab = re.compile(r'-t(?:\s|$)')
231 def vim(self, argstr):
232 def do_arg(pat, rarg):
233 x = len(pat.findall(argstr))
234 if x:
235 a = pat.sub('',argstr)
236 return rarg, a
237 else: return '', argstr
238
239 t, argstr = do_arg(newtab, '-tab')
240 vimhook.extras = t
241 argstr = 'edit -x ' + argstr
242 ip.magic(argstr)
243
244 ip.define_magic('vim', vim)
245
@@ -1,11 +0,0 b''
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3 """wxIPython -- An enhanced Interactive Python GUI fonrtend
4 This script starts the Wx graphical frontend.
5 This is experimental so far.
6 Currently it support only ipython0 instance. Will be upgraded to ipython1 one.
7 """
8
9 from IPython.gui.wx import wxIPython
10
11 wxIPython.main()
@@ -1,11 +0,0 b''
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3 """IPythonX -- An enhanced Interactive Python
4
5 This script starts the Wx graphical frontend. This is experimental so
6 far.
7 """
8
9 from IPython.frontend.wx import ipythonx
10
11 ipythonx.main()
@@ -1,43 +0,0 b''
1 # -*- coding: utf-8 -*-
2 """
3 Extension for printing Numeric Arrays in flexible ways.
4 """
5
6 from Numeric import ArrayType
7
8 def num_display(self,arg):
9 """Display method for printing which treats Numeric arrays specially.
10 """
11
12 # Non-numpy variables are printed using the system default
13 if type(arg) != ArrayType:
14 self._display(arg)
15 return
16 # Otherwise, we do work.
17 format = __IPYTHON__.runtime_rc.numarray_print_format
18 print 'NumPy array, format:',format
19 # Here is where all the printing logic needs to be implemented
20 print arg # nothing yet :)
21
22
23 def magic_format(self,parameter_s=''):
24 """Specifies format of numerical output.
25
26 This command is similar to Ocave's format command.
27 """
28
29 valid_formats = ['long','short']
30
31 if parameter_s in valid_formats:
32 self.runtime_rc.numarray_print_format = parameter_s
33 print 'Numeric output format is now:',parameter_s
34 else:
35 print 'Invalid format:',parameter_s
36 print 'Valid formats:',valid_formats
37
38 # setup default format
39 __IPYTHON__.runtime_rc.numarray_print_format = 'long'
40
41 # Bind our new functions to the interpreter
42 __IPYTHON__.__class__.magic_format = magic_format
43 __IPYTHON__.hooks.display = num_display
@@ -1,302 +0,0 b''
1 # -*- coding: utf-8 -*-
2 """
3 A set of convenient utilities for numerical work.
4
5 Most of this module requires Numerical Python or is meant to be used with it.
6 See http://www.pfdubois.com/numpy for details.
7 """
8
9 #*****************************************************************************
10 # Copyright (C) 2001-2005 Fernando Perez <fperez@colorado.edu>
11 #
12 # Distributed under the terms of the BSD License. The full license is in
13 # the file COPYING, distributed as part of this software.
14 #*****************************************************************************
15
16 __all__ = ['sum_flat','mean_flat','rms_flat','base_repr','binary_repr',
17 'amin','amax','amap','zeros_like','empty_like',
18 'frange','diagonal_matrix','identity',
19 'fromfunction_kw','log2','ispower2',
20 'norm','l1norm','l2norm','exp_safe',
21 'inf','infty','Infinity',
22 'Numeric']
23
24 #****************************************************************************
25 # required modules
26 import __main__
27 import math
28 import operator
29 import sys
30
31 import Numeric
32 from Numeric import *
33
34 #*****************************************************************************
35 # Globals
36
37 # useful for testing infinities in results of array divisions (which don't
38 # raise an exception)
39 # Python, LaTeX and Mathematica names.
40 inf = infty = Infinity = (array([1])/0.0)[0]
41
42 #****************************************************************************
43 # function definitions
44 exp_safe_MIN = math.log(2.2250738585072014e-308)
45 exp_safe_MAX = 1.7976931348623157e+308
46
47 def exp_safe(x):
48 """Compute exponentials which safely underflow to zero.
49
50 Slow but convenient to use. Note that NumArray will introduce proper
51 floating point exception handling with access to the underlying
52 hardware."""
53
54 if type(x) is ArrayType:
55 return exp(clip(x,exp_safe_MIN,exp_safe_MAX))
56 else:
57 return math.exp(x)
58
59 def amap(fn,*args):
60 """amap(function, sequence[, sequence, ...]) -> array.
61
62 Works like map(), but it returns an array. This is just a convenient
63 shorthand for Numeric.array(map(...))"""
64 return array(map(fn,*args))
65
66 def amin(m,axis=0):
67 """amin(m,axis=0) returns the minimum of m along dimension axis.
68 """
69 return minimum.reduce(asarray(m),axis)
70
71 def amax(m,axis=0):
72 """amax(m,axis=0) returns the maximum of m along dimension axis.
73 """
74 return maximum.reduce(asarray(m),axis)
75
76 def zeros_like(a):
77 """Return an array of zeros of the shape and typecode of a.
78
79 If you don't explicitly need the array to be zeroed, you should instead
80 use empty_like(), which is faster as it only allocates memory."""
81
82 return zeros(a.shape,a.typecode())
83
84 def empty_like(a):
85 """Return an empty (uninitialized) array of the shape and typecode of a.
86
87 Note that this does NOT initialize the returned array. If you require
88 your array to be initialized, you should use zeros_like().
89
90 This requires Numeric.empty(), which appeared in Numeric 23.7."""
91
92 return empty(a.shape,a.typecode())
93
94 def sum_flat(a):
95 """Return the sum of all the elements of a, flattened out.
96
97 It uses a.flat, and if a is not contiguous, a call to ravel(a) is made."""
98
99 if a.iscontiguous():
100 return Numeric.sum(a.flat)
101 else:
102 return Numeric.sum(ravel(a))
103
104 def mean_flat(a):
105 """Return the mean of all the elements of a, flattened out."""
106
107 return sum_flat(a)/float(size(a))
108
109 def rms_flat(a):
110 """Return the root mean square of all the elements of a, flattened out."""
111
112 return math.sqrt(sum_flat(absolute(a)**2)/float(size(a)))
113
114 def l1norm(a):
115 """Return the l1 norm of a, flattened out.
116
117 Implemented as a separate function (not a call to norm() for speed).
118
119 Ref: http://mathworld.wolfram.com/L1-Norm.html"""
120
121 return sum_flat(absolute(a))
122
123 def l2norm(a):
124 """Return the l2 norm of a, flattened out.
125
126 Implemented as a separate function (not a call to norm() for speed).
127
128 Ref: http://mathworld.wolfram.com/L2-Norm.html"""
129
130 return math.sqrt(sum_flat(absolute(a)**2))
131
132 def norm(a,p=2):
133 """norm(a,p=2) -> l-p norm of a.flat
134
135 Return the l-p norm of a, considered as a flat array. This is NOT a true
136 matrix norm, since arrays of arbitrary rank are always flattened.
137
138 p can be a number or one of the strings ('inf','Infinity') to get the
139 L-infinity norm.
140
141 Ref: http://mathworld.wolfram.com/VectorNorm.html
142 http://mathworld.wolfram.com/L-Infinity-Norm.html"""
143
144 if p in ('inf','Infinity'):
145 return max(absolute(a).flat)
146 else:
147 return (sum_flat(absolute(a)**p))**(1.0/p)
148
149 def frange(xini,xfin=None,delta=None,**kw):
150 """frange([start,] stop[, step, keywords]) -> array of floats
151
152 Return a Numeric array() containing a progression of floats. Similar to
153 arange(), but defaults to a closed interval.
154
155 frange(x0, x1) returns [x0, x0+1, x0+2, ..., x1]; start defaults to 0, and
156 the endpoint *is included*. This behavior is different from that of
157 range() and arange(). This is deliberate, since frange will probably be
158 more useful for generating lists of points for function evaluation, and
159 endpoints are often desired in this use. The usual behavior of range() can
160 be obtained by setting the keyword 'closed=0', in this case frange()
161 basically becomes arange().
162
163 When step is given, it specifies the increment (or decrement). All
164 arguments can be floating point numbers.
165
166 frange(x0,x1,d) returns [x0,x0+d,x0+2d,...,xfin] where xfin<=x1.
167
168 frange can also be called with the keyword 'npts'. This sets the number of
169 points the list should contain (and overrides the value 'step' might have
170 been given). arange() doesn't offer this option.
171
172 Examples:
173 >>> frange(3)
174 array([ 0., 1., 2., 3.])
175 >>> frange(3,closed=0)
176 array([ 0., 1., 2.])
177 >>> frange(1,6,2)
178 array([1, 3, 5])
179 >>> frange(1,6.5,npts=5)
180 array([ 1. , 2.375, 3.75 , 5.125, 6.5 ])
181 """
182
183 #defaults
184 kw.setdefault('closed',1)
185 endpoint = kw['closed'] != 0
186
187 # funny logic to allow the *first* argument to be optional (like range())
188 # This was modified with a simpler version from a similar frange() found
189 # at http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/66472
190 if xfin == None:
191 xfin = xini + 0.0
192 xini = 0.0
193
194 if delta == None:
195 delta = 1.0
196
197 # compute # of points, spacing and return final list
198 try:
199 npts=kw['npts']
200 delta=(xfin-xini)/float(npts-endpoint)
201 except KeyError:
202 # round() gets npts right even with the vagaries of floating point.
203 npts=int(round((xfin-xini)/delta+endpoint))
204
205 return arange(npts)*delta+xini
206
207 def diagonal_matrix(diag):
208 """Return square diagonal matrix whose non-zero elements are given by the
209 input array."""
210
211 return diag*identity(len(diag))
212
213 def identity(n,rank=2,typecode='l'):
214 """identity(n,r) returns the identity matrix of shape (n,n,...,n) (rank r).
215
216 For ranks higher than 2, this object is simply a multi-index Kronecker
217 delta:
218 / 1 if i0=i1=...=iR,
219 id[i0,i1,...,iR] = -|
220 \ 0 otherwise.
221
222 Optionally a typecode may be given (it defaults to 'l').
223
224 Since rank defaults to 2, this function behaves in the default case (when
225 only n is given) like the Numeric identity function."""
226
227 iden = zeros((n,)*rank,typecode=typecode)
228 for i in range(n):
229 idx = (i,)*rank
230 iden[idx] = 1
231 return iden
232
233 def base_repr (number, base = 2, padding = 0):
234 """Return the representation of a number in any given base."""
235 chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'
236 if number < base: \
237 return (padding - 1) * chars [0] + chars [int (number)]
238 max_exponent = int (math.log (number)/math.log (base))
239 max_power = long (base) ** max_exponent
240 lead_digit = int (number/max_power)
241 return chars [lead_digit] + \
242 base_repr (number - max_power * lead_digit, base, \
243 max (padding - 1, max_exponent))
244
245 def binary_repr(number, max_length = 1025):
246 """Return the binary representation of the input number as a string.
247
248 This is more efficient than using base_repr with base 2.
249
250 Increase the value of max_length for very large numbers. Note that on
251 32-bit machines, 2**1023 is the largest integer power of 2 which can be
252 converted to a Python float."""
253
254 assert number < 2L << max_length
255 shifts = map (operator.rshift, max_length * [number], \
256 range (max_length - 1, -1, -1))
257 digits = map (operator.mod, shifts, max_length * [2])
258 if not digits.count (1): return 0
259 digits = digits [digits.index (1):]
260 return ''.join (map (repr, digits)).replace('L','')
261
262 def log2(x,ln2 = math.log(2.0)):
263 """Return the log(x) in base 2.
264
265 This is a _slow_ function but which is guaranteed to return the correct
266 integer value if the input is an ineger exact power of 2."""
267
268 try:
269 bin_n = binary_repr(x)[1:]
270 except (AssertionError,TypeError):
271 return math.log(x)/ln2
272 else:
273 if '1' in bin_n:
274 return math.log(x)/ln2
275 else:
276 return len(bin_n)
277
278 def ispower2(n):
279 """Returns the log base 2 of n if n is a power of 2, zero otherwise.
280
281 Note the potential ambiguity if n==1: 2**0==1, interpret accordingly."""
282
283 bin_n = binary_repr(n)[1:]
284 if '1' in bin_n:
285 return 0
286 else:
287 return len(bin_n)
288
289 def fromfunction_kw(function, dimensions, **kwargs):
290 """Drop-in replacement for fromfunction() from Numerical Python.
291
292 Allows passing keyword arguments to the desired function.
293
294 Call it as (keywords are optional):
295 fromfunction_kw(MyFunction, dimensions, keywords)
296
297 The function MyFunction() is responsible for handling the dictionary of
298 keywords it will recieve."""
299
300 return function(tuple(indices(dimensions)),**kwargs)
301
302 #**************************** end file <numutils.py> ************************
@@ -1,82 +0,0 b''
1 """
2 Base front end class for all async frontends.
3 """
4 __docformat__ = "restructuredtext en"
5
6 # Tell nose to skip this module
7 __test__ = {}
8
9 #-------------------------------------------------------------------------------
10 # Copyright (C) 2008-2011 The IPython Development Team
11 #
12 # Distributed under the terms of the BSD License. The full license is in
13 # the file COPYING, distributed as part of this software.
14 #-------------------------------------------------------------------------------
15
16 #-------------------------------------------------------------------------------
17 # Imports
18 #-------------------------------------------------------------------------------
19
20 # Third-party
21 from twisted.python.failure import Failure
22 from zope.interface import implements, classProvides
23
24 # From IPython
25 from IPython.frontend.frontendbase import (FrontEndBase, IFrontEnd,
26 IFrontEndFactory)
27 from IPython.kernel.core.history import FrontEndHistory
28 from IPython.kernel.engineservice import IEngineCore
29
30 import uuid
31
32 #-----------------------------------------------------------------------------
33 # Classes and functions
34 #-----------------------------------------------------------------------------
35
36 class AsyncFrontEndBase(FrontEndBase):
37 """
38 Overrides FrontEndBase to wrap execute in a deferred result.
39 All callbacks are made as callbacks on the deferred result.
40 """
41
42 implements(IFrontEnd)
43 classProvides(IFrontEndFactory)
44
45 def __init__(self, engine=None, history=None):
46 assert(engine==None or IEngineCore.providedBy(engine))
47 self.engine = IEngineCore(engine)
48 if history is None:
49 self.history = FrontEndHistory(input_cache=[''])
50 else:
51 self.history = history
52
53 def execute(self, block, blockID=None):
54 """Execute the block and return the deferred result.
55
56 Parameters:
57 block : {str, AST}
58 blockID : any
59 Caller may provide an ID to identify this block.
60 result['blockID'] := blockID
61
62 Result:
63 Deferred result of self.interpreter.execute
64 """
65
66 if(not self.is_complete(block)):
67 return Failure(Exception("Block is not compilable"))
68
69 if(blockID == None):
70 blockID = uuid.uuid4()
71
72 d = self.engine.execute(block)
73 d.addCallback(self._add_history, block=block)
74 d.addCallbacks(self._add_block_id_for_result,
75 errback=self._add_block_id_for_failure,
76 callbackArgs=(blockID,),
77 errbackArgs=(blockID,))
78 d.addBoth(self.update_cell_prompt, blockID=blockID)
79 d.addCallbacks(self.render_result,
80 errback=self.render_error)
81
82 return d
This diff has been collapsed as it changes many lines, (560 lines changed) Show them Hide them
@@ -1,560 +0,0 b''
1 # encoding: utf-8
2 # -*- test-case-name: IPython.frontend.cocoa.tests.test_cocoa_frontend -*-
3
4 """PyObjC classes to provide a Cocoa frontend to the
5 IPython.kernel.engineservice.IEngineBase.
6
7 To add an IPython interpreter to a cocoa app, instantiate an
8 IPythonCocoaController in a XIB and connect its textView outlet to an
9 NSTextView instance in your UI. That's it.
10
11 Author: Barry Wark
12 """
13
14 __docformat__ = "restructuredtext en"
15
16 #-----------------------------------------------------------------------------
17 # Copyright (C) 2008-2011 The IPython Development Team
18 #
19 # Distributed under the terms of the BSD License. The full license is in
20 # the file COPYING, distributed as part of this software.
21 #-----------------------------------------------------------------------------
22
23 #-----------------------------------------------------------------------------
24 # Imports
25 #-----------------------------------------------------------------------------
26
27 import sys
28 import objc
29 import uuid
30
31 from Foundation import NSObject, NSMutableArray, NSMutableDictionary,\
32 NSLog, NSNotificationCenter, NSMakeRange,\
33 NSLocalizedString, NSIntersectionRange,\
34 NSString, NSAutoreleasePool
35
36 from AppKit import NSApplicationWillTerminateNotification, NSBeep,\
37 NSTextView, NSRulerView, NSVerticalRuler
38
39 from pprint import saferepr
40
41 import IPython
42 from IPython.kernel.engineservice import ThreadedEngineService
43 from IPython.frontend.asyncfrontendbase import AsyncFrontEndBase
44
45 from twisted.internet.threads import blockingCallFromThread
46 from twisted.python.failure import Failure
47
48 #-----------------------------------------------------------------------------
49 # Classes to implement the Cocoa frontend
50 #-----------------------------------------------------------------------------
51
52 # TODO:
53 # 1. use MultiEngineClient and out-of-process engine rather than
54 # ThreadedEngineService?
55 # 2. integrate Xgrid launching of engines
56
57 class AutoreleasePoolWrappedThreadedEngineService(ThreadedEngineService):
58 """Wrap all blocks in an NSAutoreleasePool"""
59
60 def wrapped_execute(self, msg, lines):
61 """wrapped_execute"""
62 try:
63 p = NSAutoreleasePool.alloc().init()
64 result = super(AutoreleasePoolWrappedThreadedEngineService,
65 self).wrapped_execute(msg, lines)
66 finally:
67 p.drain()
68
69 return result
70
71
72
73 class Cell(NSObject):
74 """
75 Representation of the prompts, input and output of a cell in the
76 frontend
77 """
78
79 blockNumber = objc.ivar().unsigned_long()
80 blockID = objc.ivar()
81 inputBlock = objc.ivar()
82 output = objc.ivar()
83
84
85
86 class CellBlock(object):
87 """
88 Storage for information about text ranges relating to a single cell
89 """
90
91
92 def __init__(self, inputPromptRange, inputRange=None, outputPromptRange=None,
93 outputRange=None):
94 super(CellBlock, self).__init__()
95 self.inputPromptRange = inputPromptRange
96 self.inputRange = inputRange
97 self.outputPromptRange = outputPromptRange
98 self.outputRange = outputRange
99
100 def update_ranges_for_insertion(self, text, textRange):
101 """Update ranges for text insertion at textRange"""
102
103 for r in [self.inputPromptRange,self.inputRange,
104 self.outputPromptRange, self.outputRange]:
105 if(r == None):
106 continue
107 intersection = NSIntersectionRange(r,textRange)
108 if(intersection.length == 0): #ranges don't intersect
109 if r.location >= textRange.location:
110 r.location += len(text)
111 else: #ranges intersect
112 if(r.location > textRange.location):
113 offset = len(text) - intersection.length
114 r.length -= offset
115 r.location += offset
116 elif(r.location == textRange.location):
117 r.length += len(text) - intersection.length
118 else:
119 r.length -= intersection.length
120
121
122 def update_ranges_for_deletion(self, textRange):
123 """Update ranges for text deletion at textRange"""
124
125 for r in [self.inputPromptRange,self.inputRange,
126 self.outputPromptRange, self.outputRange]:
127 if(r==None):
128 continue
129 intersection = NSIntersectionRange(r, textRange)
130 if(intersection.length == 0): #ranges don't intersect
131 if r.location >= textRange.location:
132 r.location -= textRange.length
133 else: #ranges intersect
134 if(r.location > textRange.location):
135 offset = intersection.length
136 r.length -= offset
137 r.location += offset
138 elif(r.location == textRange.location):
139 r.length += intersection.length
140 else:
141 r.length -= intersection.length
142
143 def __repr__(self):
144 return 'CellBlock('+ str((self.inputPromptRange,
145 self.inputRange,
146 self.outputPromptRange,
147 self.outputRange)) + ')'
148
149
150
151
152 class IPythonCocoaController(NSObject, AsyncFrontEndBase):
153 userNS = objc.ivar() #mirror of engine.user_ns (key=>str(value))
154 waitingForEngine = objc.ivar().bool()
155 textView = objc.IBOutlet()
156
157 def init(self):
158 self = super(IPythonCocoaController, self).init()
159 AsyncFrontEndBase.__init__(self,
160 engine=AutoreleasePoolWrappedThreadedEngineService())
161 if(self != None):
162 self._common_init()
163
164 return self
165
166 def _common_init(self):
167 """_common_init"""
168
169 self.userNS = NSMutableDictionary.dictionary()
170 self.waitingForEngine = False
171
172 self.lines = {}
173 self.tabSpaces = 4
174 self.tabUsesSpaces = True
175 self.currentBlockID = self.next_block_ID()
176 self.blockRanges = {} # blockID=>CellBlock
177
178
179 def awakeFromNib(self):
180 """awakeFromNib"""
181
182 self._common_init()
183
184 # Start the IPython engine
185 self.engine.startService()
186 NSLog('IPython engine started')
187
188 # Register for app termination
189 nc = NSNotificationCenter.defaultCenter()
190 nc.addObserver_selector_name_object_(
191 self,
192 'appWillTerminate:',
193 NSApplicationWillTerminateNotification,
194 None)
195
196 self.textView.setDelegate_(self)
197 self.textView.enclosingScrollView().setHasVerticalRuler_(True)
198 r = NSRulerView.alloc().initWithScrollView_orientation_(
199 self.textView.enclosingScrollView(),
200 NSVerticalRuler)
201 self.verticalRulerView = r
202 self.verticalRulerView.setClientView_(self.textView)
203 self._start_cli_banner()
204 self.start_new_block()
205
206
207 def appWillTerminate_(self, notification):
208 """appWillTerminate"""
209
210 self.engine.stopService()
211
212
213 def complete(self, token):
214 """Complete token in engine's user_ns
215
216 Parameters
217 ----------
218 token : string
219
220 Result
221 ------
222 Deferred result of
223 IPython.kernel.engineservice.IEngineBase.complete
224 """
225
226 return self.engine.complete(token)
227
228
229 def execute(self, block, blockID=None):
230 self.waitingForEngine = True
231 self.willChangeValueForKey_('commandHistory')
232 d = super(IPythonCocoaController, self).execute(block,
233 blockID)
234 d.addBoth(self._engine_done)
235 d.addCallback(self._update_user_ns)
236
237 return d
238
239
240 def push_(self, namespace):
241 """Push dictionary of key=>values to python namespace"""
242
243 self.waitingForEngine = True
244 self.willChangeValueForKey_('commandHistory')
245 d = self.engine.push(namespace)
246 d.addBoth(self._engine_done)
247 d.addCallback(self._update_user_ns)
248
249
250 def pull_(self, keys):
251 """Pull keys from python namespace"""
252
253 self.waitingForEngine = True
254 result = blockingCallFromThread(self.engine.pull, keys)
255 self.waitingForEngine = False
256
257 @objc.signature('v@:@I')
258 def executeFileAtPath_encoding_(self, path, encoding):
259 """Execute file at path in an empty namespace. Update the engine
260 user_ns with the resulting locals."""
261
262 lines,err = NSString.stringWithContentsOfFile_encoding_error_(
263 path,
264 encoding,
265 None)
266 self.engine.execute(lines)
267
268
269 def _engine_done(self, x):
270 self.waitingForEngine = False
271 self.didChangeValueForKey_('commandHistory')
272 return x
273
274 def _update_user_ns(self, result):
275 """Update self.userNS from self.engine's namespace"""
276 d = self.engine.keys()
277 d.addCallback(self._get_engine_namespace_values_for_keys)
278
279 return result
280
281
282 def _get_engine_namespace_values_for_keys(self, keys):
283 d = self.engine.pull(keys)
284 d.addCallback(self._store_engine_namespace_values, keys=keys)
285
286
287 def _store_engine_namespace_values(self, values, keys=[]):
288 assert(len(values) == len(keys))
289 self.willChangeValueForKey_('userNS')
290 for (k,v) in zip(keys,values):
291 self.userNS[k] = saferepr(v)
292 self.didChangeValueForKey_('userNS')
293
294
295 def update_cell_prompt(self, result, blockID=None):
296 print self.blockRanges
297 if(isinstance(result, Failure)):
298 prompt = self.input_prompt()
299
300 else:
301 prompt = self.input_prompt(number=result['number'])
302
303 r = self.blockRanges[blockID].inputPromptRange
304 self.insert_text(prompt,
305 textRange=r,
306 scrollToVisible=False
307 )
308
309 return result
310
311
312 def render_result(self, result):
313 blockID = result['blockID']
314 inputRange = self.blockRanges[blockID].inputRange
315 del self.blockRanges[blockID]
316
317 #print inputRange,self.current_block_range()
318 self.insert_text('\n' +
319 self.output_prompt(number=result['number']) +
320 result.get('display',{}).get('pprint','') +
321 '\n\n',
322 textRange=NSMakeRange(inputRange.location+inputRange.length,
323 0))
324 return result
325
326
327 def render_error(self, failure):
328 print failure
329 blockID = failure.blockID
330 inputRange = self.blockRanges[blockID].inputRange
331 self.insert_text('\n' +
332 self.output_prompt() +
333 '\n' +
334 failure.getErrorMessage() +
335 '\n\n',
336 textRange=NSMakeRange(inputRange.location +
337 inputRange.length,
338 0))
339 self.start_new_block()
340 return failure
341
342
343 def _start_cli_banner(self):
344 """Print banner"""
345
346 banner = """IPython1 %s -- An enhanced Interactive Python.""" % \
347 IPython.__version__
348
349 self.insert_text(banner + '\n\n')
350
351
352 def start_new_block(self):
353 """"""
354
355 self.currentBlockID = self.next_block_ID()
356 self.blockRanges[self.currentBlockID] = self.new_cell_block()
357 self.insert_text(self.input_prompt(),
358 textRange=self.current_block_range().inputPromptRange)
359
360
361
362 def next_block_ID(self):
363
364 return uuid.uuid4()
365
366 def new_cell_block(self):
367 """A new CellBlock at the end of self.textView.textStorage()"""
368
369 return CellBlock(NSMakeRange(self.textView.textStorage().length(),
370 0), #len(self.input_prompt())),
371 NSMakeRange(self.textView.textStorage().length(),# + len(self.input_prompt()),
372 0))
373
374
375 def current_block_range(self):
376 return self.blockRanges.get(self.currentBlockID,
377 self.new_cell_block())
378
379 def current_block(self):
380 """The current block's text"""
381
382 return self.text_for_range(self.current_block_range().inputRange)
383
384 def text_for_range(self, textRange):
385 """text_for_range"""
386
387 ts = self.textView.textStorage()
388 return ts.string().substringWithRange_(textRange)
389
390 def current_line(self):
391 block = self.text_for_range(self.current_block_range().inputRange)
392 block = block.split('\n')
393 return block[-1]
394
395
396 def insert_text(self, string=None, textRange=None, scrollToVisible=True):
397 """Insert text into textView at textRange, updating blockRanges
398 as necessary
399 """
400 if(textRange == None):
401 #range for end of text
402 textRange = NSMakeRange(self.textView.textStorage().length(), 0)
403
404
405 self.textView.replaceCharactersInRange_withString_(
406 textRange, string)
407
408 for r in self.blockRanges.itervalues():
409 r.update_ranges_for_insertion(string, textRange)
410
411 self.textView.setSelectedRange_(textRange)
412 if(scrollToVisible):
413 self.textView.scrollRangeToVisible_(textRange)
414
415
416
417 def replace_current_block_with_string(self, textView, string):
418 textView.replaceCharactersInRange_withString_(
419 self.current_block_range().inputRange,
420 string)
421 self.current_block_range().inputRange.length = len(string)
422 r = NSMakeRange(textView.textStorage().length(), 0)
423 textView.scrollRangeToVisible_(r)
424 textView.setSelectedRange_(r)
425
426
427 def current_indent_string(self):
428 """returns string for indent or None if no indent"""
429
430 return self._indent_for_block(self.current_block())
431
432
433 def _indent_for_block(self, block):
434 lines = block.split('\n')
435 if(len(lines) > 1):
436 currentIndent = len(lines[-1]) - len(lines[-1].lstrip())
437 if(currentIndent == 0):
438 currentIndent = self.tabSpaces
439
440 if(self.tabUsesSpaces):
441 result = ' ' * currentIndent
442 else:
443 result = '\t' * (currentIndent/self.tabSpaces)
444 else:
445 result = None
446
447 return result
448
449
450 # NSTextView delegate methods...
451 def textView_doCommandBySelector_(self, textView, selector):
452 assert(textView == self.textView)
453 NSLog("textView_doCommandBySelector_: "+selector)
454
455
456 if(selector == 'insertNewline:'):
457 indent = self.current_indent_string()
458 if(indent):
459 line = indent + self.current_line()
460 else:
461 line = self.current_line()
462
463 if(self.is_complete(self.current_block())):
464 self.execute(self.current_block(),
465 blockID=self.currentBlockID)
466 self.start_new_block()
467
468 return True
469
470 return False
471
472 elif(selector == 'moveUp:'):
473 prevBlock = self.get_history_previous(self.current_block())
474 if(prevBlock != None):
475 self.replace_current_block_with_string(textView, prevBlock)
476 else:
477 NSBeep()
478 return True
479
480 elif(selector == 'moveDown:'):
481 nextBlock = self.get_history_next()
482 if(nextBlock != None):
483 self.replace_current_block_with_string(textView, nextBlock)
484 else:
485 NSBeep()
486 return True
487
488 elif(selector == 'moveToBeginningOfParagraph:'):
489 textView.setSelectedRange_(NSMakeRange(
490 self.current_block_range().inputRange.location,
491 0))
492 return True
493 elif(selector == 'moveToEndOfParagraph:'):
494 textView.setSelectedRange_(NSMakeRange(
495 self.current_block_range().inputRange.location + \
496 self.current_block_range().inputRange.length, 0))
497 return True
498 elif(selector == 'deleteToEndOfParagraph:'):
499 if(textView.selectedRange().location <= \
500 self.current_block_range().location):
501 raise NotImplemented()
502
503 return False # don't actually handle the delete
504
505 elif(selector == 'insertTab:'):
506 if(len(self.current_line().strip()) == 0): #only white space
507 return False
508 else:
509 self.textView.complete_(self)
510 return True
511
512 elif(selector == 'deleteBackward:'):
513 #if we're at the beginning of the current block, ignore
514 if(textView.selectedRange().location == \
515 self.current_block_range().inputRange.location):
516 return True
517 else:
518 for r in self.blockRanges.itervalues():
519 deleteRange = textView.selectedRange
520 if(deleteRange.length == 0):
521 deleteRange.location -= 1
522 deleteRange.length = 1
523 r.update_ranges_for_deletion(deleteRange)
524 return False
525 return False
526
527
528 def textView_shouldChangeTextInRanges_replacementStrings_(self,
529 textView, ranges, replacementStrings):
530 """
531 Delegate method for NSTextView.
532
533 Refuse change text in ranges not at end, but make those changes at
534 end.
535 """
536
537 assert(len(ranges) == len(replacementStrings))
538 allow = True
539 for r,s in zip(ranges, replacementStrings):
540 r = r.rangeValue()
541 if(textView.textStorage().length() > 0 and
542 r.location < self.current_block_range().inputRange.location):
543 self.insert_text(s)
544 allow = False
545
546 return allow
547
548 def textView_completions_forPartialWordRange_indexOfSelectedItem_(self,
549 textView, words, charRange, index):
550 try:
551 ts = textView.textStorage()
552 token = ts.string().substringWithRange_(charRange)
553 completions = blockingCallFromThread(self.complete, token)
554 except:
555 completions = objc.nil
556 NSBeep()
557
558 return (completions,0)
559
560
1 NO CONTENT: file was removed, binary diff hidden
This diff has been collapsed as it changes many lines, (3424 lines changed) Show them Hide them
@@ -1,3424 +0,0 b''
1 <?xml version="1.0" encoding="UTF-8"?>
2 <archive type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="7.01">
3 <data>
4 <int key="IBDocument.SystemTarget">1050</int>
5 <string key="IBDocument.SystemVersion">9D34</string>
6 <string key="IBDocument.InterfaceBuilderVersion">629</string>
7 <string key="IBDocument.AppKitVersion">949.33</string>
8 <string key="IBDocument.HIToolboxVersion">352.00</string>
9 <object class="NSMutableArray" key="IBDocument.EditedObjectIDs">
10 <bool key="EncodedWithXMLCoder">YES</bool>
11 <integer value="416"/>
12 <integer value="29"/>
13 </object>
14 <object class="NSArray" key="IBDocument.PluginDependencies">
15 <bool key="EncodedWithXMLCoder">YES</bool>
16 <string id="885801228">com.apple.InterfaceBuilderKit</string>
17 <string id="113577022">com.apple.InterfaceBuilder.CocoaPlugin</string>
18 </object>
19 <object class="NSMutableArray" key="IBDocument.RootObjects" id="1048">
20 <bool key="EncodedWithXMLCoder">YES</bool>
21 <object class="NSCustomObject" id="1021">
22 <string key="NSClassName" id="190635311">NSApplication</string>
23 </object>
24 <object class="NSCustomObject" id="1014">
25 <string key="NSClassName">FirstResponder</string>
26 </object>
27 <object class="NSCustomObject" id="1050">
28 <reference key="NSClassName" ref="190635311"/>
29 </object>
30 <object class="NSMenu" id="649796088">
31 <string key="NSTitle">AMainMenu</string>
32 <object class="NSMutableArray" key="NSMenuItems">
33 <bool key="EncodedWithXMLCoder">YES</bool>
34 <object class="NSMenuItem" id="694149608">
35 <reference key="NSMenu" ref="649796088"/>
36 <string key="NSTitle" id="837240250">IPython1Sandbox</string>
37 <string key="NSKeyEquiv" id="255189770"/>
38 <int key="NSKeyEquivModMask">1048576</int>
39 <int key="NSMnemonicLoc">2147483647</int>
40 <object class="NSCustomResource" key="NSOnImage" id="271266416">
41 <string key="NSClassName" id="375865337">NSImage</string>
42 <string key="NSResourceName">NSMenuCheckmark</string>
43 </object>
44 <object class="NSCustomResource" key="NSMixedImage" id="508123839">
45 <reference key="NSClassName" ref="375865337"/>
46 <string key="NSResourceName">NSMenuMixedState</string>
47 </object>
48 <string key="NSAction">submenuAction:</string>
49 <object class="NSMenu" key="NSSubmenu" id="110575045">
50 <reference key="NSTitle" ref="837240250"/>
51 <object class="NSMutableArray" key="NSMenuItems">
52 <bool key="EncodedWithXMLCoder">YES</bool>
53 <object class="NSMenuItem" id="238522557">
54 <reference key="NSMenu" ref="110575045"/>
55 <string key="NSTitle">About IPython1Sandbox</string>
56 <reference key="NSKeyEquiv" ref="255189770"/>
57 <int key="NSMnemonicLoc">2147483647</int>
58 <reference key="NSOnImage" ref="271266416"/>
59 <reference key="NSMixedImage" ref="508123839"/>
60 </object>
61 <object class="NSMenuItem" id="304266470">
62 <reference key="NSMenu" ref="110575045"/>
63 <bool key="NSIsDisabled">YES</bool>
64 <bool key="NSIsSeparator">YES</bool>
65 <reference key="NSTitle" ref="255189770"/>
66 <reference key="NSKeyEquiv" ref="255189770"/>
67 <int key="NSKeyEquivModMask">1048576</int>
68 <int key="NSMnemonicLoc">2147483647</int>
69 <reference key="NSOnImage" ref="271266416"/>
70 <reference key="NSMixedImage" ref="508123839"/>
71 </object>
72 <object class="NSMenuItem" id="609285721">
73 <reference key="NSMenu" ref="110575045"/>
74 <string type="base64-UTF8" key="NSTitle">UHJlZmVyZW5jZXPigKY</string>
75 <string key="NSKeyEquiv">,</string>
76 <int key="NSKeyEquivModMask">1048576</int>
77 <int key="NSMnemonicLoc">2147483647</int>
78 <reference key="NSOnImage" ref="271266416"/>
79 <reference key="NSMixedImage" ref="508123839"/>
80 </object>
81 <object class="NSMenuItem" id="481834944">
82 <reference key="NSMenu" ref="110575045"/>
83 <bool key="NSIsDisabled">YES</bool>
84 <bool key="NSIsSeparator">YES</bool>
85 <reference key="NSTitle" ref="255189770"/>
86 <reference key="NSKeyEquiv" ref="255189770"/>
87 <int key="NSKeyEquivModMask">1048576</int>
88 <int key="NSMnemonicLoc">2147483647</int>
89 <reference key="NSOnImage" ref="271266416"/>
90 <reference key="NSMixedImage" ref="508123839"/>
91 </object>
92 <object class="NSMenuItem" id="1046388886">
93 <reference key="NSMenu" ref="110575045"/>
94 <string key="NSTitle" id="642338826">Services</string>
95 <reference key="NSKeyEquiv" ref="255189770"/>
96 <int key="NSKeyEquivModMask">1048576</int>
97 <int key="NSMnemonicLoc">2147483647</int>
98 <reference key="NSOnImage" ref="271266416"/>
99 <reference key="NSMixedImage" ref="508123839"/>
100 <string key="NSAction">submenuAction:</string>
101 <object class="NSMenu" key="NSSubmenu" id="752062318">
102 <reference key="NSTitle" ref="642338826"/>
103 <object class="NSMutableArray" key="NSMenuItems">
104 <bool key="EncodedWithXMLCoder">YES</bool>
105 </object>
106 <string key="NSName">_NSServicesMenu</string>
107 </object>
108 </object>
109 <object class="NSMenuItem" id="646227648">
110 <reference key="NSMenu" ref="110575045"/>
111 <bool key="NSIsDisabled">YES</bool>
112 <bool key="NSIsSeparator">YES</bool>
113 <reference key="NSTitle" ref="255189770"/>
114 <reference key="NSKeyEquiv" ref="255189770"/>
115 <int key="NSKeyEquivModMask">1048576</int>
116 <int key="NSMnemonicLoc">2147483647</int>
117 <reference key="NSOnImage" ref="271266416"/>
118 <reference key="NSMixedImage" ref="508123839"/>
119 </object>
120 <object class="NSMenuItem" id="755159360">
121 <reference key="NSMenu" ref="110575045"/>
122 <string key="NSTitle">Hide IPython1Sandbox</string>
123 <string key="NSKeyEquiv" id="940330891">h</string>
124 <int key="NSKeyEquivModMask">1048576</int>
125 <int key="NSMnemonicLoc">2147483647</int>
126 <reference key="NSOnImage" ref="271266416"/>
127 <reference key="NSMixedImage" ref="508123839"/>
128 </object>
129 <object class="NSMenuItem" id="342932134">
130 <reference key="NSMenu" ref="110575045"/>
131 <string key="NSTitle">Hide Others</string>
132 <reference key="NSKeyEquiv" ref="940330891"/>
133 <int key="NSKeyEquivModMask">1572864</int>
134 <int key="NSMnemonicLoc">2147483647</int>
135 <reference key="NSOnImage" ref="271266416"/>
136 <reference key="NSMixedImage" ref="508123839"/>
137 </object>
138 <object class="NSMenuItem" id="908899353">
139 <reference key="NSMenu" ref="110575045"/>
140 <string key="NSTitle">Show All</string>
141 <reference key="NSKeyEquiv" ref="255189770"/>
142 <int key="NSKeyEquivModMask">1048576</int>
143 <int key="NSMnemonicLoc">2147483647</int>
144 <reference key="NSOnImage" ref="271266416"/>
145 <reference key="NSMixedImage" ref="508123839"/>
146 </object>
147 <object class="NSMenuItem" id="1056857174">
148 <reference key="NSMenu" ref="110575045"/>
149 <bool key="NSIsDisabled">YES</bool>
150 <bool key="NSIsSeparator">YES</bool>
151 <reference key="NSTitle" ref="255189770"/>
152 <reference key="NSKeyEquiv" ref="255189770"/>
153 <int key="NSKeyEquivModMask">1048576</int>
154 <int key="NSMnemonicLoc">2147483647</int>
155 <reference key="NSOnImage" ref="271266416"/>
156 <reference key="NSMixedImage" ref="508123839"/>
157 </object>
158 <object class="NSMenuItem" id="632727374">
159 <reference key="NSMenu" ref="110575045"/>
160 <string key="NSTitle">Quit IPython1Sandbox</string>
161 <string key="NSKeyEquiv">q</string>
162 <int key="NSKeyEquivModMask">1048576</int>
163 <int key="NSMnemonicLoc">2147483647</int>
164 <reference key="NSOnImage" ref="271266416"/>
165 <reference key="NSMixedImage" ref="508123839"/>
166 </object>
167 </object>
168 <string key="NSName">_NSAppleMenu</string>
169 </object>
170 </object>
171 <object class="NSMenuItem" id="379814623">
172 <reference key="NSMenu" ref="649796088"/>
173 <string key="NSTitle" id="881404960">File</string>
174 <reference key="NSKeyEquiv" ref="255189770"/>
175 <int key="NSKeyEquivModMask">1048576</int>
176 <int key="NSMnemonicLoc">2147483647</int>
177 <reference key="NSOnImage" ref="271266416"/>
178 <reference key="NSMixedImage" ref="508123839"/>
179 <string key="NSAction">submenuAction:</string>
180 <object class="NSMenu" key="NSSubmenu" id="720053764">
181 <reference key="NSTitle" ref="881404960"/>
182 <object class="NSMutableArray" key="NSMenuItems">
183 <bool key="EncodedWithXMLCoder">YES</bool>
184 <object class="NSMenuItem" id="705341025">
185 <reference key="NSMenu" ref="720053764"/>
186 <string key="NSTitle">New</string>
187 <string key="NSKeyEquiv">n</string>
188 <int key="NSKeyEquivModMask">1048576</int>
189 <int key="NSMnemonicLoc">2147483647</int>
190 <reference key="NSOnImage" ref="271266416"/>
191 <reference key="NSMixedImage" ref="508123839"/>
192 </object>
193 <object class="NSMenuItem" id="722745758">
194 <reference key="NSMenu" ref="720053764"/>
195 <string type="base64-UTF8" key="NSTitle">T3BlbuKApg</string>
196 <string key="NSKeyEquiv">o</string>
197 <int key="NSKeyEquivModMask">1048576</int>
198 <int key="NSMnemonicLoc">2147483647</int>
199 <reference key="NSOnImage" ref="271266416"/>
200 <reference key="NSMixedImage" ref="508123839"/>
201 </object>
202 <object class="NSMenuItem" id="1025936716">
203 <reference key="NSMenu" ref="720053764"/>
204 <string key="NSTitle" id="975517829">Open Recent</string>
205 <reference key="NSKeyEquiv" ref="255189770"/>
206 <int key="NSKeyEquivModMask">1048576</int>
207 <int key="NSMnemonicLoc">2147483647</int>
208 <reference key="NSOnImage" ref="271266416"/>
209 <reference key="NSMixedImage" ref="508123839"/>
210 <string key="NSAction">submenuAction:</string>
211 <object class="NSMenu" key="NSSubmenu" id="1065607017">
212 <reference key="NSTitle" ref="975517829"/>
213 <object class="NSMutableArray" key="NSMenuItems">
214 <bool key="EncodedWithXMLCoder">YES</bool>
215 <object class="NSMenuItem" id="759406840">
216 <reference key="NSMenu" ref="1065607017"/>
217 <string key="NSTitle">Clear Menu</string>
218 <reference key="NSKeyEquiv" ref="255189770"/>
219 <int key="NSKeyEquivModMask">1048576</int>
220 <int key="NSMnemonicLoc">2147483647</int>
221 <reference key="NSOnImage" ref="271266416"/>
222 <reference key="NSMixedImage" ref="508123839"/>
223 </object>
224 </object>
225 <string key="NSName">_NSRecentDocumentsMenu</string>
226 </object>
227 </object>
228 <object class="NSMenuItem" id="425164168">
229 <reference key="NSMenu" ref="720053764"/>
230 <bool key="NSIsDisabled">YES</bool>
231 <bool key="NSIsSeparator">YES</bool>
232 <reference key="NSTitle" ref="255189770"/>
233 <reference key="NSKeyEquiv" ref="255189770"/>
234 <int key="NSKeyEquivModMask">1048576</int>
235 <int key="NSMnemonicLoc">2147483647</int>
236 <reference key="NSOnImage" ref="271266416"/>
237 <reference key="NSMixedImage" ref="508123839"/>
238 </object>
239 <object class="NSMenuItem" id="776162233">
240 <reference key="NSMenu" ref="720053764"/>
241 <string key="NSTitle">Close</string>
242 <string key="NSKeyEquiv">w</string>
243 <int key="NSKeyEquivModMask">1048576</int>
244 <int key="NSMnemonicLoc">2147483647</int>
245 <reference key="NSOnImage" ref="271266416"/>
246 <reference key="NSMixedImage" ref="508123839"/>
247 </object>
248 <object class="NSMenuItem" id="1023925487">
249 <reference key="NSMenu" ref="720053764"/>
250 <string key="NSTitle">Save</string>
251 <string key="NSKeyEquiv">s</string>
252 <int key="NSKeyEquivModMask">1048576</int>
253 <int key="NSMnemonicLoc">2147483647</int>
254 <reference key="NSOnImage" ref="271266416"/>
255 <reference key="NSMixedImage" ref="508123839"/>
256 </object>
257 <object class="NSMenuItem" id="117038363">
258 <reference key="NSMenu" ref="720053764"/>
259 <string type="base64-UTF8" key="NSTitle">U2F2ZSBBc+KApg</string>
260 <string key="NSKeyEquiv">S</string>
261 <int key="NSKeyEquivModMask">1179648</int>
262 <int key="NSMnemonicLoc">2147483647</int>
263 <reference key="NSOnImage" ref="271266416"/>
264 <reference key="NSMixedImage" ref="508123839"/>
265 </object>
266 <object class="NSMenuItem" id="579971712">
267 <reference key="NSMenu" ref="720053764"/>
268 <string key="NSTitle">Revert to Saved</string>
269 <reference key="NSKeyEquiv" ref="255189770"/>
270 <int key="NSMnemonicLoc">2147483647</int>
271 <reference key="NSOnImage" ref="271266416"/>
272 <reference key="NSMixedImage" ref="508123839"/>
273 </object>
274 <object class="NSMenuItem" id="1010469920">
275 <reference key="NSMenu" ref="720053764"/>
276 <bool key="NSIsDisabled">YES</bool>
277 <bool key="NSIsSeparator">YES</bool>
278 <reference key="NSTitle" ref="255189770"/>
279 <reference key="NSKeyEquiv" ref="255189770"/>
280 <int key="NSKeyEquivModMask">1048576</int>
281 <int key="NSMnemonicLoc">2147483647</int>
282 <reference key="NSOnImage" ref="271266416"/>
283 <reference key="NSMixedImage" ref="508123839"/>
284 </object>
285 <object class="NSMenuItem" id="294629803">
286 <reference key="NSMenu" ref="720053764"/>
287 <string key="NSTitle">Page Setup...</string>
288 <string key="NSKeyEquiv">P</string>
289 <int key="NSKeyEquivModMask">1179648</int>
290 <int key="NSMnemonicLoc">2147483647</int>
291 <reference key="NSOnImage" ref="271266416"/>
292 <reference key="NSMixedImage" ref="508123839"/>
293 <reference key="NSToolTip" ref="255189770"/>
294 </object>
295 <object class="NSMenuItem" id="49223823">
296 <reference key="NSMenu" ref="720053764"/>
297 <string type="base64-UTF8" key="NSTitle">UHJpbnTigKY</string>
298 <string key="NSKeyEquiv">p</string>
299 <int key="NSKeyEquivModMask">1048576</int>
300 <int key="NSMnemonicLoc">2147483647</int>
301 <reference key="NSOnImage" ref="271266416"/>
302 <reference key="NSMixedImage" ref="508123839"/>
303 </object>
304 </object>
305 </object>
306 </object>
307 <object class="NSMenuItem" id="952259628">
308 <reference key="NSMenu" ref="649796088"/>
309 <string key="NSTitle" id="1037326483">Edit</string>
310 <reference key="NSKeyEquiv" ref="255189770"/>
311 <int key="NSKeyEquivModMask">1048576</int>
312 <int key="NSMnemonicLoc">2147483647</int>
313 <reference key="NSOnImage" ref="271266416"/>
314 <reference key="NSMixedImage" ref="508123839"/>
315 <string key="NSAction">submenuAction:</string>
316 <object class="NSMenu" key="NSSubmenu" id="789758025">
317 <reference key="NSTitle" ref="1037326483"/>
318 <object class="NSMutableArray" key="NSMenuItems">
319 <bool key="EncodedWithXMLCoder">YES</bool>
320 <object class="NSMenuItem" id="1058277027">
321 <reference key="NSMenu" ref="789758025"/>
322 <string key="NSTitle">Undo</string>
323 <string key="NSKeyEquiv">z</string>
324 <int key="NSKeyEquivModMask">1048576</int>
325 <int key="NSMnemonicLoc">2147483647</int>
326 <reference key="NSOnImage" ref="271266416"/>
327 <reference key="NSMixedImage" ref="508123839"/>
328 </object>
329 <object class="NSMenuItem" id="790794224">
330 <reference key="NSMenu" ref="789758025"/>
331 <string key="NSTitle">Redo</string>
332 <string key="NSKeyEquiv">Z</string>
333 <int key="NSKeyEquivModMask">1179648</int>
334 <int key="NSMnemonicLoc">2147483647</int>
335 <reference key="NSOnImage" ref="271266416"/>
336 <reference key="NSMixedImage" ref="508123839"/>
337 </object>
338 <object class="NSMenuItem" id="1040322652">
339 <reference key="NSMenu" ref="789758025"/>
340 <bool key="NSIsDisabled">YES</bool>
341 <bool key="NSIsSeparator">YES</bool>
342 <reference key="NSTitle" ref="255189770"/>
343 <reference key="NSKeyEquiv" ref="255189770"/>
344 <int key="NSKeyEquivModMask">1048576</int>
345 <int key="NSMnemonicLoc">2147483647</int>
346 <reference key="NSOnImage" ref="271266416"/>
347 <reference key="NSMixedImage" ref="508123839"/>
348 </object>
349 <object class="NSMenuItem" id="296257095">
350 <reference key="NSMenu" ref="789758025"/>
351 <string key="NSTitle">Cut</string>
352 <string key="NSKeyEquiv">x</string>
353 <int key="NSKeyEquivModMask">1048576</int>
354 <int key="NSMnemonicLoc">2147483647</int>
355 <reference key="NSOnImage" ref="271266416"/>
356 <reference key="NSMixedImage" ref="508123839"/>
357 </object>
358 <object class="NSMenuItem" id="860595796">
359 <reference key="NSMenu" ref="789758025"/>
360 <string key="NSTitle">Copy</string>
361 <string key="NSKeyEquiv">c</string>
362 <int key="NSKeyEquivModMask">1048576</int>
363 <int key="NSMnemonicLoc">2147483647</int>
364 <reference key="NSOnImage" ref="271266416"/>
365 <reference key="NSMixedImage" ref="508123839"/>
366 </object>
367 <object class="NSMenuItem" id="29853731">
368 <reference key="NSMenu" ref="789758025"/>
369 <string key="NSTitle">Paste</string>
370 <string key="NSKeyEquiv">v</string>
371 <int key="NSKeyEquivModMask">1048576</int>
372 <int key="NSMnemonicLoc">2147483647</int>
373 <reference key="NSOnImage" ref="271266416"/>
374 <reference key="NSMixedImage" ref="508123839"/>
375 </object>
376 <object class="NSMenuItem" id="437104165">
377 <reference key="NSMenu" ref="789758025"/>
378 <string key="NSTitle">Delete</string>
379 <reference key="NSKeyEquiv" ref="255189770"/>
380 <int key="NSKeyEquivModMask">1048576</int>
381 <int key="NSMnemonicLoc">2147483647</int>
382 <reference key="NSOnImage" ref="271266416"/>
383 <reference key="NSMixedImage" ref="508123839"/>
384 </object>
385 <object class="NSMenuItem" id="583158037">
386 <reference key="NSMenu" ref="789758025"/>
387 <string key="NSTitle">Select All</string>
388 <string key="NSKeyEquiv">a</string>
389 <int key="NSKeyEquivModMask">1048576</int>
390 <int key="NSMnemonicLoc">2147483647</int>
391 <reference key="NSOnImage" ref="271266416"/>
392 <reference key="NSMixedImage" ref="508123839"/>
393 </object>
394 <object class="NSMenuItem" id="212016141">
395 <reference key="NSMenu" ref="789758025"/>
396 <bool key="NSIsDisabled">YES</bool>
397 <bool key="NSIsSeparator">YES</bool>
398 <reference key="NSTitle" ref="255189770"/>
399 <reference key="NSKeyEquiv" ref="255189770"/>
400 <int key="NSKeyEquivModMask">1048576</int>
401 <int key="NSMnemonicLoc">2147483647</int>
402 <reference key="NSOnImage" ref="271266416"/>
403 <reference key="NSMixedImage" ref="508123839"/>
404 </object>
405 <object class="NSMenuItem" id="892235320">
406 <reference key="NSMenu" ref="789758025"/>
407 <string key="NSTitle" id="688083180">Find</string>
408 <reference key="NSKeyEquiv" ref="255189770"/>
409 <int key="NSKeyEquivModMask">1048576</int>
410 <int key="NSMnemonicLoc">2147483647</int>
411 <reference key="NSOnImage" ref="271266416"/>
412 <reference key="NSMixedImage" ref="508123839"/>
413 <string key="NSAction">submenuAction:</string>
414 <object class="NSMenu" key="NSSubmenu" id="963351320">
415 <reference key="NSTitle" ref="688083180"/>
416 <object class="NSMutableArray" key="NSMenuItems">
417 <bool key="EncodedWithXMLCoder">YES</bool>
418 <object class="NSMenuItem" id="447796847">
419 <reference key="NSMenu" ref="963351320"/>
420 <string type="base64-UTF8" key="NSTitle">RmluZOKApg</string>
421 <string key="NSKeyEquiv" id="469505129">f</string>
422 <int key="NSKeyEquivModMask">1048576</int>
423 <int key="NSMnemonicLoc">2147483647</int>
424 <reference key="NSOnImage" ref="271266416"/>
425 <reference key="NSMixedImage" ref="508123839"/>
426 <int key="NSTag">1</int>
427 </object>
428 <object class="NSMenuItem" id="326711663">
429 <reference key="NSMenu" ref="963351320"/>
430 <string key="NSTitle">Find Next</string>
431 <string key="NSKeyEquiv" id="762398675">g</string>
432 <int key="NSKeyEquivModMask">1048576</int>
433 <int key="NSMnemonicLoc">2147483647</int>
434 <reference key="NSOnImage" ref="271266416"/>
435 <reference key="NSMixedImage" ref="508123839"/>
436 <int key="NSTag">2</int>
437 </object>
438 <object class="NSMenuItem" id="270902937">
439 <reference key="NSMenu" ref="963351320"/>
440 <string key="NSTitle">Find Previous</string>
441 <string key="NSKeyEquiv" id="819654342">G</string>
442 <int key="NSKeyEquivModMask">1179648</int>
443 <int key="NSMnemonicLoc">2147483647</int>
444 <reference key="NSOnImage" ref="271266416"/>
445 <reference key="NSMixedImage" ref="508123839"/>
446 <int key="NSTag">3</int>
447 </object>
448 <object class="NSMenuItem" id="159080638">
449 <reference key="NSMenu" ref="963351320"/>
450 <string key="NSTitle">Use Selection for Find</string>
451 <string key="NSKeyEquiv">e</string>
452 <int key="NSKeyEquivModMask">1048576</int>
453 <int key="NSMnemonicLoc">2147483647</int>
454 <reference key="NSOnImage" ref="271266416"/>
455 <reference key="NSMixedImage" ref="508123839"/>
456 <int key="NSTag">7</int>
457 </object>
458 <object class="NSMenuItem" id="88285865">
459 <reference key="NSMenu" ref="963351320"/>
460 <string key="NSTitle">Jump to Selection</string>
461 <string key="NSKeyEquiv">j</string>
462 <int key="NSKeyEquivModMask">1048576</int>
463 <int key="NSMnemonicLoc">2147483647</int>
464 <reference key="NSOnImage" ref="271266416"/>
465 <reference key="NSMixedImage" ref="508123839"/>
466 </object>
467 </object>
468 </object>
469 </object>
470 <object class="NSMenuItem" id="972420730">
471 <reference key="NSMenu" ref="789758025"/>
472 <string key="NSTitle" id="739167250">Spelling and Grammar</string>
473 <reference key="NSKeyEquiv" ref="255189770"/>
474 <int key="NSKeyEquivModMask">1048576</int>
475 <int key="NSMnemonicLoc">2147483647</int>
476 <reference key="NSOnImage" ref="271266416"/>
477 <reference key="NSMixedImage" ref="508123839"/>
478 <string key="NSAction">submenuAction:</string>
479 <object class="NSMenu" key="NSSubmenu" id="769623530">
480 <reference key="NSTitle" ref="739167250"/>
481 <object class="NSMutableArray" key="NSMenuItems">
482 <bool key="EncodedWithXMLCoder">YES</bool>
483 <object class="NSMenuItem" id="679648819">
484 <reference key="NSMenu" ref="769623530"/>
485 <string type="base64-UTF8" key="NSTitle">U2hvdyBTcGVsbGluZ+KApg</string>
486 <string key="NSKeyEquiv">:</string>
487 <int key="NSKeyEquivModMask">1048576</int>
488 <int key="NSMnemonicLoc">2147483647</int>
489 <reference key="NSOnImage" ref="271266416"/>
490 <reference key="NSMixedImage" ref="508123839"/>
491 </object>
492 <object class="NSMenuItem" id="96193923">
493 <reference key="NSMenu" ref="769623530"/>
494 <string key="NSTitle">Check Spelling</string>
495 <string key="NSKeyEquiv">;</string>
496 <int key="NSKeyEquivModMask">1048576</int>
497 <int key="NSMnemonicLoc">2147483647</int>
498 <reference key="NSOnImage" ref="271266416"/>
499 <reference key="NSMixedImage" ref="508123839"/>
500 </object>
501 <object class="NSMenuItem" id="948374510">
502 <reference key="NSMenu" ref="769623530"/>
503 <string key="NSTitle">Check Spelling While Typing</string>
504 <reference key="NSKeyEquiv" ref="255189770"/>
505 <int key="NSKeyEquivModMask">1048576</int>
506 <int key="NSMnemonicLoc">2147483647</int>
507 <reference key="NSOnImage" ref="271266416"/>
508 <reference key="NSMixedImage" ref="508123839"/>
509 </object>
510 <object class="NSMenuItem" id="967646866">
511 <reference key="NSMenu" ref="769623530"/>
512 <string key="NSTitle">Check Grammar With Spelling</string>
513 <reference key="NSKeyEquiv" ref="255189770"/>
514 <int key="NSKeyEquivModMask">1048576</int>
515 <int key="NSMnemonicLoc">2147483647</int>
516 <reference key="NSOnImage" ref="271266416"/>
517 <reference key="NSMixedImage" ref="508123839"/>
518 </object>
519 </object>
520 </object>
521 </object>
522 <object class="NSMenuItem" id="507821607">
523 <reference key="NSMenu" ref="789758025"/>
524 <string key="NSTitle" id="904739598">Substitutions</string>
525 <reference key="NSKeyEquiv" ref="255189770"/>
526 <int key="NSKeyEquivModMask">1048576</int>
527 <int key="NSMnemonicLoc">2147483647</int>
528 <reference key="NSOnImage" ref="271266416"/>
529 <reference key="NSMixedImage" ref="508123839"/>
530 <string key="NSAction">submenuAction:</string>
531 <object class="NSMenu" key="NSSubmenu" id="698887838">
532 <reference key="NSTitle" ref="904739598"/>
533 <object class="NSMutableArray" key="NSMenuItems">
534 <bool key="EncodedWithXMLCoder">YES</bool>
535 <object class="NSMenuItem" id="605118523">
536 <reference key="NSMenu" ref="698887838"/>
537 <string key="NSTitle">Smart Copy/Paste</string>
538 <reference key="NSKeyEquiv" ref="469505129"/>
539 <int key="NSKeyEquivModMask">1048576</int>
540 <int key="NSMnemonicLoc">2147483647</int>
541 <reference key="NSOnImage" ref="271266416"/>
542 <reference key="NSMixedImage" ref="508123839"/>
543 <int key="NSTag">1</int>
544 </object>
545 <object class="NSMenuItem" id="197661976">
546 <reference key="NSMenu" ref="698887838"/>
547 <string key="NSTitle">Smart Quotes</string>
548 <reference key="NSKeyEquiv" ref="762398675"/>
549 <int key="NSKeyEquivModMask">1048576</int>
550 <int key="NSMnemonicLoc">2147483647</int>
551 <reference key="NSOnImage" ref="271266416"/>
552 <reference key="NSMixedImage" ref="508123839"/>
553 <int key="NSTag">2</int>
554 </object>
555 <object class="NSMenuItem" id="708854459">
556 <reference key="NSMenu" ref="698887838"/>
557 <string key="NSTitle">Smart Links</string>
558 <reference key="NSKeyEquiv" ref="819654342"/>
559 <int key="NSKeyEquivModMask">1179648</int>
560 <int key="NSMnemonicLoc">2147483647</int>
561 <reference key="NSOnImage" ref="271266416"/>
562 <reference key="NSMixedImage" ref="508123839"/>
563 <int key="NSTag">3</int>
564 </object>
565 </object>
566 </object>
567 </object>
568 <object class="NSMenuItem" id="676164635">
569 <reference key="NSMenu" ref="789758025"/>
570 <string key="NSTitle" id="812002426">Speech</string>
571 <reference key="NSKeyEquiv" ref="255189770"/>
572 <int key="NSKeyEquivModMask">1048576</int>
573 <int key="NSMnemonicLoc">2147483647</int>
574 <reference key="NSOnImage" ref="271266416"/>
575 <reference key="NSMixedImage" ref="508123839"/>
576 <string key="NSAction">submenuAction:</string>
577 <object class="NSMenu" key="NSSubmenu" id="785027613">
578 <reference key="NSTitle" ref="812002426"/>
579 <object class="NSMutableArray" key="NSMenuItems">
580 <bool key="EncodedWithXMLCoder">YES</bool>
581 <object class="NSMenuItem" id="731782645">
582 <reference key="NSMenu" ref="785027613"/>
583 <string key="NSTitle">Start Speaking</string>
584 <reference key="NSKeyEquiv" ref="255189770"/>
585 <int key="NSKeyEquivModMask">1048576</int>
586 <int key="NSMnemonicLoc">2147483647</int>
587 <reference key="NSOnImage" ref="271266416"/>
588 <reference key="NSMixedImage" ref="508123839"/>
589 </object>
590 <object class="NSMenuItem" id="680220178">
591 <reference key="NSMenu" ref="785027613"/>
592 <string key="NSTitle">Stop Speaking</string>
593 <reference key="NSKeyEquiv" ref="255189770"/>
594 <int key="NSKeyEquivModMask">1048576</int>
595 <int key="NSMnemonicLoc">2147483647</int>
596 <reference key="NSOnImage" ref="271266416"/>
597 <reference key="NSMixedImage" ref="508123839"/>
598 </object>
599 </object>
600 </object>
601 </object>
602 </object>
603 </object>
604 </object>
605 <object class="NSMenuItem" id="626404410">
606 <reference key="NSMenu" ref="649796088"/>
607 <string key="NSTitle" id="241242548">Format</string>
608 <reference key="NSKeyEquiv" ref="255189770"/>
609 <int key="NSKeyEquivModMask">1048576</int>
610 <int key="NSMnemonicLoc">2147483647</int>
611 <reference key="NSOnImage" ref="271266416"/>
612 <reference key="NSMixedImage" ref="508123839"/>
613 <string key="NSAction">submenuAction:</string>
614 <object class="NSMenu" key="NSSubmenu" id="502084290">
615 <reference key="NSTitle" ref="241242548"/>
616 <object class="NSMutableArray" key="NSMenuItems">
617 <bool key="EncodedWithXMLCoder">YES</bool>
618 <object class="NSMenuItem" id="519768076">
619 <reference key="NSMenu" ref="502084290"/>
620 <string key="NSTitle">Show Fonts</string>
621 <string key="NSKeyEquiv" id="806579634">t</string>
622 <int key="NSKeyEquivModMask">1048576</int>
623 <int key="NSMnemonicLoc">2147483647</int>
624 <reference key="NSOnImage" ref="271266416"/>
625 <reference key="NSMixedImage" ref="508123839"/>
626 </object>
627 <object class="NSMenuItem" id="1028416764">
628 <reference key="NSMenu" ref="502084290"/>
629 <string key="NSTitle">Show Colors</string>
630 <string key="NSKeyEquiv">C</string>
631 <int key="NSKeyEquivModMask">1179648</int>
632 <int key="NSMnemonicLoc">2147483647</int>
633 <reference key="NSOnImage" ref="271266416"/>
634 <reference key="NSMixedImage" ref="508123839"/>
635 </object>
636 </object>
637 </object>
638 </object>
639 <object class="NSMenuItem" id="586577488">
640 <reference key="NSMenu" ref="649796088"/>
641 <string key="NSTitle" id="809723865">View</string>
642 <reference key="NSKeyEquiv" ref="255189770"/>
643 <int key="NSKeyEquivModMask">1048576</int>
644 <int key="NSMnemonicLoc">2147483647</int>
645 <reference key="NSOnImage" ref="271266416"/>
646 <reference key="NSMixedImage" ref="508123839"/>
647 <string key="NSAction">submenuAction:</string>
648 <object class="NSMenu" key="NSSubmenu" id="466310130">
649 <reference key="NSTitle" ref="809723865"/>
650 <object class="NSMutableArray" key="NSMenuItems">
651 <bool key="EncodedWithXMLCoder">YES</bool>
652 <object class="NSMenuItem" id="102151532">
653 <reference key="NSMenu" ref="466310130"/>
654 <string key="NSTitle">Show Toolbar</string>
655 <reference key="NSKeyEquiv" ref="806579634"/>
656 <int key="NSKeyEquivModMask">1572864</int>
657 <int key="NSMnemonicLoc">2147483647</int>
658 <reference key="NSOnImage" ref="271266416"/>
659 <reference key="NSMixedImage" ref="508123839"/>
660 </object>
661 <object class="NSMenuItem" id="237841660">
662 <reference key="NSMenu" ref="466310130"/>
663 <string type="base64-UTF8" key="NSTitle">Q3VzdG9taXplIFRvb2xiYXLigKY</string>
664 <reference key="NSKeyEquiv" ref="255189770"/>
665 <int key="NSKeyEquivModMask">1048576</int>
666 <int key="NSMnemonicLoc">2147483647</int>
667 <reference key="NSOnImage" ref="271266416"/>
668 <reference key="NSMixedImage" ref="508123839"/>
669 </object>
670 </object>
671 </object>
672 </object>
673 <object class="NSMenuItem" id="713487014">
674 <reference key="NSMenu" ref="649796088"/>
675 <string key="NSTitle" id="64165424">Window</string>
676 <reference key="NSKeyEquiv" ref="255189770"/>
677 <int key="NSKeyEquivModMask">1048576</int>
678 <int key="NSMnemonicLoc">2147483647</int>
679 <reference key="NSOnImage" ref="271266416"/>
680 <reference key="NSMixedImage" ref="508123839"/>
681 <string key="NSAction">submenuAction:</string>
682 <object class="NSMenu" key="NSSubmenu" id="835318025">
683 <reference key="NSTitle" ref="64165424"/>
684 <object class="NSMutableArray" key="NSMenuItems">
685 <bool key="EncodedWithXMLCoder">YES</bool>
686 <object class="NSMenuItem" id="1011231497">
687 <reference key="NSMenu" ref="835318025"/>
688 <string key="NSTitle">Minimize</string>
689 <string key="NSKeyEquiv">m</string>
690 <int key="NSKeyEquivModMask">1048576</int>
691 <int key="NSMnemonicLoc">2147483647</int>
692 <reference key="NSOnImage" ref="271266416"/>
693 <reference key="NSMixedImage" ref="508123839"/>
694 </object>
695 <object class="NSMenuItem" id="575023229">
696 <reference key="NSMenu" ref="835318025"/>
697 <string key="NSTitle">Zoom</string>
698 <reference key="NSKeyEquiv" ref="255189770"/>
699 <int key="NSKeyEquivModMask">1048576</int>
700 <int key="NSMnemonicLoc">2147483647</int>
701 <reference key="NSOnImage" ref="271266416"/>
702 <reference key="NSMixedImage" ref="508123839"/>
703 </object>
704 <object class="NSMenuItem" id="299356726">
705 <reference key="NSMenu" ref="835318025"/>
706 <bool key="NSIsDisabled">YES</bool>
707 <bool key="NSIsSeparator">YES</bool>
708 <reference key="NSTitle" ref="255189770"/>
709 <reference key="NSKeyEquiv" ref="255189770"/>
710 <int key="NSKeyEquivModMask">1048576</int>
711 <int key="NSMnemonicLoc">2147483647</int>
712 <reference key="NSOnImage" ref="271266416"/>
713 <reference key="NSMixedImage" ref="508123839"/>
714 </object>
715 <object class="NSMenuItem" id="625202149">
716 <reference key="NSMenu" ref="835318025"/>
717 <string key="NSTitle">Bring All to Front</string>
718 <reference key="NSKeyEquiv" ref="255189770"/>
719 <int key="NSKeyEquivModMask">1048576</int>
720 <int key="NSMnemonicLoc">2147483647</int>
721 <reference key="NSOnImage" ref="271266416"/>
722 <reference key="NSMixedImage" ref="508123839"/>
723 </object>
724 </object>
725 <string key="NSName">_NSWindowsMenu</string>
726 </object>
727 </object>
728 <object class="NSMenuItem" id="391199113">
729 <reference key="NSMenu" ref="649796088"/>
730 <string key="NSTitle" id="461919786">Help</string>
731 <reference key="NSKeyEquiv" ref="255189770"/>
732 <int key="NSKeyEquivModMask">1048576</int>
733 <int key="NSMnemonicLoc">2147483647</int>
734 <reference key="NSOnImage" ref="271266416"/>
735 <reference key="NSMixedImage" ref="508123839"/>
736 <string key="NSAction">submenuAction:</string>
737 <object class="NSMenu" key="NSSubmenu" id="374024848">
738 <reference key="NSTitle" ref="461919786"/>
739 <object class="NSMutableArray" key="NSMenuItems">
740 <bool key="EncodedWithXMLCoder">YES</bool>
741 <object class="NSMenuItem" id="238773614">
742 <reference key="NSMenu" ref="374024848"/>
743 <string key="NSTitle">IPython1Sandbox Help</string>
744 <string key="NSKeyEquiv">?</string>
745 <int key="NSKeyEquivModMask">1048576</int>
746 <int key="NSMnemonicLoc">2147483647</int>
747 <reference key="NSOnImage" ref="271266416"/>
748 <reference key="NSMixedImage" ref="508123839"/>
749 </object>
750 </object>
751 </object>
752 </object>
753 </object>
754 <string key="NSName">_NSMainMenu</string>
755 </object>
756 <object class="NSWindowTemplate" id="972006081">
757 <int key="NSWindowStyleMask">15</int>
758 <int key="NSWindowBacking">2</int>
759 <string key="NSWindowRect">{{335, 413}, {725, 337}}</string>
760 <int key="NSWTFlags">1946157056</int>
761 <string key="NSWindowTitle">IPython1 (Cocoa)</string>
762 <string key="NSWindowClass">NSWindow</string>
763 <nil key="NSViewClass"/>
764 <object class="NSView" key="NSWindowView" id="439893737">
765 <reference key="NSNextResponder"/>
766 <int key="NSvFlags">256</int>
767 <object class="NSMutableArray" key="NSSubviews">
768 <bool key="EncodedWithXMLCoder">YES</bool>
769 <object class="NSSplitView" id="741760375">
770 <reference key="NSNextResponder" ref="439893737"/>
771 <int key="NSvFlags">274</int>
772 <object class="NSMutableArray" key="NSSubviews">
773 <bool key="EncodedWithXMLCoder">YES</bool>
774 <object class="NSBox" id="554641139">
775 <reference key="NSNextResponder" ref="741760375"/>
776 <int key="NSvFlags">22</int>
777 <object class="NSMutableArray" key="NSSubviews">
778 <bool key="EncodedWithXMLCoder">YES</bool>
779 <object class="NSView" id="597872307">
780 <reference key="NSNextResponder" ref="554641139"/>
781 <int key="NSvFlags">256</int>
782 <object class="NSMutableArray" key="NSSubviews">
783 <bool key="EncodedWithXMLCoder">YES</bool>
784 <object class="NSScrollView" id="188193463">
785 <reference key="NSNextResponder" ref="597872307"/>
786 <int key="NSvFlags">274</int>
787 <object class="NSMutableArray" key="NSSubviews">
788 <bool key="EncodedWithXMLCoder">YES</bool>
789 <object class="NSClipView" id="638544389">
790 <reference key="NSNextResponder" ref="188193463"/>
791 <int key="NSvFlags">2304</int>
792 <object class="NSMutableArray" key="NSSubviews">
793 <bool key="EncodedWithXMLCoder">YES</bool>
794 <object class="NSTextView" id="163417131">
795 <reference key="NSNextResponder" ref="638544389"/>
796 <int key="NSvFlags">2322</int>
797 <object class="NSMutableSet" key="NSDragTypes">
798 <bool key="EncodedWithXMLCoder">YES</bool>
799 <object class="NSMutableArray" key="set.sortedObjects">
800 <bool key="EncodedWithXMLCoder">YES</bool>
801 <string>Apple HTML pasteboard type</string>
802 <string>Apple PDF pasteboard type</string>
803 <string>Apple PICT pasteboard type</string>
804 <string>Apple PNG pasteboard type</string>
805 <string>Apple URL pasteboard type</string>
806 <string>CorePasteboardFlavorType 0x6D6F6F76</string>
807 <string>CorePasteboardFlavorType 0x75726C20</string>
808 <string>NSColor pasteboard type</string>
809 <string>NSFilenamesPboardType</string>
810 <string>NSStringPboardType</string>
811 <string>NeXT Encapsulated PostScript v1.2 pasteboard type</string>
812 <string>NeXT RTFD pasteboard type</string>
813 <string>NeXT Rich Text Format v1.0 pasteboard type</string>
814 <string>NeXT TIFF v4.0 pasteboard type</string>
815 <string>NeXT font pasteboard type</string>
816 <string>NeXT ruler pasteboard type</string>
817 <string>WebURLsWithTitlesPboardType</string>
818 </object>
819 </object>
820 <string key="NSFrame">{{0, 38}, {433, 14}}</string>
821 <reference key="NSSuperview" ref="638544389"/>
822 <reference key="NSWindow"/>
823 <object class="NSTextContainer" key="NSTextContainer" id="662117317">
824 <object class="NSLayoutManager" key="NSLayoutManager">
825 <object class="NSTextStorage" key="NSTextStorage">
826 <object class="NSMutableString" key="NSString">
827 <characters key="NS.bytes"/>
828 </object>
829 <nil key="NSDelegate"/>
830 </object>
831 <object class="NSMutableArray" key="NSTextContainers">
832 <bool key="EncodedWithXMLCoder">YES</bool>
833 <reference ref="662117317"/>
834 </object>
835 <int key="NSLMFlags">6</int>
836 <nil key="NSDelegate"/>
837 </object>
838 <reference key="NSTextView" ref="163417131"/>
839 <double key="NSWidth">4.330000e+02</double>
840 <int key="NSTCFlags">1</int>
841 </object>
842 <object class="NSTextViewSharedData" key="NSSharedData">
843 <int key="NSFlags">346991</int>
844 <object class="NSColor" key="NSBackgroundColor">
845 <int key="NSColorSpace">2</int>
846 <bytes key="NSRGB">MSAwLjk1Mjk0MTI0IDAuODUwOTgwNDYAA</bytes>
847 </object>
848 <object class="NSColor" key="NSInsertionColor" id="555789289">
849 <int key="NSColorSpace">3</int>
850 <bytes key="NSWhite">MAA</bytes>
851 </object>
852 <object class="NSDictionary" key="NSSelectedAttributes">
853 <bool key="EncodedWithXMLCoder">YES</bool>
854 <object class="NSMutableArray" key="dict.sortedKeys">
855 <bool key="EncodedWithXMLCoder">YES</bool>
856 <string>NSBackgroundColor</string>
857 <string id="19777717">NSColor</string>
858 </object>
859 <object class="NSMutableArray" key="dict.values">
860 <bool key="EncodedWithXMLCoder">YES</bool>
861 <object class="NSColor">
862 <int key="NSColorSpace">6</int>
863 <string key="NSCatalogName" id="484387293">System</string>
864 <string key="NSColorName">selectedTextBackgroundColor</string>
865 <object class="NSColor" key="NSColor" id="377165725">
866 <int key="NSColorSpace">3</int>
867 <bytes key="NSWhite">MC42NjY2NjY2OQA</bytes>
868 </object>
869 </object>
870 <object class="NSColor">
871 <int key="NSColorSpace">6</int>
872 <reference key="NSCatalogName" ref="484387293"/>
873 <string key="NSColorName">selectedTextColor</string>
874 <reference key="NSColor" ref="555789289"/>
875 </object>
876 </object>
877 </object>
878 <nil key="NSMarkedAttributes"/>
879 <object class="NSDictionary" key="NSLinkAttributes">
880 <bool key="EncodedWithXMLCoder">YES</bool>
881 <object class="NSMutableArray" key="dict.sortedKeys">
882 <bool key="EncodedWithXMLCoder">YES</bool>
883 <reference ref="19777717"/>
884 <string>NSUnderline</string>
885 </object>
886 <object class="NSMutableArray" key="dict.values">
887 <bool key="EncodedWithXMLCoder">YES</bool>
888 <object class="NSColor">
889 <int key="NSColorSpace">1</int>
890 <bytes key="NSRGB">MCAwIDEAA</bytes>
891 </object>
892 <integer value="1" id="9"/>
893 </object>
894 </object>
895 <nil key="NSDefaultParagraphStyle"/>
896 </object>
897 <int key="NSTVFlags">6</int>
898 <string key="NSMaxSize">{480, 1e+07}</string>
899 <string key="NSMinize">{84, 0}</string>
900 <nil key="NSDelegate"/>
901 </object>
902 </object>
903 <string key="NSFrame">{{1, 1}, {433, 231}}</string>
904 <string key="NSBounds">{{0, 38}, {433, 231}}</string>
905 <reference key="NSSuperview" ref="188193463"/>
906 <reference key="NSWindow"/>
907 <reference key="NSNextKeyView" ref="163417131"/>
908 <reference key="NSDocView" ref="163417131"/>
909 <object class="NSColor" key="NSBGColor" id="521347521">
910 <int key="NSColorSpace">3</int>
911 <bytes key="NSWhite">MQA</bytes>
912 </object>
913 <object class="NSCursor" key="NSCursor">
914 <string key="NSHotSpot">{4, -5}</string>
915 <int key="NSCursorType">1</int>
916 </object>
917 <int key="NScvFlags">4</int>
918 </object>
919 <object class="NSScroller" id="418410897">
920 <reference key="NSNextResponder" ref="188193463"/>
921 <int key="NSvFlags">-2147483392</int>
922 <string key="NSFrame">{{427, 1}, {15, 263}}</string>
923 <reference key="NSSuperview" ref="188193463"/>
924 <reference key="NSWindow"/>
925 <reference key="NSTarget" ref="188193463"/>
926 <string key="NSAction" id="688920982">_doScroller:</string>
927 <double key="NSPercent">3.389175e-01</double>
928 </object>
929 <object class="NSScroller" id="936733673">
930 <reference key="NSNextResponder" ref="188193463"/>
931 <int key="NSvFlags">256</int>
932 <string key="NSFrame">{{-100, -100}, {87, 18}}</string>
933 <reference key="NSSuperview" ref="188193463"/>
934 <reference key="NSWindow"/>
935 <int key="NSsFlags">1</int>
936 <reference key="NSTarget" ref="188193463"/>
937 <reference key="NSAction" ref="688920982"/>
938 <double key="NSCurValue">1.000000e+00</double>
939 <double key="NSPercent">9.456522e-01</double>
940 </object>
941 </object>
942 <string key="NSFrame">{{18, 14}, {435, 233}}</string>
943 <reference key="NSSuperview" ref="597872307"/>
944 <reference key="NSWindow"/>
945 <reference key="NSNextKeyView" ref="638544389"/>
946 <int key="NSsFlags">530</int>
947 <reference key="NSVScroller" ref="418410897"/>
948 <reference key="NSHScroller" ref="936733673"/>
949 <reference key="NSContentView" ref="638544389"/>
950 </object>
951 </object>
952 <string key="NSFrame">{{1, 1}, {471, 257}}</string>
953 <reference key="NSSuperview" ref="554641139"/>
954 <reference key="NSWindow"/>
955 </object>
956 </object>
957 <string key="NSFrameSize">{473, 273}</string>
958 <reference key="NSSuperview" ref="741760375"/>
959 <reference key="NSWindow"/>
960 <string key="NSOffsets" id="1055927954">{0, 0}</string>
961 <object class="NSTextFieldCell" key="NSTitleCell">
962 <int key="NSCellFlags">67239424</int>
963 <int key="NSCellFlags2">0</int>
964 <string key="NSContents">Console</string>
965 <object class="NSFont" key="NSSupport" id="26">
966 <string key="NSName" id="378950370">LucidaGrande</string>
967 <double key="NSSize">1.100000e+01</double>
968 <int key="NSfFlags">3100</int>
969 </object>
970 <object class="NSColor" key="NSBackgroundColor" id="131515055">
971 <int key="NSColorSpace">6</int>
972 <reference key="NSCatalogName" ref="484387293"/>
973 <string key="NSColorName">textBackgroundColor</string>
974 <reference key="NSColor" ref="521347521"/>
975 </object>
976 <object class="NSColor" key="NSTextColor">
977 <int key="NSColorSpace">3</int>
978 <bytes key="NSWhite">MCAwLjgwMDAwMDAxAA</bytes>
979 </object>
980 </object>
981 <reference key="NSContentView" ref="597872307"/>
982 <int key="NSBorderType">1</int>
983 <int key="NSBoxType">0</int>
984 <int key="NSTitlePosition">2</int>
985 <bool key="NSTransparent">NO</bool>
986 </object>
987 <object class="NSBox" id="764100755">
988 <reference key="NSNextResponder" ref="741760375"/>
989 <int key="NSvFlags">51</int>
990 <object class="NSMutableArray" key="NSSubviews">
991 <bool key="EncodedWithXMLCoder">YES</bool>
992 <object class="NSView" id="581281551">
993 <reference key="NSNextResponder" ref="764100755"/>
994 <int key="NSvFlags">256</int>
995 <object class="NSMutableArray" key="NSSubviews">
996 <bool key="EncodedWithXMLCoder">YES</bool>
997 <object class="NSScrollView" id="516244966">
998 <reference key="NSNextResponder" ref="581281551"/>
999 <int key="NSvFlags">274</int>
1000 <object class="NSMutableArray" key="NSSubviews">
1001 <bool key="EncodedWithXMLCoder">YES</bool>
1002 <object class="NSClipView" id="119083427">
1003 <reference key="NSNextResponder" ref="516244966"/>
1004 <int key="NSvFlags">2304</int>
1005 <object class="NSMutableArray" key="NSSubviews">
1006 <bool key="EncodedWithXMLCoder">YES</bool>
1007 <object class="NSTableView" id="23853726">
1008 <reference key="NSNextResponder" ref="119083427"/>
1009 <int key="NSvFlags">256</int>
1010 <string key="NSFrameSize">{156, 200}</string>
1011 <reference key="NSSuperview" ref="119083427"/>
1012 <reference key="NSWindow"/>
1013 <bool key="NSEnabled">YES</bool>
1014 <object class="NSTableHeaderView" key="NSHeaderView" id="1048357090">
1015 <reference key="NSNextResponder" ref="746968320"/>
1016 <int key="NSvFlags">256</int>
1017 <string key="NSFrameSize">{156, 17}</string>
1018 <reference key="NSSuperview" ref="746968320"/>
1019 <reference key="NSWindow"/>
1020 <reference key="NSTableView" ref="23853726"/>
1021 </object>
1022 <object class="_NSCornerView" key="NSCornerView" id="212282722">
1023 <reference key="NSNextResponder" ref="516244966"/>
1024 <int key="NSvFlags">256</int>
1025 <string key="NSFrame">{{157, 0}, {16, 17}}</string>
1026 <reference key="NSSuperview" ref="516244966"/>
1027 <reference key="NSWindow"/>
1028 </object>
1029 <object class="NSMutableArray" key="NSTableColumns">
1030 <bool key="EncodedWithXMLCoder">YES</bool>
1031 <object class="NSTableColumn" id="920426212">
1032 <double key="NSWidth">7.100000e+01</double>
1033 <double key="NSMinWidth">4.000000e+01</double>
1034 <double key="NSMaxWidth">1.000000e+03</double>
1035 <object class="NSTableHeaderCell" key="NSHeaderCell">
1036 <int key="NSCellFlags">75628032</int>
1037 <int key="NSCellFlags2">0</int>
1038 <string key="NSContents">Variable</string>
1039 <reference key="NSSupport" ref="26"/>
1040 <object class="NSColor" key="NSBackgroundColor" id="890615311">
1041 <int key="NSColorSpace">3</int>
1042 <bytes key="NSWhite">MC4zMzMzMzI5OQA</bytes>
1043 </object>
1044 <object class="NSColor" key="NSTextColor" id="866628999">
1045 <int key="NSColorSpace">6</int>
1046 <reference key="NSCatalogName" ref="484387293"/>
1047 <string key="NSColorName">headerTextColor</string>
1048 <reference key="NSColor" ref="555789289"/>
1049 </object>
1050 </object>
1051 <object class="NSTextFieldCell" key="NSDataCell" id="525071236">
1052 <int key="NSCellFlags">337772096</int>
1053 <int key="NSCellFlags2">2048</int>
1054 <string key="NSContents" id="456204663">Text Cell</string>
1055 <object class="NSFont" key="NSSupport" id="8196371">
1056 <reference key="NSName" ref="378950370"/>
1057 <double key="NSSize">1.300000e+01</double>
1058 <int key="NSfFlags">1044</int>
1059 </object>
1060 <reference key="NSControlView" ref="23853726"/>
1061 <object class="NSColor" key="NSBackgroundColor" id="224028609">
1062 <int key="NSColorSpace">6</int>
1063 <reference key="NSCatalogName" ref="484387293"/>
1064 <string key="NSColorName">controlBackgroundColor</string>
1065 <reference key="NSColor" ref="377165725"/>
1066 </object>
1067 <object class="NSColor" key="NSTextColor" id="205104690">
1068 <int key="NSColorSpace">6</int>
1069 <reference key="NSCatalogName" ref="484387293"/>
1070 <string key="NSColorName">controlTextColor</string>
1071 <reference key="NSColor" ref="555789289"/>
1072 </object>
1073 </object>
1074 <int key="NSResizingMask">3</int>
1075 <bool key="NSIsResizeable">YES</bool>
1076 <bool key="NSIsEditable">YES</bool>
1077 <reference key="NSTableView" ref="23853726"/>
1078 </object>
1079 <object class="NSTableColumn" id="857054683">
1080 <double key="NSWidth">7.900000e+01</double>
1081 <double key="NSMinWidth">4.000000e+01</double>
1082 <double key="NSMaxWidth">1.000000e+03</double>
1083 <object class="NSTableHeaderCell" key="NSHeaderCell">
1084 <int key="NSCellFlags">75628032</int>
1085 <int key="NSCellFlags2">0</int>
1086 <string key="NSContents">Value</string>
1087 <reference key="NSSupport" ref="26"/>
1088 <reference key="NSBackgroundColor" ref="890615311"/>
1089 <reference key="NSTextColor" ref="866628999"/>
1090 </object>
1091 <object class="NSTextFieldCell" key="NSDataCell" id="377147224">
1092 <int key="NSCellFlags">337772096</int>
1093 <int key="NSCellFlags2">2048</int>
1094 <reference key="NSContents" ref="456204663"/>
1095 <reference key="NSSupport" ref="8196371"/>
1096 <reference key="NSControlView" ref="23853726"/>
1097 <reference key="NSBackgroundColor" ref="224028609"/>
1098 <reference key="NSTextColor" ref="205104690"/>
1099 </object>
1100 <int key="NSResizingMask">3</int>
1101 <bool key="NSIsResizeable">YES</bool>
1102 <bool key="NSIsEditable">YES</bool>
1103 <reference key="NSTableView" ref="23853726"/>
1104 </object>
1105 </object>
1106 <double key="NSIntercellSpacingWidth">3.000000e+00</double>
1107 <double key="NSIntercellSpacingHeight">2.000000e+00</double>
1108 <reference key="NSBackgroundColor" ref="521347521"/>
1109 <object class="NSColor" key="NSGridColor">
1110 <int key="NSColorSpace">6</int>
1111 <reference key="NSCatalogName" ref="484387293"/>
1112 <string key="NSColorName">gridColor</string>
1113 <object class="NSColor" key="NSColor">
1114 <int key="NSColorSpace">3</int>
1115 <bytes key="NSWhite">MC41AA</bytes>
1116 </object>
1117 </object>
1118 <double key="NSRowHeight">1.700000e+01</double>
1119 <int key="NSTvFlags">-692060160</int>
1120 <int key="NSGridStyleMask">1</int>
1121 <int key="NSColumnAutoresizingStyle">4</int>
1122 <int key="NSDraggingSourceMaskForLocal">15</int>
1123 <int key="NSDraggingSourceMaskForNonLocal">0</int>
1124 <bool key="NSAllowsTypeSelect">YES</bool>
1125 </object>
1126 </object>
1127 <string key="NSFrame">{{1, 17}, {156, 200}}</string>
1128 <reference key="NSSuperview" ref="516244966"/>
1129 <reference key="NSWindow"/>
1130 <reference key="NSNextKeyView" ref="23853726"/>
1131 <reference key="NSDocView" ref="23853726"/>
1132 <reference key="NSBGColor" ref="224028609"/>
1133 <int key="NScvFlags">4</int>
1134 </object>
1135 <object class="NSScroller" id="512953560">
1136 <reference key="NSNextResponder" ref="516244966"/>
1137 <int key="NSvFlags">256</int>
1138 <string key="NSFrame">{{157, 17}, {15, 200}}</string>
1139 <reference key="NSSuperview" ref="516244966"/>
1140 <reference key="NSWindow"/>
1141 <reference key="NSTarget" ref="516244966"/>
1142 <reference key="NSAction" ref="688920982"/>
1143 <double key="NSPercent">9.961240e-01</double>
1144 </object>
1145 <object class="NSScroller" id="47103270">
1146 <reference key="NSNextResponder" ref="516244966"/>
1147 <int key="NSvFlags">256</int>
1148 <string key="NSFrame">{{1, 217}, {156, 15}}</string>
1149 <reference key="NSSuperview" ref="516244966"/>
1150 <reference key="NSWindow"/>
1151 <int key="NSsFlags">1</int>
1152 <reference key="NSTarget" ref="516244966"/>
1153 <reference key="NSAction" ref="688920982"/>
1154 <double key="NSPercent">7.179487e-01</double>
1155 </object>
1156 <object class="NSClipView" id="746968320">
1157 <reference key="NSNextResponder" ref="516244966"/>
1158 <int key="NSvFlags">2304</int>
1159 <object class="NSMutableArray" key="NSSubviews">
1160 <bool key="EncodedWithXMLCoder">YES</bool>
1161 <reference ref="1048357090"/>
1162 </object>
1163 <string key="NSFrame">{{1, 0}, {156, 17}}</string>
1164 <reference key="NSSuperview" ref="516244966"/>
1165 <reference key="NSWindow"/>
1166 <reference key="NSNextKeyView" ref="1048357090"/>
1167 <reference key="NSDocView" ref="1048357090"/>
1168 <reference key="NSBGColor" ref="224028609"/>
1169 <int key="NScvFlags">4</int>
1170 </object>
1171 <reference ref="212282722"/>
1172 </object>
1173 <string key="NSFrame">{{18, 14}, {173, 233}}</string>
1174 <reference key="NSSuperview" ref="581281551"/>
1175 <reference key="NSWindow"/>
1176 <reference key="NSNextKeyView" ref="119083427"/>
1177 <int key="NSsFlags">50</int>
1178 <reference key="NSVScroller" ref="512953560"/>
1179 <reference key="NSHScroller" ref="47103270"/>
1180 <reference key="NSContentView" ref="119083427"/>
1181 <reference key="NSHeaderClipView" ref="746968320"/>
1182 <reference key="NSCornerView" ref="212282722"/>
1183 <bytes key="NSScrollAmts">QSAAAEEgAABBmAAAQZgAAA</bytes>
1184 </object>
1185 </object>
1186 <string key="NSFrame">{{1, 1}, {209, 257}}</string>
1187 <reference key="NSSuperview" ref="764100755"/>
1188 <reference key="NSWindow"/>
1189 </object>
1190 </object>
1191 <string key="NSFrame">{{474, 0}, {211, 273}}</string>
1192 <reference key="NSSuperview" ref="741760375"/>
1193 <reference key="NSWindow"/>
1194 <reference key="NSOffsets" ref="1055927954"/>
1195 <object class="NSTextFieldCell" key="NSTitleCell">
1196 <int key="NSCellFlags">67239424</int>
1197 <int key="NSCellFlags2">0</int>
1198 <string key="NSContents">Workspace</string>
1199 <reference key="NSSupport" ref="26"/>
1200 <reference key="NSBackgroundColor" ref="131515055"/>
1201 <object class="NSColor" key="NSTextColor">
1202 <int key="NSColorSpace">3</int>
1203 <bytes key="NSWhite">MCAwLjgwMDAwMDAxAA</bytes>
1204 </object>
1205 </object>
1206 <reference key="NSContentView" ref="581281551"/>
1207 <int key="NSBorderType">1</int>
1208 <int key="NSBoxType">0</int>
1209 <int key="NSTitlePosition">2</int>
1210 <bool key="NSTransparent">NO</bool>
1211 </object>
1212 </object>
1213 <string key="NSFrame">{{20, 44}, {685, 273}}</string>
1214 <reference key="NSSuperview" ref="439893737"/>
1215 <reference key="NSWindow"/>
1216 <bool key="NSIsVertical">YES</bool>
1217 <int key="NSDividerStyle">2</int>
1218 <string key="NSAutosaveName">ipython1_console_workspace_split</string>
1219 </object>
1220 <object class="NSProgressIndicator" id="74807016">
1221 <reference key="NSNextResponder" ref="439893737"/>
1222 <int key="NSvFlags">1313</int>
1223 <object class="NSPSMatrix" key="NSDrawMatrix"/>
1224 <string key="NSFrame">{{689, 20}, {16, 16}}</string>
1225 <reference key="NSSuperview" ref="439893737"/>
1226 <reference key="NSWindow"/>
1227 <int key="NSpiFlags">28938</int>
1228 <double key="NSMinValue">1.600000e+01</double>
1229 <double key="NSMaxValue">1.000000e+02</double>
1230 </object>
1231 </object>
1232 <string key="NSFrameSize">{725, 337}</string>
1233 <reference key="NSSuperview"/>
1234 <reference key="NSWindow"/>
1235 </object>
1236 <string key="NSScreenRect">{{0, 0}, {1280, 778}}</string>
1237 <string key="NSFrameAutosaveName">ipython1_sandbox</string>
1238 </object>
1239 <object class="NSCustomObject" id="610635028">
1240 <string key="NSClassName" id="982950837">IPython1SandboxAppDelegate</string>
1241 </object>
1242 <object class="NSDictionaryController" id="808393665">
1243 <object class="NSMutableArray" key="NSDeclaredKeys">
1244 <bool key="EncodedWithXMLCoder">YES</bool>
1245 <string>keys</string>
1246 <string id="181461860">key</string>
1247 <string id="276523235">value</string>
1248 </object>
1249 <bool key="NSEditable">YES</bool>
1250 <bool key="NSAvoidsEmptySelection">YES</bool>
1251 <bool key="NSPreservesSelection">YES</bool>
1252 <bool key="NSSelectsInsertedObjects">YES</bool>
1253 <bool key="NSFilterRestrictsInsertion">YES</bool>
1254 <object class="NSArray" key="NSSortDescriptors">
1255 <bool key="EncodedWithXMLCoder">YES</bool>
1256 <object class="NSSortDescriptor">
1257 <reference key="NSKey" ref="181461860"/>
1258 <bool key="NSAscending">YES</bool>
1259 <string key="NSSelector">compare:</string>
1260 </object>
1261 </object>
1262 <bool key="NSClearsFilterPredicateOnInsertion">YES</bool>
1263 <reference key="NSInitialKey" ref="181461860"/>
1264 <reference key="NSInitialValue" ref="276523235"/>
1265 </object>
1266 <object class="NSCustomObject" id="631572152">
1267 <string key="NSClassName" id="695797635">IPythonCocoaController</string>
1268 </object>
1269 </object>
1270 <object class="IBObjectContainer" key="IBDocument.Objects">
1271 <object class="NSMutableArray" key="connectionRecords">
1272 <bool key="EncodedWithXMLCoder">YES</bool>
1273 <object class="IBConnectionRecord">
1274 <object class="IBActionConnection" key="connection">
1275 <string key="label">performMiniaturize:</string>
1276 <reference key="source" ref="1014"/>
1277 <reference key="destination" ref="1011231497"/>
1278 </object>
1279 <int key="connectionID">37</int>
1280 </object>
1281 <object class="IBConnectionRecord">
1282 <object class="IBActionConnection" key="connection">
1283 <string key="label">arrangeInFront:</string>
1284 <reference key="source" ref="1014"/>
1285 <reference key="destination" ref="625202149"/>
1286 </object>
1287 <int key="connectionID">39</int>
1288 </object>
1289 <object class="IBConnectionRecord">
1290 <object class="IBActionConnection" key="connection">
1291 <string key="label">print:</string>
1292 <reference key="source" ref="1014"/>
1293 <reference key="destination" ref="49223823"/>
1294 </object>
1295 <int key="connectionID">86</int>
1296 </object>
1297 <object class="IBConnectionRecord">
1298 <object class="IBActionConnection" key="connection">
1299 <string key="label">runPageLayout:</string>
1300 <reference key="source" ref="1014"/>
1301 <reference key="destination" ref="294629803"/>
1302 </object>
1303 <int key="connectionID">87</int>
1304 </object>
1305 <object class="IBConnectionRecord">
1306 <object class="IBActionConnection" key="connection">
1307 <string key="label">clearRecentDocuments:</string>
1308 <reference key="source" ref="1014"/>
1309 <reference key="destination" ref="759406840"/>
1310 </object>
1311 <int key="connectionID">127</int>
1312 </object>
1313 <object class="IBConnectionRecord">
1314 <object class="IBActionConnection" key="connection">
1315 <string key="label">orderFrontStandardAboutPanel:</string>
1316 <reference key="source" ref="1021"/>
1317 <reference key="destination" ref="238522557"/>
1318 </object>
1319 <int key="connectionID">142</int>
1320 </object>
1321 <object class="IBConnectionRecord">
1322 <object class="IBActionConnection" key="connection">
1323 <string key="label">performClose:</string>
1324 <reference key="source" ref="1014"/>
1325 <reference key="destination" ref="776162233"/>
1326 </object>
1327 <int key="connectionID">193</int>
1328 </object>
1329 <object class="IBConnectionRecord">
1330 <object class="IBActionConnection" key="connection">
1331 <string key="label">toggleContinuousSpellChecking:</string>
1332 <reference key="source" ref="1014"/>
1333 <reference key="destination" ref="948374510"/>
1334 </object>
1335 <int key="connectionID">222</int>
1336 </object>
1337 <object class="IBConnectionRecord">
1338 <object class="IBActionConnection" key="connection">
1339 <string key="label">undo:</string>
1340 <reference key="source" ref="1014"/>
1341 <reference key="destination" ref="1058277027"/>
1342 </object>
1343 <int key="connectionID">223</int>
1344 </object>
1345 <object class="IBConnectionRecord">
1346 <object class="IBActionConnection" key="connection">
1347 <string key="label">copy:</string>
1348 <reference key="source" ref="1014"/>
1349 <reference key="destination" ref="860595796"/>
1350 </object>
1351 <int key="connectionID">224</int>
1352 </object>
1353 <object class="IBConnectionRecord">
1354 <object class="IBActionConnection" key="connection">
1355 <string key="label">checkSpelling:</string>
1356 <reference key="source" ref="1014"/>
1357 <reference key="destination" ref="96193923"/>
1358 </object>
1359 <int key="connectionID">225</int>
1360 </object>
1361 <object class="IBConnectionRecord">
1362 <object class="IBActionConnection" key="connection">
1363 <string key="label">paste:</string>
1364 <reference key="source" ref="1014"/>
1365 <reference key="destination" ref="29853731"/>
1366 </object>
1367 <int key="connectionID">226</int>
1368 </object>
1369 <object class="IBConnectionRecord">
1370 <object class="IBActionConnection" key="connection">
1371 <string key="label">stopSpeaking:</string>
1372 <reference key="source" ref="1014"/>
1373 <reference key="destination" ref="680220178"/>
1374 </object>
1375 <int key="connectionID">227</int>
1376 </object>
1377 <object class="IBConnectionRecord">
1378 <object class="IBActionConnection" key="connection">
1379 <string key="label">cut:</string>
1380 <reference key="source" ref="1014"/>
1381 <reference key="destination" ref="296257095"/>
1382 </object>
1383 <int key="connectionID">228</int>
1384 </object>
1385 <object class="IBConnectionRecord">
1386 <object class="IBActionConnection" key="connection">
1387 <string key="label">showGuessPanel:</string>
1388 <reference key="source" ref="1014"/>
1389 <reference key="destination" ref="679648819"/>
1390 </object>
1391 <int key="connectionID">230</int>
1392 </object>
1393 <object class="IBConnectionRecord">
1394 <object class="IBActionConnection" key="connection">
1395 <string key="label">redo:</string>
1396 <reference key="source" ref="1014"/>
1397 <reference key="destination" ref="790794224"/>
1398 </object>
1399 <int key="connectionID">231</int>
1400 </object>
1401 <object class="IBConnectionRecord">
1402 <object class="IBActionConnection" key="connection">
1403 <string key="label">selectAll:</string>
1404 <reference key="source" ref="1014"/>
1405 <reference key="destination" ref="583158037"/>
1406 </object>
1407 <int key="connectionID">232</int>
1408 </object>
1409 <object class="IBConnectionRecord">
1410 <object class="IBActionConnection" key="connection">
1411 <string key="label">startSpeaking:</string>
1412 <reference key="source" ref="1014"/>
1413 <reference key="destination" ref="731782645"/>
1414 </object>
1415 <int key="connectionID">233</int>
1416 </object>
1417 <object class="IBConnectionRecord">
1418 <object class="IBActionConnection" key="connection">
1419 <string key="label">delete:</string>
1420 <reference key="source" ref="1014"/>
1421 <reference key="destination" ref="437104165"/>
1422 </object>
1423 <int key="connectionID">235</int>
1424 </object>
1425 <object class="IBConnectionRecord">
1426 <object class="IBActionConnection" key="connection">
1427 <string key="label">performZoom:</string>
1428 <reference key="source" ref="1014"/>
1429 <reference key="destination" ref="575023229"/>
1430 </object>
1431 <int key="connectionID">240</int>
1432 </object>
1433 <object class="IBConnectionRecord">
1434 <object class="IBActionConnection" key="connection">
1435 <string key="label">performFindPanelAction:</string>
1436 <reference key="source" ref="1014"/>
1437 <reference key="destination" ref="447796847"/>
1438 </object>
1439 <int key="connectionID">241</int>
1440 </object>
1441 <object class="IBConnectionRecord">
1442 <object class="IBActionConnection" key="connection">
1443 <string key="label">centerSelectionInVisibleArea:</string>
1444 <reference key="source" ref="1014"/>
1445 <reference key="destination" ref="88285865"/>
1446 </object>
1447 <int key="connectionID">245</int>
1448 </object>
1449 <object class="IBConnectionRecord">
1450 <object class="IBActionConnection" key="connection">
1451 <string key="label">toggleGrammarChecking:</string>
1452 <reference key="source" ref="1014"/>
1453 <reference key="destination" ref="967646866"/>
1454 </object>
1455 <int key="connectionID">347</int>
1456 </object>
1457 <object class="IBConnectionRecord">
1458 <object class="IBActionConnection" key="connection">
1459 <string key="label">toggleSmartInsertDelete:</string>
1460 <reference key="source" ref="1014"/>
1461 <reference key="destination" ref="605118523"/>
1462 </object>
1463 <int key="connectionID">355</int>
1464 </object>
1465 <object class="IBConnectionRecord">
1466 <object class="IBActionConnection" key="connection">
1467 <string key="label">toggleAutomaticQuoteSubstitution:</string>
1468 <reference key="source" ref="1014"/>
1469 <reference key="destination" ref="197661976"/>
1470 </object>
1471 <int key="connectionID">356</int>
1472 </object>
1473 <object class="IBConnectionRecord">
1474 <object class="IBActionConnection" key="connection">
1475 <string key="label">toggleAutomaticLinkDetection:</string>
1476 <reference key="source" ref="1014"/>
1477 <reference key="destination" ref="708854459"/>
1478 </object>
1479 <int key="connectionID">357</int>
1480 </object>
1481 <object class="IBConnectionRecord">
1482 <object class="IBActionConnection" key="connection">
1483 <string key="label">showHelp:</string>
1484 <reference key="source" ref="1014"/>
1485 <reference key="destination" ref="238773614"/>
1486 </object>
1487 <int key="connectionID">360</int>
1488 </object>
1489 <object class="IBConnectionRecord">
1490 <object class="IBActionConnection" key="connection">
1491 <string key="label">orderFrontColorPanel:</string>
1492 <reference key="source" ref="1014"/>
1493 <reference key="destination" ref="1028416764"/>
1494 </object>
1495 <int key="connectionID">361</int>
1496 </object>
1497 <object class="IBConnectionRecord">
1498 <object class="IBActionConnection" key="connection">
1499 <string key="label">saveDocument:</string>
1500 <reference key="source" ref="1014"/>
1501 <reference key="destination" ref="1023925487"/>
1502 </object>
1503 <int key="connectionID">362</int>
1504 </object>
1505 <object class="IBConnectionRecord">
1506 <object class="IBActionConnection" key="connection">
1507 <string key="label">saveDocumentAs:</string>
1508 <reference key="source" ref="1014"/>
1509 <reference key="destination" ref="117038363"/>
1510 </object>
1511 <int key="connectionID">363</int>
1512 </object>
1513 <object class="IBConnectionRecord">
1514 <object class="IBActionConnection" key="connection">
1515 <string key="label">revertDocumentToSaved:</string>
1516 <reference key="source" ref="1014"/>
1517 <reference key="destination" ref="579971712"/>
1518 </object>
1519 <int key="connectionID">364</int>
1520 </object>
1521 <object class="IBConnectionRecord">
1522 <object class="IBActionConnection" key="connection">
1523 <string key="label">runToolbarCustomizationPalette:</string>
1524 <reference key="source" ref="1014"/>
1525 <reference key="destination" ref="237841660"/>
1526 </object>
1527 <int key="connectionID">365</int>
1528 </object>
1529 <object class="IBConnectionRecord">
1530 <object class="IBActionConnection" key="connection">
1531 <string key="label">toggleToolbarShown:</string>
1532 <reference key="source" ref="1014"/>
1533 <reference key="destination" ref="102151532"/>
1534 </object>
1535 <int key="connectionID">366</int>
1536 </object>
1537 <object class="IBConnectionRecord">
1538 <object class="IBActionConnection" key="connection">
1539 <string key="label">hide:</string>
1540 <reference key="source" ref="1014"/>
1541 <reference key="destination" ref="755159360"/>
1542 </object>
1543 <int key="connectionID">367</int>
1544 </object>
1545 <object class="IBConnectionRecord">
1546 <object class="IBActionConnection" key="connection">
1547 <string key="label">hideOtherApplications:</string>
1548 <reference key="source" ref="1014"/>
1549 <reference key="destination" ref="342932134"/>
1550 </object>
1551 <int key="connectionID">368</int>
1552 </object>
1553 <object class="IBConnectionRecord">
1554 <object class="IBActionConnection" key="connection">
1555 <string key="label">terminate:</string>
1556 <reference key="source" ref="1014"/>
1557 <reference key="destination" ref="632727374"/>
1558 </object>
1559 <int key="connectionID">369</int>
1560 </object>
1561 <object class="IBConnectionRecord">
1562 <object class="IBActionConnection" key="connection">
1563 <string key="label">unhideAllApplications:</string>
1564 <reference key="source" ref="1014"/>
1565 <reference key="destination" ref="908899353"/>
1566 </object>
1567 <int key="connectionID">370</int>
1568 </object>
1569 <object class="IBConnectionRecord">
1570 <object class="IBOutletConnection" key="connection">
1571 <string key="label" id="606168085">delegate</string>
1572 <reference key="source" ref="1050"/>
1573 <reference key="destination" ref="610635028"/>
1574 </object>
1575 <int key="connectionID">374</int>
1576 </object>
1577 <object class="IBConnectionRecord">
1578 <object class="IBBindingConnection" key="connection">
1579 <string key="label" id="187454546">contentDictionary: userNS</string>
1580 <reference key="source" ref="808393665"/>
1581 <reference key="destination" ref="631572152"/>
1582 <object class="NSNibBindingConnector" key="connector">
1583 <reference key="NSSource" ref="808393665"/>
1584 <reference key="NSDestination" ref="631572152"/>
1585 <reference key="NSLabel" ref="187454546"/>
1586 <string key="NSBinding">contentDictionary</string>
1587 <string key="NSKeyPath">userNS</string>
1588 <int key="NSNibBindingConnectorVersion">2</int>
1589 </object>
1590 </object>
1591 <int key="connectionID">424</int>
1592 </object>
1593 <object class="IBConnectionRecord">
1594 <object class="IBBindingConnection" key="connection">
1595 <string key="label" id="688370141">value: arrangedObjects.value</string>
1596 <reference key="source" ref="857054683"/>
1597 <reference key="destination" ref="808393665"/>
1598 <object class="NSNibBindingConnector" key="connector">
1599 <reference key="NSSource" ref="857054683"/>
1600 <reference key="NSDestination" ref="808393665"/>
1601 <reference key="NSLabel" ref="688370141"/>
1602 <reference key="NSBinding" ref="276523235"/>
1603 <string key="NSKeyPath">arrangedObjects.value</string>
1604 <int key="NSNibBindingConnectorVersion">2</int>
1605 </object>
1606 </object>
1607 <int key="connectionID">427</int>
1608 </object>
1609 <object class="IBConnectionRecord">
1610 <object class="IBBindingConnection" key="connection">
1611 <string key="label" id="764859820">value: arrangedObjects.key</string>
1612 <reference key="source" ref="920426212"/>
1613 <reference key="destination" ref="808393665"/>
1614 <object class="NSNibBindingConnector" key="connector">
1615 <reference key="NSSource" ref="920426212"/>
1616 <reference key="NSDestination" ref="808393665"/>
1617 <reference key="NSLabel" ref="764859820"/>
1618 <reference key="NSBinding" ref="276523235"/>
1619 <string key="NSKeyPath">arrangedObjects.key</string>
1620 <int key="NSNibBindingConnectorVersion">2</int>
1621 </object>
1622 </object>
1623 <int key="connectionID">428</int>
1624 </object>
1625 <object class="IBConnectionRecord">
1626 <object class="IBOutletConnection" key="connection">
1627 <reference key="label" ref="606168085"/>
1628 <reference key="source" ref="972006081"/>
1629 <reference key="destination" ref="631572152"/>
1630 </object>
1631 <int key="connectionID">429</int>
1632 </object>
1633 <object class="IBConnectionRecord">
1634 <object class="IBBindingConnection" key="connection">
1635 <string key="label" id="97087091">animate: waitingForEngine</string>
1636 <reference key="source" ref="74807016"/>
1637 <reference key="destination" ref="631572152"/>
1638 <object class="NSNibBindingConnector" key="connector">
1639 <reference key="NSSource" ref="74807016"/>
1640 <reference key="NSDestination" ref="631572152"/>
1641 <reference key="NSLabel" ref="97087091"/>
1642 <string key="NSBinding">animate</string>
1643 <string key="NSKeyPath">waitingForEngine</string>
1644 <int key="NSNibBindingConnectorVersion">2</int>
1645 </object>
1646 </object>
1647 <int key="connectionID">437</int>
1648 </object>
1649 <object class="IBConnectionRecord">
1650 <object class="IBBindingConnection" key="connection">
1651 <string key="label" id="289275654">filterPredicate: workspaceFilterPredicate</string>
1652 <reference key="source" ref="808393665"/>
1653 <reference key="destination" ref="610635028"/>
1654 <object class="NSNibBindingConnector" key="connector">
1655 <reference key="NSSource" ref="808393665"/>
1656 <reference key="NSDestination" ref="610635028"/>
1657 <reference key="NSLabel" ref="289275654"/>
1658 <string key="NSBinding">filterPredicate</string>
1659 <string key="NSKeyPath">workspaceFilterPredicate</string>
1660 <int key="NSNibBindingConnectorVersion">2</int>
1661 </object>
1662 </object>
1663 <int key="connectionID">440</int>
1664 </object>
1665 <object class="IBConnectionRecord">
1666 <object class="IBOutletConnection" key="connection">
1667 <string key="label">ipythonController</string>
1668 <reference key="source" ref="610635028"/>
1669 <reference key="destination" ref="631572152"/>
1670 </object>
1671 <int key="connectionID">441</int>
1672 </object>
1673 <object class="IBConnectionRecord">
1674 <object class="IBOutletConnection" key="connection">
1675 <string key="label" id="684042788">textView</string>
1676 <reference key="source" ref="631572152"/>
1677 <reference key="destination" ref="163417131"/>
1678 </object>
1679 <int key="connectionID">444</int>
1680 </object>
1681 <object class="IBConnectionRecord">
1682 <object class="IBOutletConnection" key="connection">
1683 <string key="label">initialFirstResponder</string>
1684 <reference key="source" ref="972006081"/>
1685 <reference key="destination" ref="163417131"/>
1686 </object>
1687 <int key="connectionID">445</int>
1688 </object>
1689 </object>
1690 <object class="IBMutableOrderedSet" key="objectRecords">
1691 <object class="NSArray" key="orderedObjects">
1692 <bool key="EncodedWithXMLCoder">YES</bool>
1693 <object class="IBObjectRecord">
1694 <int key="objectID">0</int>
1695 <object class="NSArray" key="object" id="1049">
1696 <bool key="EncodedWithXMLCoder">YES</bool>
1697 </object>
1698 <reference key="children" ref="1048"/>
1699 <nil key="parent"/>
1700 </object>
1701 <object class="IBObjectRecord">
1702 <int key="objectID">-2</int>
1703 <reference key="object" ref="1021"/>
1704 <reference key="parent" ref="1049"/>
1705 <string type="base64-UTF8" key="objectName">RmlsZSdzIE93bmVyA</string>
1706 </object>
1707 <object class="IBObjectRecord">
1708 <int key="objectID">-1</int>
1709 <reference key="object" ref="1014"/>
1710 <reference key="parent" ref="1049"/>
1711 <string key="objectName">First Responder</string>
1712 </object>
1713 <object class="IBObjectRecord">
1714 <int key="objectID">-3</int>
1715 <reference key="object" ref="1050"/>
1716 <reference key="parent" ref="1049"/>
1717 <string key="objectName">Application</string>
1718 </object>
1719 <object class="IBObjectRecord">
1720 <int key="objectID">29</int>
1721 <reference key="object" ref="649796088"/>
1722 <object class="NSMutableArray" key="children">
1723 <bool key="EncodedWithXMLCoder">YES</bool>
1724 <reference ref="713487014"/>
1725 <reference ref="694149608"/>
1726 <reference ref="391199113"/>
1727 <reference ref="952259628"/>
1728 <reference ref="379814623"/>
1729 <reference ref="586577488"/>
1730 <reference ref="626404410"/>
1731 </object>
1732 <reference key="parent" ref="1049"/>
1733 <string key="objectName">MainMenu</string>
1734 </object>
1735 <object class="IBObjectRecord">
1736 <int key="objectID">19</int>
1737 <reference key="object" ref="713487014"/>
1738 <object class="NSMutableArray" key="children">
1739 <bool key="EncodedWithXMLCoder">YES</bool>
1740 <reference ref="835318025"/>
1741 </object>
1742 <reference key="parent" ref="649796088"/>
1743 </object>
1744 <object class="IBObjectRecord">
1745 <int key="objectID">56</int>
1746 <reference key="object" ref="694149608"/>
1747 <object class="NSMutableArray" key="children">
1748 <bool key="EncodedWithXMLCoder">YES</bool>
1749 <reference ref="110575045"/>
1750 </object>
1751 <reference key="parent" ref="649796088"/>
1752 </object>
1753 <object class="IBObjectRecord">
1754 <int key="objectID">103</int>
1755 <reference key="object" ref="391199113"/>
1756 <object class="NSMutableArray" key="children">
1757 <bool key="EncodedWithXMLCoder">YES</bool>
1758 <reference ref="374024848"/>
1759 </object>
1760 <reference key="parent" ref="649796088"/>
1761 <string key="objectName" id="508169456">1</string>
1762 </object>
1763 <object class="IBObjectRecord">
1764 <int key="objectID">217</int>
1765 <reference key="object" ref="952259628"/>
1766 <object class="NSMutableArray" key="children">
1767 <bool key="EncodedWithXMLCoder">YES</bool>
1768 <reference ref="789758025"/>
1769 </object>
1770 <reference key="parent" ref="649796088"/>
1771 </object>
1772 <object class="IBObjectRecord">
1773 <int key="objectID">83</int>
1774 <reference key="object" ref="379814623"/>
1775 <object class="NSMutableArray" key="children">
1776 <bool key="EncodedWithXMLCoder">YES</bool>
1777 <reference ref="720053764"/>
1778 </object>
1779 <reference key="parent" ref="649796088"/>
1780 </object>
1781 <object class="IBObjectRecord">
1782 <int key="objectID">81</int>
1783 <reference key="object" ref="720053764"/>
1784 <object class="NSMutableArray" key="children">
1785 <bool key="EncodedWithXMLCoder">YES</bool>
1786 <reference ref="1023925487"/>
1787 <reference ref="117038363"/>
1788 <reference ref="49223823"/>
1789 <reference ref="722745758"/>
1790 <reference ref="705341025"/>
1791 <reference ref="1025936716"/>
1792 <reference ref="294629803"/>
1793 <reference ref="776162233"/>
1794 <reference ref="425164168"/>
1795 <reference ref="579971712"/>
1796 <reference ref="1010469920"/>
1797 </object>
1798 <reference key="parent" ref="379814623"/>
1799 </object>
1800 <object class="IBObjectRecord">
1801 <int key="objectID">75</int>
1802 <reference key="object" ref="1023925487"/>
1803 <reference key="parent" ref="720053764"/>
1804 <string key="objectName">3</string>
1805 </object>
1806 <object class="IBObjectRecord">
1807 <int key="objectID">80</int>
1808 <reference key="object" ref="117038363"/>
1809 <reference key="parent" ref="720053764"/>
1810 <string key="objectName">8</string>
1811 </object>
1812 <object class="IBObjectRecord">
1813 <int key="objectID">78</int>
1814 <reference key="object" ref="49223823"/>
1815 <reference key="parent" ref="720053764"/>
1816 <string key="objectName">6</string>
1817 </object>
1818 <object class="IBObjectRecord">
1819 <int key="objectID">72</int>
1820 <reference key="object" ref="722745758"/>
1821 <reference key="parent" ref="720053764"/>
1822 </object>
1823 <object class="IBObjectRecord">
1824 <int key="objectID">82</int>
1825 <reference key="object" ref="705341025"/>
1826 <reference key="parent" ref="720053764"/>
1827 <string key="objectName">9</string>
1828 </object>
1829 <object class="IBObjectRecord">
1830 <int key="objectID">124</int>
1831 <reference key="object" ref="1025936716"/>
1832 <object class="NSMutableArray" key="children">
1833 <bool key="EncodedWithXMLCoder">YES</bool>
1834 <reference ref="1065607017"/>
1835 </object>
1836 <reference key="parent" ref="720053764"/>
1837 </object>
1838 <object class="IBObjectRecord">
1839 <int key="objectID">77</int>
1840 <reference key="object" ref="294629803"/>
1841 <reference key="parent" ref="720053764"/>
1842 <string key="objectName">5</string>
1843 </object>
1844 <object class="IBObjectRecord">
1845 <int key="objectID">73</int>
1846 <reference key="object" ref="776162233"/>
1847 <reference key="parent" ref="720053764"/>
1848 <reference key="objectName" ref="508169456"/>
1849 </object>
1850 <object class="IBObjectRecord">
1851 <int key="objectID">79</int>
1852 <reference key="object" ref="425164168"/>
1853 <reference key="parent" ref="720053764"/>
1854 <string key="objectName">7</string>
1855 </object>
1856 <object class="IBObjectRecord">
1857 <int key="objectID">112</int>
1858 <reference key="object" ref="579971712"/>
1859 <reference key="parent" ref="720053764"/>
1860 <string key="objectName">10</string>
1861 </object>
1862 <object class="IBObjectRecord">
1863 <int key="objectID">74</int>
1864 <reference key="object" ref="1010469920"/>
1865 <reference key="parent" ref="720053764"/>
1866 <string key="objectName" id="464456376">2</string>
1867 </object>
1868 <object class="IBObjectRecord">
1869 <int key="objectID">125</int>
1870 <reference key="object" ref="1065607017"/>
1871 <object class="NSMutableArray" key="children">
1872 <bool key="EncodedWithXMLCoder">YES</bool>
1873 <reference ref="759406840"/>
1874 </object>
1875 <reference key="parent" ref="1025936716"/>
1876 </object>
1877 <object class="IBObjectRecord">
1878 <int key="objectID">126</int>
1879 <reference key="object" ref="759406840"/>
1880 <reference key="parent" ref="1065607017"/>
1881 </object>
1882 <object class="IBObjectRecord">
1883 <int key="objectID">205</int>
1884 <reference key="object" ref="789758025"/>
1885 <object class="NSMutableArray" key="children">
1886 <bool key="EncodedWithXMLCoder">YES</bool>
1887 <reference ref="437104165"/>
1888 <reference ref="583158037"/>
1889 <reference ref="1058277027"/>
1890 <reference ref="212016141"/>
1891 <reference ref="296257095"/>
1892 <reference ref="29853731"/>
1893 <reference ref="860595796"/>
1894 <reference ref="1040322652"/>
1895 <reference ref="790794224"/>
1896 <reference ref="892235320"/>
1897 <reference ref="972420730"/>
1898 <reference ref="676164635"/>
1899 <reference ref="507821607"/>
1900 </object>
1901 <reference key="parent" ref="952259628"/>
1902 </object>
1903 <object class="IBObjectRecord">
1904 <int key="objectID">202</int>
1905 <reference key="object" ref="437104165"/>
1906 <reference key="parent" ref="789758025"/>
1907 </object>
1908 <object class="IBObjectRecord">
1909 <int key="objectID">198</int>
1910 <reference key="object" ref="583158037"/>
1911 <reference key="parent" ref="789758025"/>
1912 </object>
1913 <object class="IBObjectRecord">
1914 <int key="objectID">207</int>
1915 <reference key="object" ref="1058277027"/>
1916 <reference key="parent" ref="789758025"/>
1917 </object>
1918 <object class="IBObjectRecord">
1919 <int key="objectID">214</int>
1920 <reference key="object" ref="212016141"/>
1921 <reference key="parent" ref="789758025"/>
1922 </object>
1923 <object class="IBObjectRecord">
1924 <int key="objectID">199</int>
1925 <reference key="object" ref="296257095"/>
1926 <reference key="parent" ref="789758025"/>
1927 </object>
1928 <object class="IBObjectRecord">
1929 <int key="objectID">203</int>
1930 <reference key="object" ref="29853731"/>
1931 <reference key="parent" ref="789758025"/>
1932 </object>
1933 <object class="IBObjectRecord">
1934 <int key="objectID">197</int>
1935 <reference key="object" ref="860595796"/>
1936 <reference key="parent" ref="789758025"/>
1937 </object>
1938 <object class="IBObjectRecord">
1939 <int key="objectID">206</int>
1940 <reference key="object" ref="1040322652"/>
1941 <reference key="parent" ref="789758025"/>
1942 </object>
1943 <object class="IBObjectRecord">
1944 <int key="objectID">215</int>
1945 <reference key="object" ref="790794224"/>
1946 <reference key="parent" ref="789758025"/>
1947 </object>
1948 <object class="IBObjectRecord">
1949 <int key="objectID">218</int>
1950 <reference key="object" ref="892235320"/>
1951 <object class="NSMutableArray" key="children">
1952 <bool key="EncodedWithXMLCoder">YES</bool>
1953 <reference ref="963351320"/>
1954 </object>
1955 <reference key="parent" ref="789758025"/>
1956 </object>
1957 <object class="IBObjectRecord">
1958 <int key="objectID">216</int>
1959 <reference key="object" ref="972420730"/>
1960 <object class="NSMutableArray" key="children">
1961 <bool key="EncodedWithXMLCoder">YES</bool>
1962 <reference ref="769623530"/>
1963 </object>
1964 <reference key="parent" ref="789758025"/>
1965 </object>
1966 <object class="IBObjectRecord">
1967 <int key="objectID">200</int>
1968 <reference key="object" ref="769623530"/>
1969 <object class="NSMutableArray" key="children">
1970 <bool key="EncodedWithXMLCoder">YES</bool>
1971 <reference ref="948374510"/>
1972 <reference ref="96193923"/>
1973 <reference ref="679648819"/>
1974 <reference ref="967646866"/>
1975 </object>
1976 <reference key="parent" ref="972420730"/>
1977 </object>
1978 <object class="IBObjectRecord">
1979 <int key="objectID">219</int>
1980 <reference key="object" ref="948374510"/>
1981 <reference key="parent" ref="769623530"/>
1982 </object>
1983 <object class="IBObjectRecord">
1984 <int key="objectID">201</int>
1985 <reference key="object" ref="96193923"/>
1986 <reference key="parent" ref="769623530"/>
1987 </object>
1988 <object class="IBObjectRecord">
1989 <int key="objectID">204</int>
1990 <reference key="object" ref="679648819"/>
1991 <reference key="parent" ref="769623530"/>
1992 </object>
1993 <object class="IBObjectRecord">
1994 <int key="objectID">220</int>
1995 <reference key="object" ref="963351320"/>
1996 <object class="NSMutableArray" key="children">
1997 <bool key="EncodedWithXMLCoder">YES</bool>
1998 <reference ref="270902937"/>
1999 <reference ref="88285865"/>
2000 <reference ref="159080638"/>
2001 <reference ref="326711663"/>
2002 <reference ref="447796847"/>
2003 </object>
2004 <reference key="parent" ref="892235320"/>
2005 </object>
2006 <object class="IBObjectRecord">
2007 <int key="objectID">213</int>
2008 <reference key="object" ref="270902937"/>
2009 <reference key="parent" ref="963351320"/>
2010 </object>
2011 <object class="IBObjectRecord">
2012 <int key="objectID">210</int>
2013 <reference key="object" ref="88285865"/>
2014 <reference key="parent" ref="963351320"/>
2015 </object>
2016 <object class="IBObjectRecord">
2017 <int key="objectID">221</int>
2018 <reference key="object" ref="159080638"/>
2019 <reference key="parent" ref="963351320"/>
2020 </object>
2021 <object class="IBObjectRecord">
2022 <int key="objectID">208</int>
2023 <reference key="object" ref="326711663"/>
2024 <reference key="parent" ref="963351320"/>
2025 </object>
2026 <object class="IBObjectRecord">
2027 <int key="objectID">209</int>
2028 <reference key="object" ref="447796847"/>
2029 <reference key="parent" ref="963351320"/>
2030 </object>
2031 <object class="IBObjectRecord">
2032 <int key="objectID">106</int>
2033 <reference key="object" ref="374024848"/>
2034 <object class="NSMutableArray" key="children">
2035 <bool key="EncodedWithXMLCoder">YES</bool>
2036 <reference ref="238773614"/>
2037 </object>
2038 <reference key="parent" ref="391199113"/>
2039 <reference key="objectName" ref="464456376"/>
2040 </object>
2041 <object class="IBObjectRecord">
2042 <int key="objectID">111</int>
2043 <reference key="object" ref="238773614"/>
2044 <reference key="parent" ref="374024848"/>
2045 </object>
2046 <object class="IBObjectRecord">
2047 <int key="objectID">57</int>
2048 <reference key="object" ref="110575045"/>
2049 <object class="NSMutableArray" key="children">
2050 <bool key="EncodedWithXMLCoder">YES</bool>
2051 <reference ref="238522557"/>
2052 <reference ref="755159360"/>
2053 <reference ref="908899353"/>
2054 <reference ref="632727374"/>
2055 <reference ref="646227648"/>
2056 <reference ref="609285721"/>
2057 <reference ref="481834944"/>
2058 <reference ref="304266470"/>
2059 <reference ref="1046388886"/>
2060 <reference ref="1056857174"/>
2061 <reference ref="342932134"/>
2062 </object>
2063 <reference key="parent" ref="694149608"/>
2064 </object>
2065 <object class="IBObjectRecord">
2066 <int key="objectID">58</int>
2067 <reference key="object" ref="238522557"/>
2068 <reference key="parent" ref="110575045"/>
2069 </object>
2070 <object class="IBObjectRecord">
2071 <int key="objectID">134</int>
2072 <reference key="object" ref="755159360"/>
2073 <reference key="parent" ref="110575045"/>
2074 </object>
2075 <object class="IBObjectRecord">
2076 <int key="objectID">150</int>
2077 <reference key="object" ref="908899353"/>
2078 <reference key="parent" ref="110575045"/>
2079 </object>
2080 <object class="IBObjectRecord">
2081 <int key="objectID">136</int>
2082 <reference key="object" ref="632727374"/>
2083 <reference key="parent" ref="110575045"/>
2084 <string key="objectName">1111</string>
2085 </object>
2086 <object class="IBObjectRecord">
2087 <int key="objectID">144</int>
2088 <reference key="object" ref="646227648"/>
2089 <reference key="parent" ref="110575045"/>
2090 </object>
2091 <object class="IBObjectRecord">
2092 <int key="objectID">129</int>
2093 <reference key="object" ref="609285721"/>
2094 <reference key="parent" ref="110575045"/>
2095 <string key="objectName">121</string>
2096 </object>
2097 <object class="IBObjectRecord">
2098 <int key="objectID">143</int>
2099 <reference key="object" ref="481834944"/>
2100 <reference key="parent" ref="110575045"/>
2101 </object>
2102 <object class="IBObjectRecord">
2103 <int key="objectID">236</int>
2104 <reference key="object" ref="304266470"/>
2105 <reference key="parent" ref="110575045"/>
2106 </object>
2107 <object class="IBObjectRecord">
2108 <int key="objectID">131</int>
2109 <reference key="object" ref="1046388886"/>
2110 <object class="NSMutableArray" key="children">
2111 <bool key="EncodedWithXMLCoder">YES</bool>
2112 <reference ref="752062318"/>
2113 </object>
2114 <reference key="parent" ref="110575045"/>
2115 </object>
2116 <object class="IBObjectRecord">
2117 <int key="objectID">149</int>
2118 <reference key="object" ref="1056857174"/>
2119 <reference key="parent" ref="110575045"/>
2120 </object>
2121 <object class="IBObjectRecord">
2122 <int key="objectID">145</int>
2123 <reference key="object" ref="342932134"/>
2124 <reference key="parent" ref="110575045"/>
2125 </object>
2126 <object class="IBObjectRecord">
2127 <int key="objectID">130</int>
2128 <reference key="object" ref="752062318"/>
2129 <reference key="parent" ref="1046388886"/>
2130 </object>
2131 <object class="IBObjectRecord">
2132 <int key="objectID">24</int>
2133 <reference key="object" ref="835318025"/>
2134 <object class="NSMutableArray" key="children">
2135 <bool key="EncodedWithXMLCoder">YES</bool>
2136 <reference ref="299356726"/>
2137 <reference ref="625202149"/>
2138 <reference ref="575023229"/>
2139 <reference ref="1011231497"/>
2140 </object>
2141 <reference key="parent" ref="713487014"/>
2142 </object>
2143 <object class="IBObjectRecord">
2144 <int key="objectID">92</int>
2145 <reference key="object" ref="299356726"/>
2146 <reference key="parent" ref="835318025"/>
2147 </object>
2148 <object class="IBObjectRecord">
2149 <int key="objectID">5</int>
2150 <reference key="object" ref="625202149"/>
2151 <reference key="parent" ref="835318025"/>
2152 </object>
2153 <object class="IBObjectRecord">
2154 <int key="objectID">239</int>
2155 <reference key="object" ref="575023229"/>
2156 <reference key="parent" ref="835318025"/>
2157 </object>
2158 <object class="IBObjectRecord">
2159 <int key="objectID">23</int>
2160 <reference key="object" ref="1011231497"/>
2161 <reference key="parent" ref="835318025"/>
2162 </object>
2163 <object class="IBObjectRecord">
2164 <int key="objectID">295</int>
2165 <reference key="object" ref="586577488"/>
2166 <object class="NSMutableArray" key="children">
2167 <bool key="EncodedWithXMLCoder">YES</bool>
2168 <reference ref="466310130"/>
2169 </object>
2170 <reference key="parent" ref="649796088"/>
2171 </object>
2172 <object class="IBObjectRecord">
2173 <int key="objectID">296</int>
2174 <reference key="object" ref="466310130"/>
2175 <object class="NSMutableArray" key="children">
2176 <bool key="EncodedWithXMLCoder">YES</bool>
2177 <reference ref="102151532"/>
2178 <reference ref="237841660"/>
2179 </object>
2180 <reference key="parent" ref="586577488"/>
2181 </object>
2182 <object class="IBObjectRecord">
2183 <int key="objectID">297</int>
2184 <reference key="object" ref="102151532"/>
2185 <reference key="parent" ref="466310130"/>
2186 </object>
2187 <object class="IBObjectRecord">
2188 <int key="objectID">298</int>
2189 <reference key="object" ref="237841660"/>
2190 <reference key="parent" ref="466310130"/>
2191 </object>
2192 <object class="IBObjectRecord">
2193 <int key="objectID">299</int>
2194 <reference key="object" ref="626404410"/>
2195 <object class="NSMutableArray" key="children">
2196 <bool key="EncodedWithXMLCoder">YES</bool>
2197 <reference ref="502084290"/>
2198 </object>
2199 <reference key="parent" ref="649796088"/>
2200 </object>
2201 <object class="IBObjectRecord">
2202 <int key="objectID">300</int>
2203 <reference key="object" ref="502084290"/>
2204 <object class="NSMutableArray" key="children">
2205 <bool key="EncodedWithXMLCoder">YES</bool>
2206 <reference ref="519768076"/>
2207 <reference ref="1028416764"/>
2208 </object>
2209 <reference key="parent" ref="626404410"/>
2210 </object>
2211 <object class="IBObjectRecord">
2212 <int key="objectID">344</int>
2213 <reference key="object" ref="519768076"/>
2214 <reference key="parent" ref="502084290"/>
2215 </object>
2216 <object class="IBObjectRecord">
2217 <int key="objectID">345</int>
2218 <reference key="object" ref="1028416764"/>
2219 <reference key="parent" ref="502084290"/>
2220 </object>
2221 <object class="IBObjectRecord">
2222 <int key="objectID">211</int>
2223 <reference key="object" ref="676164635"/>
2224 <object class="NSMutableArray" key="children">
2225 <bool key="EncodedWithXMLCoder">YES</bool>
2226 <reference ref="785027613"/>
2227 </object>
2228 <reference key="parent" ref="789758025"/>
2229 </object>
2230 <object class="IBObjectRecord">
2231 <int key="objectID">212</int>
2232 <reference key="object" ref="785027613"/>
2233 <object class="NSMutableArray" key="children">
2234 <bool key="EncodedWithXMLCoder">YES</bool>
2235 <reference ref="680220178"/>
2236 <reference ref="731782645"/>
2237 </object>
2238 <reference key="parent" ref="676164635"/>
2239 </object>
2240 <object class="IBObjectRecord">
2241 <int key="objectID">195</int>
2242 <reference key="object" ref="680220178"/>
2243 <reference key="parent" ref="785027613"/>
2244 </object>
2245 <object class="IBObjectRecord">
2246 <int key="objectID">196</int>
2247 <reference key="object" ref="731782645"/>
2248 <reference key="parent" ref="785027613"/>
2249 </object>
2250 <object class="IBObjectRecord">
2251 <int key="objectID">346</int>
2252 <reference key="object" ref="967646866"/>
2253 <reference key="parent" ref="769623530"/>
2254 </object>
2255 <object class="IBObjectRecord">
2256 <int key="objectID">348</int>
2257 <reference key="object" ref="507821607"/>
2258 <object class="NSMutableArray" key="children">
2259 <bool key="EncodedWithXMLCoder">YES</bool>
2260 <reference ref="698887838"/>
2261 </object>
2262 <reference key="parent" ref="789758025"/>
2263 </object>
2264 <object class="IBObjectRecord">
2265 <int key="objectID">349</int>
2266 <reference key="object" ref="698887838"/>
2267 <object class="NSMutableArray" key="children">
2268 <bool key="EncodedWithXMLCoder">YES</bool>
2269 <reference ref="605118523"/>
2270 <reference ref="197661976"/>
2271 <reference ref="708854459"/>
2272 </object>
2273 <reference key="parent" ref="507821607"/>
2274 </object>
2275 <object class="IBObjectRecord">
2276 <int key="objectID">350</int>
2277 <reference key="object" ref="605118523"/>
2278 <reference key="parent" ref="698887838"/>
2279 </object>
2280 <object class="IBObjectRecord">
2281 <int key="objectID">351</int>
2282 <reference key="object" ref="197661976"/>
2283 <reference key="parent" ref="698887838"/>
2284 </object>
2285 <object class="IBObjectRecord">
2286 <int key="objectID">354</int>
2287 <reference key="object" ref="708854459"/>
2288 <reference key="parent" ref="698887838"/>
2289 </object>
2290 <object class="IBObjectRecord">
2291 <int key="objectID">371</int>
2292 <reference key="object" ref="972006081"/>
2293 <object class="NSMutableArray" key="children">
2294 <bool key="EncodedWithXMLCoder">YES</bool>
2295 <reference ref="439893737"/>
2296 </object>
2297 <reference key="parent" ref="1049"/>
2298 </object>
2299 <object class="IBObjectRecord">
2300 <int key="objectID">372</int>
2301 <reference key="object" ref="439893737"/>
2302 <object class="NSMutableArray" key="children">
2303 <bool key="EncodedWithXMLCoder">YES</bool>
2304 <reference ref="741760375"/>
2305 <reference ref="74807016"/>
2306 </object>
2307 <reference key="parent" ref="972006081"/>
2308 </object>
2309 <object class="IBObjectRecord">
2310 <int key="objectID">373</int>
2311 <reference key="object" ref="610635028"/>
2312 <reference key="parent" ref="1049"/>
2313 <reference key="objectName" ref="982950837"/>
2314 </object>
2315 <object class="IBObjectRecord">
2316 <int key="objectID">385</int>
2317 <reference key="object" ref="808393665"/>
2318 <reference key="parent" ref="1049"/>
2319 <string key="objectName">User Namespace Controller</string>
2320 </object>
2321 <object class="IBObjectRecord">
2322 <int key="objectID">421</int>
2323 <reference key="object" ref="741760375"/>
2324 <object class="NSMutableArray" key="children">
2325 <bool key="EncodedWithXMLCoder">YES</bool>
2326 <reference ref="554641139"/>
2327 <reference ref="764100755"/>
2328 </object>
2329 <reference key="parent" ref="439893737"/>
2330 </object>
2331 <object class="IBObjectRecord">
2332 <int key="objectID">420</int>
2333 <reference key="object" ref="554641139"/>
2334 <object class="NSMutableArray" key="children">
2335 <bool key="EncodedWithXMLCoder">YES</bool>
2336 <reference ref="188193463"/>
2337 </object>
2338 <reference key="parent" ref="741760375"/>
2339 </object>
2340 <object class="IBObjectRecord">
2341 <int key="objectID">416</int>
2342 <reference key="object" ref="188193463"/>
2343 <object class="NSMutableArray" key="children">
2344 <bool key="EncodedWithXMLCoder">YES</bool>
2345 <reference ref="418410897"/>
2346 <reference ref="936733673"/>
2347 <reference ref="163417131"/>
2348 </object>
2349 <reference key="parent" ref="554641139"/>
2350 </object>
2351 <object class="IBObjectRecord">
2352 <int key="objectID">417</int>
2353 <reference key="object" ref="418410897"/>
2354 <reference key="parent" ref="188193463"/>
2355 </object>
2356 <object class="IBObjectRecord">
2357 <int key="objectID">418</int>
2358 <reference key="object" ref="936733673"/>
2359 <reference key="parent" ref="188193463"/>
2360 </object>
2361 <object class="IBObjectRecord">
2362 <int key="objectID">419</int>
2363 <reference key="object" ref="163417131"/>
2364 <reference key="parent" ref="188193463"/>
2365 </object>
2366 <object class="IBObjectRecord">
2367 <int key="objectID">406</int>
2368 <reference key="object" ref="764100755"/>
2369 <object class="NSMutableArray" key="children">
2370 <bool key="EncodedWithXMLCoder">YES</bool>
2371 <reference ref="516244966"/>
2372 </object>
2373 <reference key="parent" ref="741760375"/>
2374 </object>
2375 <object class="IBObjectRecord">
2376 <int key="objectID">407</int>
2377 <reference key="object" ref="516244966"/>
2378 <object class="NSMutableArray" key="children">
2379 <bool key="EncodedWithXMLCoder">YES</bool>
2380 <reference ref="23853726"/>
2381 <reference ref="47103270"/>
2382 <reference ref="512953560"/>
2383 <reference ref="1048357090"/>
2384 </object>
2385 <reference key="parent" ref="764100755"/>
2386 </object>
2387 <object class="IBObjectRecord">
2388 <int key="objectID">411</int>
2389 <reference key="object" ref="23853726"/>
2390 <object class="NSMutableArray" key="children">
2391 <bool key="EncodedWithXMLCoder">YES</bool>
2392 <reference ref="857054683"/>
2393 <reference ref="920426212"/>
2394 </object>
2395 <reference key="parent" ref="516244966"/>
2396 </object>
2397 <object class="IBObjectRecord">
2398 <int key="objectID">410</int>
2399 <reference key="object" ref="47103270"/>
2400 <reference key="parent" ref="516244966"/>
2401 </object>
2402 <object class="IBObjectRecord">
2403 <int key="objectID">409</int>
2404 <reference key="object" ref="512953560"/>
2405 <reference key="parent" ref="516244966"/>
2406 </object>
2407 <object class="IBObjectRecord">
2408 <int key="objectID">408</int>
2409 <reference key="object" ref="1048357090"/>
2410 <reference key="parent" ref="516244966"/>
2411 </object>
2412 <object class="IBObjectRecord">
2413 <int key="objectID">413</int>
2414 <reference key="object" ref="857054683"/>
2415 <object class="NSMutableArray" key="children">
2416 <bool key="EncodedWithXMLCoder">YES</bool>
2417 <reference ref="377147224"/>
2418 </object>
2419 <reference key="parent" ref="23853726"/>
2420 </object>
2421 <object class="IBObjectRecord">
2422 <int key="objectID">412</int>
2423 <reference key="object" ref="920426212"/>
2424 <object class="NSMutableArray" key="children">
2425 <bool key="EncodedWithXMLCoder">YES</bool>
2426 <reference ref="525071236"/>
2427 </object>
2428 <reference key="parent" ref="23853726"/>
2429 </object>
2430 <object class="IBObjectRecord">
2431 <int key="objectID">415</int>
2432 <reference key="object" ref="525071236"/>
2433 <reference key="parent" ref="920426212"/>
2434 </object>
2435 <object class="IBObjectRecord">
2436 <int key="objectID">414</int>
2437 <reference key="object" ref="377147224"/>
2438 <reference key="parent" ref="857054683"/>
2439 </object>
2440 <object class="IBObjectRecord">
2441 <int key="objectID">422</int>
2442 <reference key="object" ref="631572152"/>
2443 <reference key="parent" ref="1049"/>
2444 </object>
2445 <object class="IBObjectRecord">
2446 <int key="objectID">436</int>
2447 <reference key="object" ref="74807016"/>
2448 <reference key="parent" ref="439893737"/>
2449 </object>
2450 </object>
2451 </object>
2452 <object class="NSMutableDictionary" key="flattenedProperties">
2453 <bool key="EncodedWithXMLCoder">YES</bool>
2454 <object class="NSMutableArray" key="dict.sortedKeys">
2455 <bool key="EncodedWithXMLCoder">YES</bool>
2456 <string>-1.IBPluginDependency</string>
2457 <string>-2.IBPluginDependency</string>
2458 <string>-3.IBPluginDependency</string>
2459 <string>103.IBPluginDependency</string>
2460 <string>103.ImportedFromIB2</string>
2461 <string>106.IBPluginDependency</string>
2462 <string>106.ImportedFromIB2</string>
2463 <string>106.editorWindowContentRectSynchronizationRect</string>
2464 <string>111.IBPluginDependency</string>
2465 <string>111.ImportedFromIB2</string>
2466 <string>112.IBPluginDependency</string>
2467 <string>112.ImportedFromIB2</string>
2468 <string>124.IBPluginDependency</string>
2469 <string>124.ImportedFromIB2</string>
2470 <string>125.IBPluginDependency</string>
2471 <string>125.ImportedFromIB2</string>
2472 <string>125.editorWindowContentRectSynchronizationRect</string>
2473 <string>126.IBPluginDependency</string>
2474 <string>126.ImportedFromIB2</string>
2475 <string>129.IBPluginDependency</string>
2476 <string>129.ImportedFromIB2</string>
2477 <string>130.IBPluginDependency</string>
2478 <string>130.ImportedFromIB2</string>
2479 <string>130.editorWindowContentRectSynchronizationRect</string>
2480 <string>131.IBPluginDependency</string>
2481 <string>131.ImportedFromIB2</string>
2482 <string>134.IBPluginDependency</string>
2483 <string>134.ImportedFromIB2</string>
2484 <string>136.IBPluginDependency</string>
2485 <string>136.ImportedFromIB2</string>
2486 <string>143.IBPluginDependency</string>
2487 <string>143.ImportedFromIB2</string>
2488 <string>144.IBPluginDependency</string>
2489 <string>144.ImportedFromIB2</string>
2490 <string>145.IBPluginDependency</string>
2491 <string>145.ImportedFromIB2</string>
2492 <string>149.IBPluginDependency</string>
2493 <string>149.ImportedFromIB2</string>
2494 <string>150.IBPluginDependency</string>
2495 <string>150.ImportedFromIB2</string>
2496 <string>19.IBPluginDependency</string>
2497 <string>19.ImportedFromIB2</string>
2498 <string>195.IBPluginDependency</string>
2499 <string>195.ImportedFromIB2</string>
2500 <string>196.IBPluginDependency</string>
2501 <string>196.ImportedFromIB2</string>
2502 <string>197.IBPluginDependency</string>
2503 <string>197.ImportedFromIB2</string>
2504 <string>198.IBPluginDependency</string>
2505 <string>198.ImportedFromIB2</string>
2506 <string>199.IBPluginDependency</string>
2507 <string>199.ImportedFromIB2</string>
2508 <string>200.IBPluginDependency</string>
2509 <string>200.ImportedFromIB2</string>
2510 <string>200.editorWindowContentRectSynchronizationRect</string>
2511 <string>201.IBPluginDependency</string>
2512 <string>201.ImportedFromIB2</string>
2513 <string>202.IBPluginDependency</string>
2514 <string>202.ImportedFromIB2</string>
2515 <string>203.IBPluginDependency</string>
2516 <string>203.ImportedFromIB2</string>
2517 <string>204.IBPluginDependency</string>
2518 <string>204.ImportedFromIB2</string>
2519 <string>205.IBPluginDependency</string>
2520 <string>205.ImportedFromIB2</string>
2521 <string>205.editorWindowContentRectSynchronizationRect</string>
2522 <string>206.IBPluginDependency</string>
2523 <string>206.ImportedFromIB2</string>
2524 <string>207.IBPluginDependency</string>
2525 <string>207.ImportedFromIB2</string>
2526 <string>208.IBPluginDependency</string>
2527 <string>208.ImportedFromIB2</string>
2528 <string>209.IBPluginDependency</string>
2529 <string>209.ImportedFromIB2</string>
2530 <string>210.IBPluginDependency</string>
2531 <string>210.ImportedFromIB2</string>
2532 <string>211.IBPluginDependency</string>
2533 <string>211.ImportedFromIB2</string>
2534 <string>212.IBPluginDependency</string>
2535 <string>212.ImportedFromIB2</string>
2536 <string>212.editorWindowContentRectSynchronizationRect</string>
2537 <string>213.IBPluginDependency</string>
2538 <string>213.ImportedFromIB2</string>
2539 <string>214.IBPluginDependency</string>
2540 <string>214.ImportedFromIB2</string>
2541 <string>215.IBPluginDependency</string>
2542 <string>215.ImportedFromIB2</string>
2543 <string>216.IBPluginDependency</string>
2544 <string>216.ImportedFromIB2</string>
2545 <string>217.IBPluginDependency</string>
2546 <string>217.ImportedFromIB2</string>
2547 <string>218.IBPluginDependency</string>
2548 <string>218.ImportedFromIB2</string>
2549 <string>219.IBPluginDependency</string>
2550 <string>219.ImportedFromIB2</string>
2551 <string>220.IBPluginDependency</string>
2552 <string>220.ImportedFromIB2</string>
2553 <string>220.editorWindowContentRectSynchronizationRect</string>
2554 <string>221.IBPluginDependency</string>
2555 <string>221.ImportedFromIB2</string>
2556 <string>23.IBPluginDependency</string>
2557 <string>23.ImportedFromIB2</string>
2558 <string>236.IBPluginDependency</string>
2559 <string>236.ImportedFromIB2</string>
2560 <string>239.IBPluginDependency</string>
2561 <string>239.ImportedFromIB2</string>
2562 <string>24.IBPluginDependency</string>
2563 <string>24.ImportedFromIB2</string>
2564 <string>24.editorWindowContentRectSynchronizationRect</string>
2565 <string>29.IBPluginDependency</string>
2566 <string>29.ImportedFromIB2</string>
2567 <string>29.WindowOrigin</string>
2568 <string>29.editorWindowContentRectSynchronizationRect</string>
2569 <string>295.IBPluginDependency</string>
2570 <string>296.IBPluginDependency</string>
2571 <string>296.editorWindowContentRectSynchronizationRect</string>
2572 <string>297.IBPluginDependency</string>
2573 <string>298.IBPluginDependency</string>
2574 <string>299.IBPluginDependency</string>
2575 <string>300.IBPluginDependency</string>
2576 <string>300.editorWindowContentRectSynchronizationRect</string>
2577 <string>344.IBPluginDependency</string>
2578 <string>345.IBPluginDependency</string>
2579 <string>346.IBPluginDependency</string>
2580 <string>346.ImportedFromIB2</string>
2581 <string>348.IBPluginDependency</string>
2582 <string>348.ImportedFromIB2</string>
2583 <string>349.IBPluginDependency</string>
2584 <string>349.ImportedFromIB2</string>
2585 <string>349.editorWindowContentRectSynchronizationRect</string>
2586 <string>350.IBPluginDependency</string>
2587 <string>350.ImportedFromIB2</string>
2588 <string>351.IBPluginDependency</string>
2589 <string>351.ImportedFromIB2</string>
2590 <string>354.IBPluginDependency</string>
2591 <string>354.ImportedFromIB2</string>
2592 <string>371.IBPluginDependency</string>
2593 <string>371.IBViewEditorWindowController.showingLayoutRectangles</string>
2594 <string>371.IBWindowTemplateEditedContentRect</string>
2595 <string>371.NSWindowTemplate.visibleAtLaunch</string>
2596 <string>371.editorWindowContentRectSynchronizationRect</string>
2597 <string>372.IBPluginDependency</string>
2598 <string>373.IBPluginDependency</string>
2599 <string>385.IBPluginDependency</string>
2600 <string>407.IBPluginDependency</string>
2601 <string>409.IBPluginDependency</string>
2602 <string>410.IBPluginDependency</string>
2603 <string>411.IBPluginDependency</string>
2604 <string>412.IBPluginDependency</string>
2605 <string>413.IBPluginDependency</string>
2606 <string>414.IBPluginDependency</string>
2607 <string>415.IBPluginDependency</string>
2608 <string>416.IBPluginDependency</string>
2609 <string>417.IBPluginDependency</string>
2610 <string>418.IBPluginDependency</string>
2611 <string>419.IBAttributePlaceholdersKey</string>
2612 <string>419.IBPluginDependency</string>
2613 <string>422.IBPluginDependency</string>
2614 <string>436.IBPluginDependency</string>
2615 <string>5.IBPluginDependency</string>
2616 <string>5.ImportedFromIB2</string>
2617 <string>56.IBPluginDependency</string>
2618 <string>56.ImportedFromIB2</string>
2619 <string>57.IBPluginDependency</string>
2620 <string>57.ImportedFromIB2</string>
2621 <string>57.editorWindowContentRectSynchronizationRect</string>
2622 <string>58.IBPluginDependency</string>
2623 <string>58.ImportedFromIB2</string>
2624 <string>72.IBPluginDependency</string>
2625 <string>72.ImportedFromIB2</string>
2626 <string>73.IBPluginDependency</string>
2627 <string>73.ImportedFromIB2</string>
2628 <string>74.IBPluginDependency</string>
2629 <string>74.ImportedFromIB2</string>
2630 <string>75.IBPluginDependency</string>
2631 <string>75.ImportedFromIB2</string>
2632 <string>77.IBPluginDependency</string>
2633 <string>77.ImportedFromIB2</string>
2634 <string>78.IBPluginDependency</string>
2635 <string>78.ImportedFromIB2</string>
2636 <string>79.IBPluginDependency</string>
2637 <string>79.ImportedFromIB2</string>
2638 <string>80.IBPluginDependency</string>
2639 <string>80.ImportedFromIB2</string>
2640 <string>81.IBPluginDependency</string>
2641 <string>81.ImportedFromIB2</string>
2642 <string>81.editorWindowContentRectSynchronizationRect</string>
2643 <string>82.IBPluginDependency</string>
2644 <string>82.ImportedFromIB2</string>
2645 <string>83.IBPluginDependency</string>
2646 <string>83.ImportedFromIB2</string>
2647 <string>92.IBPluginDependency</string>
2648 <string>92.ImportedFromIB2</string>
2649 </object>
2650 <object class="NSMutableArray" key="dict.values">
2651 <bool key="EncodedWithXMLCoder">YES</bool>
2652 <reference ref="113577022"/>
2653 <reference ref="885801228"/>
2654 <reference ref="885801228"/>
2655 <reference ref="113577022"/>
2656 <reference ref="9"/>
2657 <reference ref="113577022"/>
2658 <reference ref="9"/>
2659 <string>{{596, 852}, {216, 23}}</string>
2660 <reference ref="113577022"/>
2661 <reference ref="9"/>
2662 <reference ref="113577022"/>
2663 <reference ref="9"/>
2664 <reference ref="113577022"/>
2665 <reference ref="9"/>
2666 <reference ref="113577022"/>
2667 <reference ref="9"/>
2668 <string>{{522, 812}, {146, 23}}</string>
2669 <reference ref="113577022"/>
2670 <reference ref="9"/>
2671 <reference ref="113577022"/>
2672 <reference ref="9"/>
2673 <reference ref="113577022"/>
2674 <reference ref="9"/>
2675 <string>{{436, 809}, {64, 6}}</string>
2676 <reference ref="113577022"/>
2677 <reference ref="9"/>
2678 <reference ref="113577022"/>
2679 <reference ref="9"/>
2680 <reference ref="113577022"/>
2681 <reference ref="9"/>
2682 <reference ref="113577022"/>
2683 <reference ref="9"/>
2684 <reference ref="113577022"/>
2685 <reference ref="9"/>
2686 <reference ref="113577022"/>
2687 <reference ref="9"/>
2688 <reference ref="113577022"/>
2689 <reference ref="9"/>
2690 <reference ref="113577022"/>
2691 <reference ref="9"/>
2692 <reference ref="113577022"/>
2693 <reference ref="9"/>
2694 <reference ref="113577022"/>
2695 <reference ref="9"/>
2696 <reference ref="113577022"/>
2697 <reference ref="9"/>
2698 <reference ref="113577022"/>
2699 <reference ref="9"/>
2700 <reference ref="113577022"/>
2701 <reference ref="9"/>
2702 <reference ref="113577022"/>
2703 <reference ref="9"/>
2704 <reference ref="113577022"/>
2705 <reference ref="9"/>
2706 <string>{{608, 612}, {275, 83}}</string>
2707 <reference ref="113577022"/>
2708 <reference ref="9"/>
2709 <reference ref="113577022"/>
2710 <reference ref="9"/>
2711 <reference ref="113577022"/>
2712 <reference ref="9"/>
2713 <reference ref="113577022"/>
2714 <reference ref="9"/>
2715 <reference ref="113577022"/>
2716 <reference ref="9"/>
2717 <string>{{365, 632}, {243, 243}}</string>
2718 <reference ref="113577022"/>
2719 <reference ref="9"/>
2720 <reference ref="113577022"/>
2721 <reference ref="9"/>
2722 <reference ref="113577022"/>
2723 <reference ref="9"/>
2724 <reference ref="113577022"/>
2725 <reference ref="9"/>
2726 <reference ref="113577022"/>
2727 <reference ref="9"/>
2728 <reference ref="113577022"/>
2729 <reference ref="9"/>
2730 <reference ref="113577022"/>
2731 <reference ref="9"/>
2732 <string>{{608, 612}, {167, 43}}</string>
2733 <reference ref="113577022"/>
2734 <reference ref="9"/>
2735 <reference ref="113577022"/>
2736 <reference ref="9"/>
2737 <reference ref="113577022"/>
2738 <reference ref="9"/>
2739 <reference ref="113577022"/>
2740 <reference ref="9"/>
2741 <reference ref="113577022"/>
2742 <reference ref="9"/>
2743 <reference ref="113577022"/>
2744 <reference ref="9"/>
2745 <reference ref="113577022"/>
2746 <reference ref="9"/>
2747 <reference ref="113577022"/>
2748 <reference ref="9"/>
2749 <string>{{608, 612}, {241, 103}}</string>
2750 <reference ref="113577022"/>
2751 <reference ref="9"/>
2752 <reference ref="113577022"/>
2753 <reference ref="9"/>
2754 <reference ref="113577022"/>
2755 <reference ref="9"/>
2756 <reference ref="113577022"/>
2757 <reference ref="9"/>
2758 <reference ref="113577022"/>
2759 <reference ref="9"/>
2760 <string>{{525, 802}, {197, 73}}</string>
2761 <reference ref="113577022"/>
2762 <reference ref="9"/>
2763 <string>{74, 862}</string>
2764 <string>{{11, 736}, {489, 20}}</string>
2765 <reference ref="113577022"/>
2766 <reference ref="113577022"/>
2767 <string>{{475, 832}, {234, 43}}</string>
2768 <reference ref="113577022"/>
2769 <reference ref="113577022"/>
2770 <reference ref="113577022"/>
2771 <reference ref="113577022"/>
2772 <string>{{409, 832}, {176, 43}}</string>
2773 <reference ref="113577022"/>
2774 <reference ref="113577022"/>
2775 <reference ref="113577022"/>
2776 <reference ref="9"/>
2777 <reference ref="113577022"/>
2778 <reference ref="9"/>
2779 <reference ref="113577022"/>
2780 <reference ref="9"/>
2781 <string>{{608, 612}, {215, 63}}</string>
2782 <reference ref="113577022"/>
2783 <reference ref="9"/>
2784 <reference ref="113577022"/>
2785 <reference ref="9"/>
2786 <reference ref="113577022"/>
2787 <reference ref="9"/>
2788 <reference ref="113577022"/>
2789 <integer value="0"/>
2790 <string>{{27, 368}, {725, 337}}</string>
2791 <reference ref="9"/>
2792 <string>{{27, 368}, {725, 337}}</string>
2793 <reference ref="113577022"/>
2794 <reference ref="113577022"/>
2795 <reference ref="113577022"/>
2796 <reference ref="113577022"/>
2797 <reference ref="113577022"/>
2798 <reference ref="113577022"/>
2799 <reference ref="113577022"/>
2800 <reference ref="113577022"/>
2801 <reference ref="113577022"/>
2802 <reference ref="113577022"/>
2803 <reference ref="113577022"/>
2804 <reference ref="113577022"/>
2805 <reference ref="113577022"/>
2806 <reference ref="113577022"/>
2807 <object class="NSMutableDictionary">
2808 <bool key="EncodedWithXMLCoder">YES</bool>
2809 <object class="NSArray" key="dict.sortedKeys">
2810 <bool key="EncodedWithXMLCoder">YES</bool>
2811 </object>
2812 <object class="NSMutableArray" key="dict.values">
2813 <bool key="EncodedWithXMLCoder">YES</bool>
2814 </object>
2815 </object>
2816 <reference ref="113577022"/>
2817 <reference ref="113577022"/>
2818 <reference ref="113577022"/>
2819 <reference ref="113577022"/>
2820 <reference ref="9"/>
2821 <reference ref="113577022"/>
2822 <reference ref="9"/>
2823 <reference ref="113577022"/>
2824 <reference ref="9"/>
2825 <string>{{23, 794}, {245, 183}}</string>
2826 <reference ref="113577022"/>
2827 <reference ref="9"/>
2828 <reference ref="113577022"/>
2829 <reference ref="9"/>
2830 <reference ref="113577022"/>
2831 <reference ref="9"/>
2832 <reference ref="113577022"/>
2833 <reference ref="9"/>
2834 <reference ref="113577022"/>
2835 <reference ref="9"/>
2836 <reference ref="113577022"/>
2837 <reference ref="9"/>
2838 <reference ref="113577022"/>
2839 <reference ref="9"/>
2840 <reference ref="113577022"/>
2841 <reference ref="9"/>
2842 <reference ref="113577022"/>
2843 <reference ref="9"/>
2844 <reference ref="113577022"/>
2845 <reference ref="9"/>
2846 <string>{{323, 672}, {199, 203}}</string>
2847 <reference ref="113577022"/>
2848 <reference ref="9"/>
2849 <reference ref="113577022"/>
2850 <reference ref="9"/>
2851 <reference ref="113577022"/>
2852 <reference ref="9"/>
2853 </object>
2854 </object>
2855 <object class="NSMutableDictionary" key="unlocalizedProperties">
2856 <bool key="EncodedWithXMLCoder">YES</bool>
2857 <object class="NSArray" key="dict.sortedKeys">
2858 <bool key="EncodedWithXMLCoder">YES</bool>
2859 </object>
2860 <object class="NSMutableArray" key="dict.values">
2861 <bool key="EncodedWithXMLCoder">YES</bool>
2862 </object>
2863 </object>
2864 <nil key="activeLocalization"/>
2865 <object class="NSMutableDictionary" key="localizations">
2866 <bool key="EncodedWithXMLCoder">YES</bool>
2867 <object class="NSArray" key="dict.sortedKeys">
2868 <bool key="EncodedWithXMLCoder">YES</bool>
2869 </object>
2870 <object class="NSMutableArray" key="dict.values">
2871 <bool key="EncodedWithXMLCoder">YES</bool>
2872 </object>
2873 </object>
2874 <nil key="sourceID"/>
2875 <int key="maxID">445</int>
2876 </object>
2877 <object class="IBClassDescriber" key="IBDocument.Classes">
2878 <object class="NSMutableArray" key="referencedPartialClassDescriptions">
2879 <bool key="EncodedWithXMLCoder">YES</bool>
2880 <object class="IBPartialClassDescription">
2881 <reference key="className" ref="695797635"/>
2882 <nil key="superclassName"/>
2883 <object class="NSMutableDictionary" key="actions">
2884 <bool key="EncodedWithXMLCoder">YES</bool>
2885 <object class="NSArray" key="dict.sortedKeys">
2886 <bool key="EncodedWithXMLCoder">YES</bool>
2887 </object>
2888 <object class="NSMutableArray" key="dict.values">
2889 <bool key="EncodedWithXMLCoder">YES</bool>
2890 </object>
2891 </object>
2892 <object class="NSMutableDictionary" key="outlets">
2893 <reference key="NS.key.0" ref="684042788"/>
2894 <string key="NS.object.0">NSTextView</string>
2895 </object>
2896 <object class="IBClassDescriptionSource" key="sourceIdentifier">
2897 <string key="majorKey">IBUserSource</string>
2898 <reference key="minorKey" ref="255189770"/>
2899 </object>
2900 </object>
2901 <object class="IBPartialClassDescription">
2902 <string key="className">IPython1SandboxAppDelegate</string>
2903 <string key="superclassName">NSObject</string>
2904 <object class="NSMutableDictionary" key="actions">
2905 <bool key="EncodedWithXMLCoder">YES</bool>
2906 <object class="NSArray" key="dict.sortedKeys">
2907 <bool key="EncodedWithXMLCoder">YES</bool>
2908 </object>
2909 <object class="NSMutableArray" key="dict.values">
2910 <bool key="EncodedWithXMLCoder">YES</bool>
2911 </object>
2912 </object>
2913 <object class="NSMutableDictionary" key="outlets">
2914 <string key="NS.key.0">ipythonController</string>
2915 <string key="NS.object.0">id</string>
2916 </object>
2917 <object class="IBClassDescriptionSource" key="sourceIdentifier">
2918 <string key="majorKey">IBProjectSource</string>
2919 <string key="minorKey">IPython1SandboxAppDelegate.py</string>
2920 </object>
2921 </object>
2922 </object>
2923 </object>
2924 <int key="IBDocument.localizationMode">0</int>
2925 <string key="IBDocument.LastKnownRelativeProjectPath">../../IPython1Sandbox.xcodeproj</string>
2926 <int key="IBDocument.defaultPropertyAccessControl">3</int>
2927 <object class="NSMutableData" key="IBDocument.RunnableNib">
2928 <bytes key="NS.bytes">YnBsaXN0MDDUAAEAAgADAAQABQAGAAkAClgkdmVyc2lvblQkdG9wWSRhcmNoaXZlclgkb2JqZWN0cxIA
2929 AYag0QAHAAhdSUIub2JqZWN0ZGF0YYABXxAPTlNLZXllZEFyY2hpdmVyrxEC/gALAAwAMQA1ADYAPAA9
2930 AEIAWABZAFoAWwALAGgAbQB7AIAAlQCZAKAApAC0ALoAzQDRAOYA+gD7APwA/QD+AP8BAAEBAQIBAwEE
2931 AQUBBgEHAQgBCQEKAQsBDwEQARkBIQEmASoBLQExATUBOQE7AT0BTQFSAVUBWgFBAVQBYwFqAWsBbAFv
2932 AXQBdQF4AYAAkAGBAYQBhwGIAYkBjgGPAZABkwGYAZkBmwGeAasBrAGtAbEBvAG9Ab4BwQHCAcQBxQHG
2933 AdIB0wHbAdwB3wHkAeUB6AHtAfAB/AIAAgcCCwIdAiUCLwIzAlECUgJaAmQCZQJoAm4CbwJyAncCiAKP
2934 ApACkwKYApkCnAKmAqcCrAKxArICtwK4ArsCwwLJAsoC0QLWAtcC2gLcAt0C5gLnAvAC8QL1AvYC9wL4
2935 AvkC/wMAAwIDAwMEAwcDFgMYAxsDHAMfAAsDIAMhAyIDJQNXA10DbQNzASkDdAN5A3oDewN+A4IDgwOG
2936 A4cDiwOPA5YDmgObA5wDnQOhA6gDrAOtA64DrwOzA7wDwAPBA8IDwwPHA84D0gPTA9QD1QPZA+AD5APl
2937 A+YD6gPxA/UD9gP3A/gD/AQDBAQEBQQJBBAEEQQSBBMEFwQeBB8EIAQkBCsELwQwBDEENQQ9BD4EPwRA
2938 BEQESwRMBE0ETgRUBFcEWgRbBFwEXwRjBGoEawRsBG0EcgR1BHYEdwR7BIIEgwSEBIUEiQSQBJUElgSX
2939 BJgEnASjBKQEpQSmBKoEsQSyBLMEtAS4BL8EwwTEBMUExgTKBNEE0gTTBNQE2ATfBOAE4QTiBOYE7QTx
2940 BPIE8wT0BPgE/wUABQEFBgUNBQ4FDwUTBRwFHQUeBR8FJAUoBS8FMAUxBTIFNwU4BTwFQwVEBUUFRgVK
2941 BVEFVgVXBVgFXAVjBWQFZQVpBXAFcQVyBXcFeAV8BYMFhAWFBYkFkAWRBZIFlgWdBZ4FnwWjBaoFqwWs
2942 BbAFtwW4BbkFugW+BcUFxgXHBcgFzAXTBdQF1QXWBeAF9gX8Bf0F/gX/BgMGCwYMBg8GEQYXBhgGGQYa
2943 Bh0GJAYlBiYGJwYuBi8GMAY3BjgGOQZABkEGQgZDBq0GuAbCBscGyAbJBs4G1QbWBtgG2QbdBt4GyAbn
2944 BvAGyAbxBvgHAQbIBwIHEgcbByQHLQbIBy4HNgc9Bz4HRQdGB04HTwdQB1kGyAdaB2AHaQbIB2oHbwdw
2945 B3oHgwbIB4QHkgebB6IHowekB60HtgbIB7cHvAe/B8AHyQfKB9MGyAfUB+IH6QfqB+sH8gfzB/QH/QgG
2946 CA8GyAgQCBUIHgbICB8IJggvCDAIOQbICDoIPgg/CKkJFAl/CYAJgQmCCYMJhAmFCYYJhwmICYkJigmL
2947 CYwJjQmOCY8JkAmRCZIJkwmUCZUJlgmXCZgJmQmaCZsJnAmdCZ4JnwmgCaEJogmjCaQJpQmmCacJqAmp
2948 CaoJqwmsCa0JrgmvCbAJsQmyCbMJtAm1CbYJtwm4CbkJugm7CbwJvQm+Cb8JwAnBCcIJwwnECcUJxgnH
2949 CcgJyQnKCcsJzAnNCc4JzwnQCdEJ0gnTCdQJ1QnWCdcJ2AnZCdoJ2wncCd0J3gnfCeAJ4QniCeMJ5Anl
2950 CeYJ6QnsCoYLIAshCyILIwskCyULJgsnCygLKQsqCysLLAstCy4LLwswCzELMgszCzQLNQs2CzcLOAs5
2951 CzoLOws8Cz0LPgs/C0ALQQtCC0MLRAtFC0YLRwtIC0kLSgtLC0wLTQtOC08LUAtRC1ILUwtUC1ULVgtX
2952 C1gLWQtaC1sLXAtdC14LXwtgC2ELYgtjC2QLZQtmC2cLaAtpC2oLawtsC20LbgtvC3ALcQtyC3MLdAt1
2953 C3YLdwt4C3kLegt7C3wLfQt+C38LgAuBC4ILgwuEC4ULhguHC4gLiQuKC4sLjAuNC44LjwuQC5ELkguT
2954 C5QLlQuWC5cLmAuZC5oLmwucC50LngufC6ALoQuiC6MLpAulC6YLpwuoC6kLqgurC6wLrQuuC68LsAux
2955 C7ILswu0C7ULtgu3C7oLvQvAVSRudWxs3xASAA0ADgAPABAAEQASABMAFAAVABYAFwAYABkAGgAbABwA
2956 HQAeAB8AIAAhACIAIwAkACUAJgAnACgAKQAqACsALAAtAC4ALwAwVk5TUm9vdFYkY2xhc3NdTlNPYmpl
2957 Y3RzS2V5c18QD05TQ2xhc3Nlc1ZhbHVlc18QGU5TQWNjZXNzaWJpbGl0eU9pZHNWYWx1ZXNdTlNDb25u
2958 ZWN0aW9uc1tOU05hbWVzS2V5c1tOU0ZyYW1ld29ya11OU0NsYXNzZXNLZXlzWk5TT2lkc0tleXNdTlNO
2959 YW1lc1ZhbHVlc18QGU5TQWNjZXNzaWJpbGl0eUNvbm5lY3RvcnNdTlNGb250TWFuYWdlcl8QEE5TVmlz
2960 aWJsZVdpbmRvd3NfEA9OU09iamVjdHNWYWx1ZXNfEBdOU0FjY2Vzc2liaWxpdHlPaWRzS2V5c1lOU05l
2961 eHRPaWRcTlNPaWRzVmFsdWVzgAKBAv2BAZuBAmCBAvyArYEB9oAFgQJfgQJhgQH3gQL6gACABoEB9YEC
2962 +xEBv4ECYtIADgAyADMANFtOU0NsYXNzTmFtZYAEgANdTlNBcHBsaWNhdGlvbtIANwA4ADkAOlgkY2xh
2963 c3Nlc1okY2xhc3NuYW1logA6ADteTlNDdXN0b21PYmplY3RYTlNPYmplY3RfEBBJQkNvY29hRnJhbWV3
2964 b3Jr0gAOAD4APwBAWk5TLm9iamVjdHOAK6EAQYAH2wBDAA4ARABFAEYARwBIAEkASgBLAEwATQBOAE8A
2965 UABRAFIAUwBUAFUAVgArXE5TV2luZG93Vmlld1xOU1NjcmVlblJlY3RfEBNOU0ZyYW1lQXV0b3NhdmVO
2966 YW1lXU5TV2luZG93VGl0bGVZTlNXVEZsYWdzXU5TV2luZG93Q2xhc3NcTlNXaW5kb3dSZWN0XxAPTlNX
2967 aW5kb3dCYWNraW5nXxARTlNXaW5kb3dTdHlsZU1hc2tbTlNWaWV3Q2xhc3OAC4CsgKqAq4AJEnQAAACA
2968 CoAIEAIQD4AAXxAYe3szMzUsIDQxM30sIHs3MjUsIDMzN319XxAQSVB5dGhvbjEgKENvY29hKVhOU1dp
2969 bmRvd9cAXAAOAF0AXgBfAFoAYABhAGIAYwBkAGUAYQBnXxAPTlNOZXh0UmVzcG9uZGVyWk5TU3Vidmll
2970 d3NYTlN2RmxhZ3NbTlNGcmFtZVNpemVbTlNTdXBlcnZpZXeADIBdgA0RAQCAqIAMgKnSAA4APgBpAGqA
2971 NKIAawBsgA6Ao9oAXAAOAG4AXQBeAG8AcABaAGAAcQBNAHMAdAB1AHYAdwBVAGEATQB6V05TRnJhbWVe
2972 TlNBdXRvc2F2ZU5hbWVeTlNEaXZpZGVyU3R5bGVcTlNJc1ZlcnRpY2FsgAuAooCggA8RARKAoYAMgAsJ
2973 0gAOAD4AaQB9gDSiAH4Af4AQgGreAFwAgQAOAIIAgwBdAF4AXwCEAFoAhQCGAGAAhwBrAIkAigCLAIwA
2974 jQCOAI8AkABhAJIAVQBrAJRZTlNCb3hUeXBlW05TVGl0bGVDZWxsXU5TVHJhbnNwYXJlbnRcTlNCb3Jk
2975 ZXJUeXBlWU5TT2Zmc2V0c18QD05TVGl0bGVQb3NpdGlvbl1OU0NvbnRlbnRWaWV3gA4QAIBpgGAIgBEQ
2976 FoBeEAGADIBfgA6AEtIADgA+AGkAl4A0oQCUgBLXAFwADgBuAF0AXgBaAGAAfgBiAJwAnQBkAGEAfoAQ
2977 gF2AXIATgAyAENIADgA+AGkAooA0oQCjgBTcAFwApQAOAG4ApgBdAF4AWgBgAKcAqACHAJQAqgCrAKwA
2978 rQCuAHYAYQCUALEAsgCyW05TSFNjcm9sbGVyWE5Tc0ZsYWdzW05TVlNjcm9sbGVyXU5TTmV4dEtleVZp
2979 ZXeAEoBYgFuAWhECEoAVgAyAEoBUgBaAFtIADgA+AGkAtoA0owCyALEAqoAWgFSAWN0AXAAOAG4AuwC8
2980 AL0AXQBeAL4AWgC/AGAAqACjAMEAwgDDAMQAxQDGAMcAyABhAMoAowDIWE5TQm91bmRzWE5TQ3Vyc29y
2981 WU5TY3ZGbGFnc1lOU0RvY1ZpZXdZTlNCR0NvbG9ygBSAU4BNgE6AUBAEgBcRCQCAGIAMgE+AFIAY0gAO
2982 AD4AaQDPgDShAMiAGN0AXAAOAG4A0gDTANQA1QBeANYAWgDXAGAA2ACyANoA2wDcAN0A3gDfAOAA4QBh
2983 AOMAsgArXxAPTlNUZXh0Q29udGFpbmVyWU5TVFZGbGFnc1xOU1NoYXJlZERhdGFbTlNEcmFnVHlwZXNZ
2984 TlNNYXhTaXplWE5TTWluaXplWk5TRGVsZWdhdGWAFoBMgCyALRAGgDeAGREJEoBKgAyAS4AWgADSAA4A
2985 PgA/AOiAK68QEQDpAOoA6wDsAO0A7gDvAPAA8QDyAPMA9AD1APYA9wD4APmAGoAbgByAHYAegB+AIIAh
2986 gCKAI4AkgCWAJoAngCiAKYAqXxAZTmVYVCBSVEZEIHBhc3RlYm9hcmQgdHlwZV8QEk5TU3RyaW5nUGJv
2987 YXJkVHlwZV8QGk5lWFQgcnVsZXIgcGFzdGVib2FyZCB0eXBlXxAeTmVYVCBUSUZGIHY0LjAgcGFzdGVi
2988 b2FyZCB0eXBlXxAZQXBwbGUgVVJMIHBhc3RlYm9hcmQgdHlwZV8QI0NvcmVQYXN0ZWJvYXJkRmxhdm9y
2989 VHlwZSAweDZENkY2Rjc2XxAjQ29yZVBhc3RlYm9hcmRGbGF2b3JUeXBlIDB4NzU3MjZDMjBfEBtXZWJV
2990 UkxzV2l0aFRpdGxlc1Bib2FyZFR5cGVfEBlBcHBsZSBQREYgcGFzdGVib2FyZCB0eXBlXxAZQXBwbGUg
2991 UE5HIHBhc3RlYm9hcmQgdHlwZV8QGkFwcGxlIEhUTUwgcGFzdGVib2FyZCB0eXBlXxAVTlNGaWxlbmFt
2992 ZXNQYm9hcmRUeXBlXxAXTlNDb2xvciBwYXN0ZWJvYXJkIHR5cGVfEDFOZVhUIEVuY2Fwc3VsYXRlZCBQ
2993 b3N0U2NyaXB0IHYxLjIgcGFzdGVib2FyZCB0eXBlXxAaQXBwbGUgUElDVCBwYXN0ZWJvYXJkIHR5cGVf
2994 EBlOZVhUIGZvbnQgcGFzdGVib2FyZCB0eXBlXxAqTmVYVCBSaWNoIFRleHQgRm9ybWF0IHYxLjAgcGFz
2995 dGVib2FyZCB0eXBl0gA3ADgBDAENowENAQ4AO1xOU011dGFibGVTZXRVTlNTZXRfEBR7ezAsIDM4fSwg
2996 ezQzMywgMTR9fdUBEQAOARIBEwEUAJABFQDIARcBGFlOU1RDRmxhZ3NaTlNUZXh0Vmlld1dOU1dpZHRo
2997 XxAPTlNMYXlvdXRNYW5hZ2VygDaAGCNAexAAAAAAAIAu1QAOARoBGwEcANgBHQEeAR8A3QArXxAQTlNU
2998 ZXh0Q29udGFpbmVyc11OU1RleHRTdG9yYWdlWU5TTE1GbGFnc4A1gDOAL4AA0wAOASIA2AEjASQAK1hO
2999 U1N0cmluZ4AygDCAANIADgEnASgBKVlOUy5zdHJpbmeAMVDSADcAOAErASyjASwBIgA7XxAPTlNNdXRh
3000 YmxlU3RyaW5n0gA3ADgBLgEbpAEbAS8BMAA7XxAZTlNNdXRhYmxlQXR0cmlidXRlZFN0cmluZ18QEk5T
3001 QXR0cmlidXRlZFN0cmluZ9IADgA+AGkBM4A0oQDcgC3SADcAOAE2ATejATcBOAA7Xk5TTXV0YWJsZUFy
3002 cmF5V05TQXJyYXnSADcAOAE6ARSiARQAO9IANwA4ATwA0qIA0gA72AAOAT4BPwFAAUEBQgFDAUQBRQFG
3003 ACsBSAFJAUoAKwFMV05TRmxhZ3NfEBdOU0RlZmF1bHRQYXJhZ3JhcGhTdHlsZV8QEE5TSW5zZXJ0aW9u
3004 Q29sb3JfEBFOU0JhY2tncm91bmRDb2xvcl8QFE5TU2VsZWN0ZWRBdHRyaWJ1dGVzXxASTlNNYXJrZWRB
3005 dHRyaWJ1dGVzXxAQTlNMaW5rQXR0cmlidXRlc4BJEgAFS2+AAIA6gDiAO4AAgEXTAA4BTgFPAVAAVQFR
3006 XE5TQ29sb3JTcGFjZVVOU1JHQoA5TxAYMSAwLjk1Mjk0MTI0IDAuODUwOTgwNDYA0gA3ADgBUwFUogFU
3007 ADtXTlNDb2xvctMADgFOAVYBUAFYAVlXTlNXaGl0ZYA5EANCMADTAA4BWwA+AVwBXQFgV05TLmtleXOA
3008 RKIBXgFfgDyAPaIBYQFigD6AQtUADgFUAU4BZAFlAVABZwDdAWgBaVtOU0NvbG9yTmFtZV1OU0NhdGFs
3009 b2dOYW1lgDmAQYBAgD9WU3lzdGVtXxAbc2VsZWN0ZWRUZXh0QmFja2dyb3VuZENvbG9y0wAOAU4BVgFQ
3010 AVgBboA5SzAuNjY2NjY2NjkA1QAOAVQBTgFkAWUBUAFIAN0BcgFpgDmAOoBDgD9fEBFzZWxlY3RlZFRl
3011 eHRDb2xvctIANwA4AXYBd6IBdwA7XE5TRGljdGlvbmFyedMADgFbAD4BXAF6AX2ARKIBewFfgEaAPaIB
3012 fgF/gEeASFtOU1VuZGVybGluZdMADgFOAU8BUACQAYOAOUYwIDAgMQDSADcAOAGFAYaiAYYAO18QFE5T
3013 VGV4dFZpZXdTaGFyZWREYXRhXHs0ODAsIDFlKzA3fVd7ODQsIDB90gA3ADgBigESpQESAYsBjAGNADtW
3014 TlNUZXh0Vk5TVmlld1tOU1Jlc3BvbmRlcl8QFHt7MSwgMX0sIHs0MzMsIDIzMX19XxAVe3swLCAzOH0s
3015 IHs0MzMsIDIzMX190wAOAU4BVgFQAVgBkoA5QjEA0wAOAZQBlQGWAZcAkFlOU0hvdFNwb3RcTlNDdXJz
3016 b3JUeXBlgFKAUVd7NCwgLTV90gA3ADgBmgC8ogC8ADvSADcAOAGcAZ2kAZ0BjAGNADtaTlNDbGlwVmll
3017 d9kAXAGfAA4AbgBeAFoBoABgAaEAowCjAaQBpQGmAGEBqACjAapYTlNUYXJnZXRYTlNBY3Rpb25ZTlNQ
3018 ZXJjZW50gBSAFIBXgFUT/////4AAAQCADIBWgBQjP9Ww0wAAAABfEBV7ezQyNywgMX0sIHsxNSwgMjYz
3019 fX1cX2RvU2Nyb2xsZXI60gA3ADgBrgGvpQGvAbABjAGNADtaTlNTY3JvbGxlcllOU0NvbnRyb2zbAFwB
3020 nwAOAG4ApgBeAFoBoABgAbIBoQCjAKMBpAG2AJAAZABhAagAowG6AbtaTlNDdXJWYWx1ZYAUgBSAV4BZ
3021 gAyAVoAUIz/wAAAAAAAAIz/uQshgAAAAXxAYe3stMTAwLCAtMTAwfSwgezg3LCAxOH19XxAWe3sxOCwg
3022 MTR9LCB7NDM1LCAyMzN9fdIANwA4Ab8BwKQBwAGMAY0AO1xOU1Njcm9sbFZpZXdfEBR7ezEsIDF9LCB7
3023 NDcxLCAyNTd9fdIANwA4AcMBjKMBjAGNADtaezQ3MywgMjczfVZ7MCwgMH3XAccADgFBAcgByQHKAcsB
3024 zAHNAc4BzwHQAIkB0VtOU0NlbGxGbGFnc1pOU0NvbnRlbnRzWU5TU3VwcG9ydFxOU0NlbGxGbGFnczJb
3025 TlNUZXh0Q29sb3ISBAH+AIBogGWAYYBigGdXQ29uc29sZdQADgHUAdUB1gHXAdgB2QHaVk5TU2l6ZVZO
3026 U05hbWVYTlNmRmxhZ3OAZCNAJgAAAAAAAIBjEQwcXEx1Y2lkYUdyYW5kZdIANwA4Ad0B3qIB3gA7Vk5T
3027 Rm9udNUADgFUAU4BZAFlAVAAygDdAeIBaYA5gE+AZoA/XxATdGV4dEJhY2tncm91bmRDb2xvctMADgFO
3028 AVYBUAFYAeeAOU0wIDAuODAwMDAwMDEA0gA3ADgB6QHqpAHqAesB7AA7XxAPTlNUZXh0RmllbGRDZWxs
3029 XE5TQWN0aW9uQ2VsbFZOU0NlbGzSADcAOAHuAe+kAe8BjAGNADtVTlNCb3jeAFwAgQAOAIIAbgCDAF0A
3030 XgCEAFoAhQCGAGAAhwBrAIkAigHzAfQAjAH2AfcAkABhAJIAVQBrAfuADoBpgJ2AnAiAaxAzgAyAX4AO
3031 gGzSAA4APgBpAf6ANKEB+4Bs1wBcAA4AbgBdAF4AWgBgAH8AYgIDAgQAZABhAH+AaoBdgJuAbYAMgGrS
3032 AA4APgBpAgmANKECCoBu3xAPAFwApQAOAG4ApgIMAg0AXQIOAF4AWgBgAKcAqACHAfsCEACrAhICEwIU
3033 AhUCFgIXAHYAYQH7AhoCGwIbXE5TQ29ybmVyVmlld18QEE5TSGVhZGVyQ2xpcFZpZXdcTlNTY3JvbGxB
3034 bXRzgGyAloBbgJoQMoB4gHWAb08QEEEgAABBIAAAQZgAAEGYAACADIBsgJSAcIBw0gAOAD4AaQIfgDSl
3035 AhsCGgIQAhUCFIBwgJSAloB1gHjbAFwADgBuAL0AXQBeAL4AWgC/AGAAqAIKAMECKADFAikAxwIqAGEC
3036 LAIKAiqAboBTgJOAcYBygAyAhoBugHLSAA4APgBpAjGANKECKoBy3xAVAFwCNAAOAjUCNgFBAjcCDAI4
3037 AjkCOgBeAF8COwBaAjwCPQBgAj4CPwJAAhsAiQJCAkMCRADKAHoCFAJIAMUCSQBkAkoAegBhAk0AkAIb
3038 Ak8AVgJQXxAfTlNEcmFnZ2luZ1NvdXJjZU1hc2tGb3JOb25Mb2NhbFlOU1R2RmxhZ3NcTlNIZWFkZXJW
3039 aWV3XxASTlNBbGxvd3NUeXBlU2VsZWN0XxAXTlNJbnRlcmNlbGxTcGFjaW5nV2lkdGhfEBlOU0NvbHVt
3040 bkF1dG9yZXNpemluZ1N0eWxlXxAYTlNJbnRlcmNlbGxTcGFjaW5nSGVpZ2h0WU5TRW5hYmxlZFtOU0dy
3041 aWRDb2xvcl8QD05TR3JpZFN0eWxlTWFza15OU1RhYmxlQ29sdW1uc18QHE5TRHJhZ2dpbmdTb3VyY2VN
3042 YXNrRm9yTG9jYWxbTlNSb3dIZWlnaHSAcICSE//////WwAAAgHSATwmAeCNACAAAAAAAACNAAAAAAAAA
3043 AIBzCYAMgI+AcIB7I0AxAAAAAAAAWnsxNTYsIDIwMH3XAFwADgBeAF8AWgBgAlMCFQJVAGQCVgBhAhUC
3044 KltOU1RhYmxlVmlld4B1gHeAdoAMgHWActsAXAAOAG4AvQBdAF4AvgBaAL8AYACoAgoAwQJdAMUCXgDH
3045 AkQAYQIsAgoCRIBugFOAmYCYgHSADICGgG6AdFl7MTU2LCAxN33SADcAOAJmAmekAmcBjAGNADtfEBFO
3046 U1RhYmxlSGVhZGVyVmlld9YAXAAOAG4AXgBaAGACCgJqAmsAZABhAgqAboB6gHmADIBuXxAUe3sxNTcs
3047 IDB9LCB7MTYsIDE3fX3SADcAOAJwAnGkAnEBjAGNADtdX05TQ29ybmVyVmlld9IADgA+AGkCdIA0ogJ1
3048 AnaAfICL2gJ4AA4CeQETAnoCewJ8An0CfgJTAHoCgAKBAoICgwFYAoQChQB6AipeTlNJc1Jlc2l6ZWFi
3049 bGVcTlNIZWFkZXJDZWxsWk5TRGF0YUNlbGxeTlNSZXNpemluZ01hc2taTlNNaW5XaWR0aFpOU01heFdp
3050 ZHRoXE5TSXNFZGl0YWJsZQmAioB9I0BRwAAAAAAAgIMjQEQAAAAAAAAjQI9AAAAAAAAJgHLXAccADgFB
3051 AcgByQHKAcsCiQKKAosCjAHQAIkCjhIEgf4AgIKAf4B+gGKAgFhWYXJpYWJsZdMADgFOAVYBUAFYApKA
3052 OUswLjMzMzMzMjk5ANUADgFUAU4BZAFlAVABSADdApYBaYA5gDqAgYA/XxAPaGVhZGVyVGV4dENvbG9y
3053 0gA3ADgCmgKbpQKbAeoB6wHsADtfEBFOU1RhYmxlSGVhZGVyQ2VsbNgBxwAOAUEByAHJAp0BygHLAp4B
3054 zQIsAqECogIqAqQCpV1OU0NvbnRyb2xWaWV3EhQh/kCAaICGgISAhYByEQgAgIhZVGV4dCBDZWxs1AAO
3055 AdQB1QHWAdcCqQHZAquAZCNAKgAAAAAAAIBjEQQU1QAOAVQBTgFkAWUBUAFnAN0CrwFpgDmAQYCHgD9f
3056 EBZjb250cm9sQmFja2dyb3VuZENvbG9y1QAOAVQBTgFkAWUBUAFIAN0CtQFpgDmAOoCJgD9fEBBjb250
3057 cm9sVGV4dENvbG9y0gA3ADgCuQK6ogK6ADtdTlNUYWJsZUNvbHVtbtoCeAAOAnkBEwJ6AnsCfAJ9An4C
3058 UwB6AoACvgK/AsABWAKEAoUAegIqCYCKgIwjQFPAAAAAAACAjgmActcBxwAOAUEByAHJAcoBywKJAooC
3059 iwLGAdAAiQKOgIKAf4CNgGKAgFVWYWx1ZdgBxwAOAUEByAHJAp0BygHLAp4BzQIsAqECogIqAqQCpYBo
3060 gIaAhICFgHKAiNUADgFUAU4BZAFlAVAC0wDdAtQBaYA5gJGAkIA/WWdyaWRDb2xvctMADgFOAVYBUAFY
3061 AtmAOUQwLjUA0gA3ADgC2wJTpQJTAbABjAGNADtfEBV7ezEsIDE3fSwgezE1NiwgMjAwfX3ZAFwBnwAO
3062 AG4AXgBaAaAAYAGhAgoCCgGkAuEAZABhAagCCgLlgG6AboBXgJWADIBWgG4jP+/gP4AAAABfEBZ7ezE1
3063 NywgMTd9LCB7MTUsIDIwMH192gBcAZ8ADgBuAKYAXgBaAaAAYAGhAgoCCgGkAusAkABkAGEBqAIKAu+A
3064 boBugFeAl4AMgFaAbiM/5vlvoAAAAF8QFXt7MSwgMjE3fSwgezE1NiwgMTV9fdIADgA+AGkC84A0oQJE
3065 gHRfEBN7ezEsIDB9LCB7MTU2LCAxN319XxAWe3sxOCwgMTR9LCB7MTczLCAyMzN9fV8QFHt7MSwgMX0s
3066 IHsyMDksIDI1N319XxAWe3s0NzQsIDB9LCB7MjExLCAyNzN9fdcBxwAOAUEByAHJAcoBywHMAc0BzgL8
3067 AdAAiQL+gGiAZYCegGKAn1lXb3Jrc3BhY2XTAA4BTgFWAVABWAHngDlfEBZ7ezIwLCA0NH0sIHs2ODUs
3068 IDI3M319XxAgaXB5dGhvbjFfY29uc29sZV93b3Jrc3BhY2Vfc3BsaXTSADcAOAMFAwakAwYBjAGNADtb
3069 TlNTcGxpdFZpZXfaAFwADgBuAwgDCQBeAFoDCgBgAwsATQMNAw4DDwMQAxEAYQMTAE0DFVpOU01heFZh
3070 bHVlWk5TTWluVmFsdWVZTlNwaUZsYWdzXE5TRHJhd01hdHJpeIALgKeApiNAWQAAAAAAACNAMAAAAAAA
3071 ABEFIYAMEXEKgAuApNEADgMXgKXSADcAOAMZAxqiAxoAO1pOU1BTTWF0cml4XxAVe3s2ODksIDIwfSwg
3072 ezE2LCAxNn190gA3ADgDHQMepAMeAYwBjQA7XxATTlNQcm9ncmVzc0luZGljYXRvclp7NzI1LCAzMzd9
3073 XxAVe3swLCAwfSwgezEyODAsIDc3OH19XxAQaXB5dGhvbjFfc2FuZGJveNIANwA4AyMDJKIDJAA7XxAQ
3074 TlNXaW5kb3dUZW1wbGF0ZdIADgA+AGkDJ4A0rxAvAygDKQMqAysDLAMtAy4DLwMwAzEDMgMzAzQDNQM2
3075 AzcDOAM5AzoDOwM8Az0DPgM/A0ADQQNCA0MDRANFA0YDRwNIA0kDSgNLA0wDTQNOA08DUANRA1IDUwNU
3076 A1UDVoCugLyAwoDIgM6A1IDZgN+A44DogOyA8YD2gPuBAQGBAQaBAQqBAQ+BARWBARqBAR+BASWBASqB
3077 AS+BATWBATmBAT2BAUKBAUOBAUiBAUqBAU+BAVSBAViBAVyBAV6BAWKBAWaBAWqBAW6BAXOBAXiBAX2B
3078 AY2BAZGBAZSBAZfTAA4DWANZA1oDWwNcWE5TU291cmNlV05TTGFiZWyAu4CvgLrYAA4DXgNfA2ADYQNi
3079 A2MDZANlA2YDZwNoA2kDagNrA2xXTlNUaXRsZV8QEU5TS2V5RXF1aXZNb2RNYXNrWk5TS2V5RXF1aXZd
3080 TlNNbmVtb25pY0xvY1lOU09uSW1hZ2VcTlNNaXhlZEltYWdlVk5TTWVudYC5gLESABAAAICyEn////+A
3081 s4C3gLDUAA4DXgHVA24DbwNwA3EDcltOU01lbnVJdGVtc4EBpIEByoEB2YEBzFhTaG93IEFsbNMADgAy
3082 A3UDdgN3A3heTlNSZXNvdXJjZU5hbWWAtoC0gLVXTlNJbWFnZV8QD05TTWVudUNoZWNrbWFya9IANwA4
3083 A3wDfaIDfQA7XxAQTlNDdXN0b21SZXNvdXJjZdMADgAyA3UDdgN3A4GAtoC0gLhfEBBOU01lbnVNaXhl
3084 ZFN0YXRl0gA3ADgDhAOFogOFADtaTlNNZW51SXRlbV8QFnVuaGlkZUFsbEFwcGxpY2F0aW9uczrSADcA
3085 OAOIA4mjA4kDigA7XxAVTlNOaWJDb250cm9sQ29ubmVjdG9yXk5TTmliQ29ubmVjdG9y0wAOA1gDWQNa
3086 A40DjoC7gL2AwdgADgNeA18DYANhA2IDYwNkA2UDkQNnA5IDaQNqA2sDlYC5gL+AwICzgLeAvtMADgNe
3087 A24DbwOYA5mBAaSBAauBAa1eQ2hlY2sgU3BlbGxpbmdRO15jaGVja1NwZWxsaW5nOtMADgNYA1kDWgOf
3088 A6CAu4DDgMfYAA4DXgNfA2ADYQNiA2MDZANlA6MDZwOkA2kDagNrA6eAuYDFgMaAs4C3gMTTAA4DXgNu
3089 A28DqgOrgQGkgQHbgQHdZgBQAHIAaQBuAHQgJlFwVnByaW50OtMADgNYA1kDWgOxA7KAu4DJgM3ZAA4D
3090 XgNfA2ADYQNiA2MDZAO0A2UDtgO3A7gDaQNqA2sDuwFYVU5TVGFngLmAyxIAEgAAgMyAs4C3gMrTAA4D
3091 XgNuA28DvgO/gQGkgQHAgQHCW1NtYXJ0IExpbmtzUUdfEB10b2dnbGVBdXRvbWF0aWNMaW5rRGV0ZWN0
3092 aW9uOtMADgNYA1kDWgPFA8aAu4DPgNPYAA4DXgNfA2ADYQNiA2MDZANlA8kDtwPKA2kDagNrA82AuYDR
3093 gNKAs4C3gNDTAA4DXgNuA28D0APRgQGkgQGvgQGxVFJlZG9RWlVyZWRvOtMADgNYA1kDWgPXA9iAu4DV
3094 gNjYAA4DXgNfA2ADYQNiA2MDZANlA9sDZwNoA2kDagNrA9+AuYDXgLKAs4C3gNbTAA4DXgNuA28D4gPj
3095 gQGkgQHEgQHGXlN0YXJ0IFNwZWFraW5nXnN0YXJ0U3BlYWtpbmc60wAOA1gDWQNaA+gD6YC7gNqA3tgA
3096 DgNeA18DYANhA2IDYwNkA2UD7ANnA+0DaQNqA2sD8IC5gNyA3YCzgLeA29MADgNeA24DbwPzA/SBAaSB
3097 AfGBAfNfEBRJUHl0aG9uMVNhbmRib3ggSGVscFE/WXNob3dIZWxwOtMADgNYA1kDWgP6A/uAu4DggOLY
3098 AA4DXgNfA2ADYQNiA2MDZANlA/4DZwNoA2kDagNrA5WAuYDhgLKAs4C3gL5fEBtDaGVjayBTcGVsbGlu
3099 ZyBXaGlsZSBUeXBpbmdfEB50b2dnbGVDb250aW51b3VzU3BlbGxDaGVja2luZzrTAA4DWANZA1oEBwQI
3100 gLuA5IDn2AAOA14DXwNgA2EDYgNjA2QDZQQLA2cEDANpA2oDawPNgLmA5YDmgLOAt4DQWlNlbGVjdCBB
3101 bGxRYVpzZWxlY3RBbGw60wAOA1gDWQNaBBUEFoC7gOmA69gADgNeA18DYANhA2IDYwNkA2UEGQNnA2gD
3102 aQNqA2sDlYC5gOqAsoCzgLeAvl8QG0NoZWNrIEdyYW1tYXIgV2l0aCBTcGVsbGluZ18QFnRvZ2dsZUdy
3103 YW1tYXJDaGVja2luZzrTAA4DWANZA1oEIgQjgLuA7YDw2AAOA14DXwNgA2EDYgNjA2QDZQQmA2cDaANp
3104 A2oDawQqgLmA74CygLOAt4Du0wAOA14DbgNvBC0ELoEBpIEB54EB6W8QEgBDAHUAcwB0AG8AbQBpAHoA
3105 ZQAgAFQAbwBvAGwAYgBhAHIgJl8QH3J1blRvb2xiYXJDdXN0b21pemF0aW9uUGFsZXR0ZTrTAA4DWANZ
3106 A1oEMwQ0gLuA8oD12AAOA14DXwNgA2EDYgNjA2QDZQQ3BDgEOQNpA2oDawQqgLmA8xIAGAAAgPSAs4C3
3107 gO5cU2hvdyBUb29sYmFyUXRfEBN0b2dnbGVUb29sYmFyU2hvd2460wAOA1gDWQNaBEIEQ4C7gPeA+tgA
3108 DgNeA18DYANhA2IDYwNkA2UERgNnBEcDaQNqA2sDp4C5gPiA+YCzgLeAxFRTYXZlUXNdc2F2ZURvY3Vt
3109 ZW50OtQADgRPA1gDWQRQBFEEUgRTXU5TRGVzdGluYXRpb26BAQCA/YD8gP/SAA4AMgAzADSABIAD0gAO
3110 ADIAMwRZgASA/l8QGklQeXRob24xU2FuZGJveEFwcERlbGVnYXRlWGRlbGVnYXRl0gA3ADgEXQReowRe
3111 A4oAO18QFE5TTmliT3V0bGV0Q29ubmVjdG9y0wAOA1gDWQNaBGEEYoC7gQECgQEF2QAOA14DXwNgA2ED
3112 YgNjA2QDtANlBGUDZwRmA2kDagNrA7sAVYC5gQEDgQEEgLOAt4DKXFNtYXJ0IFF1b3Rlc1FnXxAhdG9n
3113 Z2xlQXV0b21hdGljUXVvdGVTdWJzdGl0dXRpb2461AAOBE8DWANZBFAEbwRRBHGBAQCBAQeA/YEBCdIA
3114 DgAyADMEdIAEgQEIXxAWSVB5dGhvbkNvY29hQ29udHJvbGxlcl8QEWlweXRob25Db250cm9sbGVy0wAO
3115 A1gDWQNaBHkEeoC7gQELgQEO2AAOA14DXwNgA2EDYgNjA2QDZQR9A2cEfgNpA2oDawNsgLmBAQyBAQ2A
3116 s4C3gLBfEBRRdWl0IElQeXRob24xU2FuZGJveFFxWnRlcm1pbmF0ZTrTAA4DWANZA1oEhwSIgLuBARCB
3117 ARTYAA4DXgNfA2ADYQNiA2MDZANlBIsDZwSMA2kDagNrBI+AuYEBEoEBE4CzgLeBARHUAA4DXgHVA24D
3118 bwSSBJMElIEBpIEB64EB74EB7VhNaW5pbWl6ZVFtXxATcGVyZm9ybU1pbmlhdHVyaXplOtMADgNYA1kD
3119 WgSaBJuAu4EBFoEBGdgADgNeA18DYANhA2IDYwNkA2UEngNnBJ8DaQNqA2sDzYC5gQEXgQEYgLOAt4DQ
3120 VFVuZG9RelV1bmRvOtMADgNYA1kDWgSoBKmAu4EBG4EBHtgADgNeA18DYANhA2IDYwNkA2UErANnBK0D
3121 aQNqA2sDlYC5gQEcgQEdgLOAt4C+bgBTAGgAbwB3ACAAUwBwAGUAbABsAGkAbgBnICZROl8QD3Nob3dH
3122 dWVzc1BhbmVsOtMADgNYA1kDWgS2BLeAu4EBIIEBJNgADgNeA18DYANhA2IDYwNkA2UEugO3BLsDaQNq
3123 A2sEvoC5gQEigQEjgLOAt4EBIdMADgNeA24DbwTBBMKBAaSBAZ+BAaFbU2hvdyBDb2xvcnNRQ18QFW9y
3124 ZGVyRnJvbnRDb2xvclBhbmVsOtMADgNYA1kDWgTIBMmAu4EBJoEBKdgADgNeA18DYANhA2IDYwNkA2UE
3125 zAQ4BM0DaQNqA2sDbIC5gQEngQEogLOAt4CwW0hpZGUgT3RoZXJzUWhfEBZoaWRlT3RoZXJBcHBsaWNh
3126 dGlvbnM60wAOA1gDWQNaBNYE14C7gQErgQEu2AAOA14DXwNgA2EDYgNjA2QDZQTaA2cE2wNpA2oDawPN
3127 gLmBASyBAS2As4C3gNBUQ29weVFjVWNvcHk60wAOA1gDWQNaBOQE5YC7gQEwgQE02QAOA14DXwNgA2ED
3128 YgNjA2QDtANlBOgDZwTpA2kDagNrBOwAkIC5gQEygQEzgLOAt4EBMdMADgNeA24DbwTvBPCBAaSBAbWB
3129 AbdlAEYAaQBuAGQgJlFmXxAXcGVyZm9ybUZpbmRQYW5lbEFjdGlvbjrTAA4DWANZA1oE9gT3gLuBATaB
3130 ATjYAA4DXgNfA2ADYQNiA2MDZANlBPoDZwNoA2kDagNrA9+AuYEBN4CygLOAt4DWXVN0b3AgU3BlYWtp
3131 bmddc3RvcFNwZWFraW5nOtQADgRPA1gDWQNaAB8FBAUFgLuAAoEBOoEBPNcADgNeA2ADYQNiA2MDZANl
3132 BQgDaANpA2oDawNsgLmBATuAsoCzgLeAsF8QFUFib3V0IElQeXRob24xU2FuZGJveF8QHW9yZGVyRnJv
3133 bnRTdGFuZGFyZEFib3V0UGFuZWw60wAOA1gDWQNaBREFEoC7gQE+gQFB2QAOBRQDXgNfA2ADYQNiA2MD
3134 ZANlA2gFFwO3BRgDaQNqA2sDp1lOU1Rvb2xUaXCAuYCygQE/gQFAgLOAt4DEXVBhZ2UgU2V0dXAuLi5R
3135 UF5ydW5QYWdlTGF5b3V0OtQADgRPA1gDWQRQBG8AQQRTgQEAgQEHgAeA/9MADgNYA1kDWgUmBSeAu4EB
3136 RIEBR9gADgNeA18DYANhA2IDYwNkA2UFKgNnBSsDaQNqA2sDzYC5gQFFgQFGgLOAt4DQVVBhc3RlUXZW
3137 cGFzdGU61AAOBE8DWANZBFAAyABBBTaBAQCAGIAHgQFJXxAVaW5pdGlhbEZpcnN0UmVzcG9uZGVy0wAO
3138 A1gDWQNaBToFO4C7gQFLgQFO2AAOA14DXwNgA2EDYgNjA2QDZQU+A2cFPwNpA2oDawTsgLmBAUyBAU2A
3139 s4C3gQExXxARSnVtcCB0byBTZWxlY3Rpb25Ral8QHWNlbnRlclNlbGVjdGlvbkluVmlzaWJsZUFyZWE6
3140 0wAOA1gDWQNaBUgFSYC7gQFQgQFT2AAOA14DXwNgA2EDYgNjA2QDZQVMA2cDaANpA2oDawVQgLmBAVKA
3141 soCzgLeBAVHUAA4DXgHVA24DbwVTBVQFVYEBpIEBpYEBp4EBplpDbGVhciBNZW51XxAVY2xlYXJSZWNl
3142 bnREb2N1bWVudHM60wAOA1gDWQNaBVoFW4C7gQFVgQFX2AAOA14DXwNgA2EDYgNjA2QDZQVeA2cDaANp
3143 A2oDawPNgLmBAVaAsoCzgLeA0FZEZWxldGVXZGVsZXRlOtMADgNYA1kDWgVnBWiAu4EBWYEBW9cADgNe
3144 A2ADYQNiA2MDZANlBWsDaANpA2oDawOngLmBAVqAsoCzgLeAxF8QD1JldmVydCB0byBTYXZlZF8QFnJl
3145 dmVydERvY3VtZW50VG9TYXZlZDrUAA4ETwNYA1kEUADIBG8FdoEBAIAYgQEHgQFdWHRleHRWaWV30wAO
3146 A1gDWQNaBXoFe4C7gQFfgQFh2AAOA14DXwNgA2EDYgNjA2QDZQV+A2cDaANpA2oDawSPgLmBAWCAsoCz
3147 gLeBARFfEBJCcmluZyBBbGwgdG8gRnJvbnRfEA9hcnJhbmdlSW5Gcm9udDrTAA4DWANZA1oFhwWIgLuB
3148 AWOBAWXYAA4DXgNfA2ADYQNiA2MDZANlBYsDZwTNA2kDagNrA2yAuYEBZIEBKICzgLeAsF8QFEhpZGUg
3149 SVB5dGhvbjFTYW5kYm94VWhpZGU60wAOA1gDWQNaBZQFlYC7gQFngQFp2AAOA14DXwNgA2EDYgNjA2QD
3150 ZQWYA2cDaANpA2oDawSPgLmBAWiAsoCzgLeBARFUWm9vbVxwZXJmb3JtWm9vbTrTAA4DWANZA1oFoQWi
3151 gLuBAWuBAW3ZAA4DXgNfA2ADYQNiA2MDZAO0A2UFpQNnBOkDaQNqA2sDuwCQgLmBAWyBATOAs4C3gMpf
3152 EBBTbWFydCBDb3B5L1Bhc3RlXxAYdG9nZ2xlU21hcnRJbnNlcnREZWxldGU60wAOA1gDWQNaBa4Fr4C7
3153 gQFvgQFy2AAOA14DXwNgA2EDYgNjA2QDZQWyA7cFswNpA2oDawOngLmBAXCBAXGAs4C3gMRoAFMAYQB2
3154 AGUAIABBAHMgJlFTXxAPc2F2ZURvY3VtZW50QXM60wAOA1gDWQNaBbwFvYC7gQF0gQF32AAOA14DXwNg
3155 A2EDYgNjA2QDZQXAA2cFwQNpA2oDawPNgLmBAXWBAXaAs4C3gNBTQ3V0UXhUY3V0OtMADgNYA1kDWgXK
3156 BcuAu4EBeYEBfNgADgNeA18DYANhA2IDYwNkA2UFzgNnBc8DaQNqA2sDp4C5gQF6gQF7gLOAt4DEVUNs
3157 b3NlUXddcGVyZm9ybUNsb3NlOtcADgRPBdcF2ANYA1kF2QXaBFEF3AXdBd4F3wBVWU5TS2V5UGF0aFlO
3158 U0JpbmRpbmdfEBxOU05pYkJpbmRpbmdDb25uZWN0b3JWZXJzaW9ugQGMgP2BAYuBAYqBAX6BAYnbBeEA
3159 DgXiBeMF5AXlBeYF5wXoBekF6gB6BewAegXuAHoF8AXxAHoAegB6BfVfEBpOU0ZpbHRlclJlc3RyaWN0
3160 c0luc2VydGlvbl8QFE5TUHJlc2VydmVzU2VsZWN0aW9uXE5TSW5pdGlhbEtleVpOU0VkaXRhYmxlXk5T
3161 RGVjbGFyZWRLZXlzXk5TSW5pdGlhbFZhbHVlXxAiTlNDbGVhcnNGaWx0ZXJQcmVkaWNhdGVPbkluc2Vy
3162 dGlvbl8QGE5TU2VsZWN0c0luc2VydGVkT2JqZWN0c18QFk5TQXZvaWRzRW1wdHlTZWxlY3Rpb25fEBFO
3163 U1NvcnREZXNjcmlwdG9ycwmBAYgJgQGBCYEBf4EBggkJCYEBg9IADgA+AGkF+IA0owX5Be4F8YEBgIEB
3164 gYEBglRrZXlzU2tleVV2YWx1ZdIADgA+BgAGAYEBh6EGAoEBhNQADgYEBgUGBgYHBe4GCQB6VU5TS2V5
3165 Wk5TU2VsZWN0b3JbTlNBc2NlbmRpbmeBAYaBAYGBAYUJWGNvbXBhcmU60gA3ADgGDQYOogYOADtfEBBO
3166 U1NvcnREZXNjcmlwdG9y0gA3ADgGEAE4ogE4ADvSADcAOAYSBhOlBhMGFAYVBhYAO18QFk5TRGljdGlv
3167 bmFyeUNvbnRyb2xsZXJfEBFOU0FycmF5Q29udHJvbGxlcl8QEk5TT2JqZWN0Q29udHJvbGxlclxOU0Nv
3168 bnRyb2xsZXJfEClmaWx0ZXJQcmVkaWNhdGU6IHdvcmtzcGFjZUZpbHRlclByZWRpY2F0ZV8QD2ZpbHRl
3169 clByZWRpY2F0ZV8QGHdvcmtzcGFjZUZpbHRlclByZWRpY2F0ZdIANwA4BhsGHKMGHAOKADtfEBVOU05p
3170 YkJpbmRpbmdDb25uZWN0b3LXAA4ETwXXBdgDWANZBdkF2gRvBiAGIQXeBiMAVYEBjIEBB4EBkIEBj4EB
3171 foEBjl8QGWNvbnRlbnREaWN0aW9uYXJ5OiB1c2VyTlNfEBFjb250ZW50RGljdGlvbmFyeVZ1c2VyTlPX
3172 AA4ETwXXBdgDWANZBdkF2gXeBioF8QJ2Bi0AVYEBjIEBfoEBk4EBgoCLgQGSXxAcdmFsdWU6IGFycmFu
3173 Z2VkT2JqZWN0cy52YWx1ZV8QFWFycmFuZ2VkT2JqZWN0cy52YWx1ZdcADgRPBdcF2ANYA1kF2QXaBd4G
3174 MwXxAnUGNgBVgQGMgQF+gQGWgQGCgHyBAZVfEBp2YWx1ZTogYXJyYW5nZWRPYmplY3RzLmtleV8QE2Fy
3175 cmFuZ2VkT2JqZWN0cy5rZXnXAA4ETwXXBdgDWANZBdkF2gRvBjwGPQBsBj8AVYEBjIEBB4EBmoEBmYCj
3176 gQGYXxAZYW5pbWF0ZTogd2FpdGluZ0ZvckVuZ2luZVdhbmltYXRlXxAQd2FpdGluZ0ZvckVuZ2luZdIA
3177 DgA+BgAGRYEBh68QZwZGAGsE5AVaBkoCwAVQBk0CgwZPBlAE1gZSBlMGVARCAE0AfgPwA7sEBwT2A7EE
3178 FQP6AgoAfwNsBJoGYwOnAEECKgTIA1sEeQPfBSYFegRSBm4EbwPoAMgF3gWuBnQE7AONBUgGeAZ5BGED
3179 nwOVA80GfgQqBoAEUQaCBREEMwIaBoYGhwaIBokAqgaLBCIFBASoA9cAbAWHBcoCRAaUA8UGlgJ2ALEF
3180 OgS+BpsFvAJ1Bp4FZwagBIcFoQS2AKMGpQIQBqcGqAWUBqoGqwSPgQGcgA6BATCBAVWBAZ2AjoEBUYEB
3181 qICDgQGqgQGugQErgQGygQGegQG/gPeAC4AQgNuAyoDkgQE2gMmA6YDggG6AaoCwgQEWgQHYgMSAB4By
3182 gQEmgK+BAQuA1oEBRIEBX4D8gQHVgQEHgNqAGIEBfoEBb4EB6oEBMYC9gQFQgQHlgQHugQECgMOAvoDQ
3183 gQG4gO6BAeGA/YEBzoEBPoDygJSBAbyBAcmBAd6BAeaAWIEBtIDtgQE6gQEbgNWAo4EBY4EBeYB0gQHN
3184 gM+BAdqAi4BUgQFLgQEhgQHwgQF0gHyBAbOBAVmBAcOBARCBAWuBASCAFIEB0YCWgQHSgQGigQFngQG6
3185 gQHkgQER2gAOA14DXwauA2AGrwNhA2IDYwNkA2UDaANnAHoDaAB6A2kDagNrA2xdTlNJc1NlcGFyYXRv
3186 clxOU0lzRGlzYWJsZWSAuYCyCYCyCYCzgLeAsNoADga5A14DXwNgA2EDYgNjA2QBoANlBL4EwQNnA2gD
3187 aQNqA2sGUwbBWU5TU3VibWVudYC5gQEhgQGfgLKAs4C3gQGegQGg1AAOA14B1QNuA28GxAbFBsaBAaSB
3188 AceBAfSBAchWRm9ybWF0XnN1Ym1lbnVBY3Rpb2460gAOAD4AaQbLgDSiBqgEtoEBooEBINgADgNeA18D
3189 YANhA2IDYwNkA2UG0ANnBDkDaQNqA2sEvoC5gQGjgPSAs4C3gQEhWlNob3cgRm9udHPSADcAOAbXA2Si
3190 A2QAO1tPcGVuIFJlY2VudNIADgA+AGkG24A0oQVIgQFQXxAWX05TUmVjZW50RG9jdW1lbnRzTWVuddoA
3191 Dga5A14DXwNgA2EDYgNjA2QBoANlBVAFUwNnA2gDaQNqA2sDpwbmgLmBAVGBAaWAsoCzgLeAxIEBqdoA
3192 Dga5A14DXwNgA2EDYgNjA2QBoANlA5UDmANnA2gDaQNqA2sDzQbvgLmAvoEBq4CygLOAt4DQgQGsXxAU
3193 U3BlbGxpbmcgYW5kIEdyYW1tYXLSAA4APgBpBvOANKQEqAONA/oEFYEBG4C9gOCA6doADga5A14DXwNg
3194 A2EDYgNjA2QBoANlA80D0ANnA2gDaQNqA2sGUwcAgLmA0IEBr4CygLOAt4EBnoEBsFRFZGl00gAOAD4A
3195 aQcEgDStBJoDxQZSBbwE1gUmBVoEBwaeBosGTwZUBqCBARaAz4EBsoEBdIEBK4EBRIEBVYDkgQGzgQG0
3196 gQGqgQG/gQHD2gAOA14DXwauA2AGrwNhA2IDYwNkA2UDaANnAHoDaAB6A2kDagNrA82AuYCyCYCyCYCz
3197 gLeA0NoADgNeA18GrgNgBq8DYQNiA2MDZANlA2gDZwB6A2gAegNpA2oDawPNgLmAsgmAsgmAs4C3gNDa
3198 AA4GuQNeA18DYANhA2IDYwNkAaADZQTsBO8DZwNoA2kDagNrA80HLIC5gQExgQG1gLKAs4C3gNCBAbZU
3199 RmluZNIADgA+AGkHMIA0pQTkBn4GqgaGBTqBATCBAbiBAbqBAbyBAUvZAA4DXgNfA2ADYQNiA2MDZAO0
3200 A2UHOANnBGYDaQNqA2sE7ABVgLmBAbmBAQSAs4C3gQExWUZpbmQgTmV4dNkADgNeA18DYANhA2IDYwNk
3201 A7QDZQdAA7cDuANpA2oDawTsAViAuYEBu4DMgLOAt4EBMV1GaW5kIFByZXZpb3Vz2QAOA14DXwNgA2ED
3202 YgNjA2QDtANlB0gDZwdJA2kDagNrBOwHTYC5gQG9gQG+gLOAt4EBMRAHXxAWVXNlIFNlbGVjdGlvbiBm
3203 b3IgRmluZFFl2gAOBrkDXgNfA2ADYQNiA2MDZAGgA2UDuwO+A2cDaANpA2oDawPNB1iAuYDKgQHAgLKA
3204 s4C3gNCBAcFdU3Vic3RpdHV0aW9uc9IADgA+AGkHXIA0owWhBGEDsYEBa4EBAoDJ2gAOBrkDXgNfA2AD
3205 YQNiA2MDZAGgA2UD3wPiA2cDaANpA2oDawPNB2iAuYDWgQHEgLKAs4C3gNCBAcVWU3BlZWNo0gAOAD4A
3206 aQdsgDSiA9cE9oDVgQE2WUFNYWluTWVuddIADgA+AGkHcoA0pwaHBpYGUAZKBokGdAabgQHJgQHagQGu
3207 gQGdgQHmgQHqgQHw2gAOBrkDXgNfA2ADYQNiA2MDZAGgA2UDbANwA2cDaANpA2oDawZTB4KAuYCwgQHK
3208 gLKAs4C3gQGegQHLXxAPSVB5dGhvbjFTYW5kYm940gAOAD4AaQeGgDSrBQQGlAaCBqUGpwZGBYcEyANb
3209 BmMEeYEBOoEBzYEBzoEB0YEB0oEBnIEBY4EBJoCvgQHYgQEL2gAOA14DXwauA2AGrwNhA2IDYwNkA2UD
3210 aANnAHoDaAB6A2kDagNrA2yAuYCyCYCyCYCzgLeAsNgADgNeA18DYANhA2IDYwNkA2UHnQNnB54DaQNq
3211 A2sDbIC5gQHPgQHQgLOAt4CwbABQAHIAZQBmAGUAcgBlAG4AYwBlAHMgJlEs2gAOA14DXwauA2AGrwNh
3212 A2IDYwNkA2UDaANnAHoDaAB6A2kDagNrA2yAuYCyCYCyCYCzgLeAsNoADga5A14DXwNgA2EDYgNjA2QB
3213 oANlBm4HsANnA2gDaQNqA2sDbAe1gLmBAdWBAdOAsoCzgLeAsIEB1FhTZXJ2aWNlc9QADgNeAdUDbgNv
3214 B7AHuge7gQGkgQHTgQHXgQHW0gAOAD4AaQe+gDSgXxAPX05TU2VydmljZXNNZW512gAOA14DXwauA2AG
3215 rwNhA2IDYwNkA2UDaANnAHoDaAB6A2kDagNrA2yAuYCyCYCyCYCzgLeAsFxfTlNBcHBsZU1lbnXaAA4G
3216 uQNeA18DYANhA2IDYwNkAaADZQOnA6oDZwNoA2kDagNrBlMH0oC5gMSBAduAsoCzgLeBAZ6BAdxURmls
3217 ZdIADgA+AGkH1oA0qwaIBoAGTQarBcoEQgWuBWcGeAURA5+BAd6BAeGBAaiBAeSBAXmA94EBb4EBWYEB
3218 5YEBPoDD2AAOA14DXwNgA2EDYgNjA2QDZQfkA2cH5QNpA2oDawOngLmBAd+BAeCAs4C3gMRTTmV3UW7Y
3219 AA4DXgNfA2ADYQNiA2MDZANlB+0DZwfuA2kDagNrA6eAuYEB4oEB44CzgLeAxGUATwBwAGUAbiAmUW/a
3220 AA4DXgNfBq4DYAavA2EDYgNjA2QDZQNoA2cAegNoAHoDaQNqA2sDp4C5gLIJgLIJgLOAt4DE2gAOA14D
3221 XwauA2AGrwNhA2IDYwNkA2UDaANnAHoDaAB6A2kDagNrA6eAuYCyCYCyCYCzgLeAxNoADga5A14DXwNg
3222 A2EDYgNjA2QBoANlBCoELQNnA2gDaQNqA2sGUwgOgLmA7oEB54CygLOAt4EBnoEB6FRWaWV30gAOAD4A
3223 aQgSgDSiBDMEIoDygO3aAA4GuQNeA18DYANhA2IDYwNkAaADZQSPBJIDZwNoA2kDagNrBlMIHYC5gQER
3224 gQHrgLKAs4C3gQGegQHsVldpbmRvd9IADgA+AGkIIYA0pASHBZQGeQV6gQEQgQFngQHugQFf2gAOA14D
3225 XwauA2AGrwNhA2IDYwNkA2UDaANnAHoDaAB6A2kDagNrBI+AuYCyCYCyCYCzgLeBARFeX05TV2luZG93
3226 c01lbnXaAA4GuQNeA18DYANhA2IDYwNkAaADZQPwA/MDZwNoA2kDagNrBlMIOIC5gNuBAfGAsoCzgLeB
3227 AZ6BAfJUSGVscNIADgA+AGkIPIA0oQPogNpbX05TTWFpbk1lbnXSAA4APgYACEGBAYevEGcDbABNBOwD
3228 zQZTAnYGTQOnAnUDzQZTA80DzQAfA80DpwBBAGsGmwZUA80D3wO7A5UDlQB/AGsGhwPNA2wGlgAfAgoD
3229 bANsA2wGoAPNBI8AHwanAB8D8ACjAB8DpwZTBosDlQVQA6cEjwO7A6cGTwZQBOwGiQOnAB8DbAOnBCoC
3230 CgTsBlMDpwZTAKMDzQQqA2wDlQPfAE0DbAOnAgoDbAPNBlMCKgCjBOwGSgZTA80CKgPNA6cDzQSPA7sE
3231 vgB+A2wCCgNsBL4EjwTsA6cGdICwgAuBATGA0IEBnoCLgQGogMSAfIDQgQGegNCA0IACgNCAxIAHgA6B
3232 AfCBAb+A0IDWgMqAvoC+gGqADoEByYDQgLCBAdqAAoBugLCAsICwgQHDgNCBARGAAoEB0oACgNuAFIAC
3233 gMSBAZ6BAbSAvoEBUYDEgQERgMqAxIEBqoEBroEBMYEB5oDEgAKAsIDEgO6AboEBMYEBnoDEgQGegBSA
3234 0IDugLCAvoDWgAuAsIDEgG6AsIDQgQGegHKAFIEBMYEBnYEBnoDQgHKA0IDEgNCBARGAyoEBIYAQgLCA
3235 boCwgQEhgQERgQExgMSBAerSAA4APgYACKuBAYevEGgAawZGBOQFWgZKAsAFUAZNAoMGTwZQBNYGUgZT
3236 BlQATQRCAH4D8AO7BAcE9gOxBBUCCgP6AH8DbABBA6cEmgZjAioEyANbBHkD3wUmBXoEUgZuBG8D6ADI
3237 Bd4AHwWuBnQGeATsA40FSAZ5BGEDnwOVA80EKgZ+BoAEUQaCBREEMwIaBoYGhwaJBogAqgaLBCIFBABs
3238 BKgD1wWHBcoCRAaUA8UGlgJ2ALEEvgU6BpsFvAJ1Bp4FZwagBIcFoQS2AKMGpQIQBqcGqAWUBqoGqwSP
3239 gA6BAZyBATCBAVWBAZ2AjoEBUYEBqICDgQGqgQGugQErgQGygQGegQG/gAuA94AQgNuAyoDkgQE2gMmA
3240 6YBugOCAaoCwgAeAxIEBFoEB2IBygQEmgK+BAQuA1oEBRIEBX4D8gQHVgQEHgNqAGIEBfoACgQFvgQHq
3241 gQHlgQExgL2BAVCBAe6BAQKAw4C+gNCA7oEBuIEB4YD9gQHOgQE+gPKAlIEBvIEByYEB5oEB3oBYgQG0
3242 gO2BATqAo4EBG4DVgQFjgQF5gHSBAc2Az4EB2oCLgFSBASGBAUuBAfCBAXSAfIEBs4EBWYEBw4EBEIEB
3243 a4EBIIAUgQHRgJaBAdKBAaKBAWeBAbqBAeSBARHSAA4APgYACRaBAYevEGgJFwkYCRkJGgkbCRwJHQke
3244 CR8JIAkhCSIJIwkkCSUJJgknCSgJKQkqCSsJLAktCS4JLwkwCTEJMgkzCTQJNQk2CTcJOAk5CToJOwk8
3245 CT0JPgk/CUAJQQlCCUMJRAlFCUYJRwlICUkJSglLCUwJTQlOCU8JUAlRCVIEWQlUCVUJVglXCVgJWQla
3246 CVsJXAldCV4JXwlgCWEJYgljCWQJZQlmCWcJaAlpCWoJawlsCW0JbglvCXAJcQlyCXMJdAl1CXYJdwl4
3247 CXkJegl7CXwJfQl+gQH4gQH5gQH6gQH7gQH8gQH9gQH+gQH/gQIAgQIBgQICgQIDgQIEgQIFgQIGgQIH
3248 gQIIgQIJgQIKgQILgQIMgQINgQIOgQIPgQIQgQIRgQISgQITgQIUgQIVgQIWgQIXgQIYgQIZgQIagQIb
3249 gQIcgQIdgQIegQIfgQIggQIhgQIigQIjgQIkgQIlgQImgQIngQIogQIpgQIqgQIrgQIsgQItgQIugQIv
3250 gQIwgQIxgQIygQIzgP6BAjSBAjWBAjaBAjeBAjiBAjmBAjqBAjuBAjyBAj2BAj6BAj+BAkCBAkGBAkKB
3251 AkOBAkSBAkWBAkaBAkeBAkiBAkmBAkqBAkuBAkyBAk2BAk6BAk+BAlCBAlGBAlKBAlOBAlSBAlWBAlaB
3252 AleBAliBAlmBAlqBAluBAlyBAl2BAl5aU3BsaXQgVmlld1tTZXBhcmF0b3ItM28QEQBNAGUAbgB1ACAA
3253 SQB0AGUAbQAgACgARgBpAG4AZCAmAClfEBJNZW51IEl0ZW0gKERlbGV0ZSlfEBJNZW51IEl0ZW0gKEZv
3254 cm1hdClfEBtUZXh0IEZpZWxkIENlbGwgKFRleHQgQ2VsbClfEBJNZW51IChPcGVuIFJlY2VudClfEBdN
3255 ZW51IEl0ZW0gKE9wZW4gUmVjZW50KV8QHVRleHQgRmllbGQgQ2VsbCAoVGV4dCBDZWxsKS0xXxAgTWVu
3256 dSBJdGVtIChTcGVsbGluZyBhbmQgR3JhbW1hcilfEBBNZW51IEl0ZW0gKEVkaXQpXxAQTWVudSBJdGVt
3257 IChDb3B5KVlTZXBhcmF0b3JYTWFpbk1lbnVfEBlNZW51IEl0ZW0gKFN1YnN0aXR1dGlvbnMpXENvbnRl
3258 bnQgVmlld1EzXUJveCAoQ29uc29sZSlRMl8QFE1lbnUgKFN1YnN0aXR1dGlvbnMpXxAWTWVudSBJdGVt
3259 IChTZWxlY3QgQWxsKV8QGU1lbnUgSXRlbSAoU3RvcCBTcGVha2luZylfEBdNZW51IEl0ZW0gKFNtYXJ0
3260 IExpbmtzKV8QJ01lbnUgSXRlbSAoQ2hlY2sgR3JhbW1hciBXaXRoIFNwZWxsaW5nKV1TY3JvbGwgVmll
3261 dy0xXxAnTWVudSBJdGVtIChDaGVjayBTcGVsbGluZyBXaGlsZSBUeXBpbmcpXxAPQm94IChXb3Jrc3Bh
3262 Y2UpXxAWTWVudSAoSVB5dGhvbjFTYW5kYm94KV8QGVdpbmRvdyAoSVB5dGhvbjEgKENvY29hKSlbTWVu
3263 dSAoRmlsZSlfEBBNZW51IEl0ZW0gKFVuZG8pW1NlcGFyYXRvci00XxAcVGFibGUgVmlldyAoVmFyaWFi
3264 bGUsIFZhbHVlKV8QF01lbnUgSXRlbSAoSGlkZSBPdGhlcnMpXxAUTWVudSBJdGVtIChTaG93IEFsbClU
3265 MTExMV1NZW51IChTcGVlY2gpXxARTWVudSBJdGVtIChQYXN0ZSlfEB5NZW51IEl0ZW0gKEJyaW5nIEFs
3266 bCB0byBGcm9udClbQXBwbGljYXRpb25fEA9NZW51IChTZXJ2aWNlcylfEBdQeXRob24gQ29jb2EgQ29u
3267 dHJvbGxlcl8QIE1lbnUgSXRlbSAoSVB5dGhvbjFTYW5kYm94IEhlbHApWVRleHQgVmlld18QGVVzZXIg
3268 TmFtZXNwYWNlIENvbnRyb2xsZXJcRmlsZSdzIE93bmVyUThfEBJNZW51IEl0ZW0gKFdpbmRvdylTMi0x
3269 W01lbnUgKEZpbmQpXxAaTWVudSBJdGVtIChDaGVjayBTcGVsbGluZylfEBZNZW51IEl0ZW0gKENsZWFy
3270 IE1lbnUpW1NlcGFyYXRvci0yXxAYTWVudSBJdGVtIChTbWFydCBRdW90ZXMpUTZfEBtNZW51IChTcGVs
3271 bGluZyBhbmQgR3JhbW1hcilbTWVudSAoRWRpdClbTWVudSAoVmlldylfEBVNZW51IEl0ZW0gKEZpbmQg
3272 TmV4dClvEBEATQBlAG4AdQAgAEkAdABlAG0AIAAoAE8AcABlAG4gJgApUzEyMVE1XxAYTWVudSBJdGVt
3273 IChTaG93IFRvb2xiYXIpXxATVmVydGljYWwgU2Nyb2xsZXItMV8QIk1lbnUgSXRlbSAoVXNlIFNlbGVj
3274 dGlvbiBmb3IgRmluZClfEBtNZW51IEl0ZW0gKElQeXRob24xU2FuZGJveClfEBBNZW51IEl0ZW0gKFZp
3275 ZXcpUTlfEBNIb3Jpem9udGFsIFNjcm9sbGVyXxAQTWVudSBJdGVtIChGaW5kKW8QHgBNAGUAbgB1ACAA
3276 SQB0AGUAbQAgACgAQwB1AHMAdABvAG0AaQB6AGUAIABUAG8AbwBsAGIAYQByICYAKV8QIU1lbnUgSXRl
3277 bSAoQWJvdXQgSVB5dGhvbjFTYW5kYm94KVxBc3luYyBBcnJvd3NvEBoATQBlAG4AdQAgAEkAdABlAG0A
3278 IAAoAFMAaABvAHcAIABTAHAAZQBsAGwAaQBuAGcgJgApXxAaTWVudSBJdGVtIChTdGFydCBTcGVha2lu
3279 ZylfECBNZW51IEl0ZW0gKEhpZGUgSVB5dGhvbjFTYW5kYm94KVMxLTFfEBFUYWJsZSBIZWFkZXIgVmll
3280 d1tTZXBhcmF0b3ItNV8QEE1lbnUgSXRlbSAoUmVkbylfEBBNZW51IEl0ZW0gKEZpbGUpXxAUVGFibGUg
3281 Q29sdW1uIChWYWx1ZSlfEBFWZXJ0aWNhbCBTY3JvbGxlcl1NZW51IChGb3JtYXQpXxAdTWVudSBJdGVt
3282 IChKdW1wIHRvIFNlbGVjdGlvbilRMV8QD01lbnUgSXRlbSAoQ3V0KV8QF1RhYmxlIENvbHVtbiAoVmFy
3283 aWFibGUpW1NlcGFyYXRvci0xUjEwXxASTWVudSBJdGVtIChTcGVlY2gpXxAUTWVudSBJdGVtIChNaW5p
3284 bWl6ZSlfEBxNZW51IEl0ZW0gKFNtYXJ0IENvcHkvUGFzdGUpXxAXTWVudSBJdGVtIChTaG93IENvbG9y
3285 cylbU2Nyb2xsIFZpZXdbU2VwYXJhdG9yLTZfEBVIb3Jpem9udGFsIFNjcm9sbGVyLTFfEBRNZW51IEl0
3286 ZW0gKFNlcnZpY2VzKV8QFk1lbnUgSXRlbSAoU2hvdyBGb250cylfEBBNZW51IEl0ZW0gKFpvb20pXxAZ
3287 TWVudSBJdGVtIChGaW5kIFByZXZpb3VzKVE3XU1lbnUgKFdpbmRvdynSAA4APgYACeiBAYeg0gAOAD4G
3288 AAnrgQGHoNIADgA+BgAJ7oEBh68QlwZGAGsDUATkBVoGSgM8Az8CwAVQBk0DLQNHA1UCgwNEBk8GUAMr
3289 A08E1gZSAygDSAZTBlQEQgBNAH4D8AO7BAcE9gOxA0kEFQP6AgoAfwNsBJoGYwOnAEECKgTIA0EDWwNG
3290 BHkD3wNKBSYFegRSAzQDTgNUA1EDMgM3A00GbgRvA+gAyAXeAB8FrgZ0BOwDjQVIBngDMQM6BnkEYQOf
3291 AzsDlQPNAzkGfgQqAykGgARRBoIFEQQzAhoDOANFAyoGhgMwBocGiAaJAKoGiwQiAywFBASoA9cAbAWH
3292 A0wFygM2AkQGlAPFBpYCdgNCAzMDLgCxBToEvgabBbwCdQaeBWcGoAM1Az0EhwNLBaEEtgM+AKMDUgal
3293 AhADVganBqgDQANDBZQGqgMvA1MGqwSPgQGcgA6BAXOBATCBAVWBAZ2BAR+BAS+AjoEBUYEBqIDUgQFP
3294 gQGUgIOBAUOBAaqBAa6AyIEBboEBK4EBsoCugQFUgQGegQG/gPeAC4AQgNuAyoDkgQE2gMmBAViA6YDg
3295 gG6AaoCwgQEWgQHYgMSAB4BygQEmgQE5gK+BAUqBAQuA1oEBXIEBRIEBX4D8gPaBAWqBAZGBAXiA7IEB
3296 BoEBZoEB1YEBB4DagBiBAX6AAoEBb4EB6oEBMYC9gQFQgQHlgOiBARWBAe6BAQKAw4EBGoC+gNCBAQ+B
3297 AbiA7oC8gQHhgP2BAc6BAT6A8oCUgQEKgQFIgMKBAbyA44EByYEB3oEB5oBYgQG0gO2AzoEBOoEBG4DV
3298 gKOBAWOBAWKBAXmBAQGAdIEBzYDPgQHagIuBAT2A8YDZgFSBAUuBASGBAfCBAXSAfIEBs4EBWYEBw4D7
3299 gQElgQEQgQFegQFrgQEggQEqgBSBAX2BAdGAloEBl4EB0oEBooEBNYEBQoEBZ4EBuoDfgQGNgQHkgQER
3300 0gAOAD4GAAqIgQGHrxCXCokKigqLCowKjQqOCo8KkAqRCpIKkwqUCpUKlgqXCpgKmQqaCpsKnAqdCp4K
3301 nwqgCqEKogqjCqQKpQqmCqcKqAqpCqoKqwqsCq0KrgqvCrAKsQqyCrMKtAq1CrYKtwq4CrkKugq7CrwK
3302 vQq+Cr8KwArBCsIKwwrECsUKxgrHCsgKyQrKCssKzArNCs4KzwrQCtEK0grTCtQK1QrWCtcK2ArZCtoK
3303 2wrcCt0K3grfCuAK4QriCuMK5ArlCuYK5wroCukK6grrCuwK7QruCu8K8ArxCvIK8wr0CvUK9gr3CvgK
3304 +Qr6CvsK/Ar9Cv4K/wsACwELAgsDCwQLBQsGCwcLCAsJCwoLCwsMCw0LDgsPCxALEQsSCxMLFAsVCxYL
3305 FwsYCxkLGgsbCxwLHQseCx+BAmOBAmSBAmWBAmaBAmeBAmiBAmmBAmqBAmuBAmyBAm2BAm6BAm+BAnCB
3306 AnGBAnKBAnOBAnSBAnWBAnaBAneBAniBAnmBAnqBAnuBAnyBAn2BAn6BAn+BAoCBAoGBAoKBAoOBAoSB
3307 AoWBAoaBAoeBAoiBAomBAoqBAouBAoyBAo2BAo6BAo+BApCBApGBApKBApOBApSBApWBApaBApeBApiB
3308 ApmBApqBApuBApyBAp2BAp6BAp+BAqCBAqGBAqKBAqOBAqSBAqWBAqaBAqeBAqiBAqmBAqqBAquBAqyB
3309 Aq2BAq6BAq+BArCBArGBArKBArOBArSBArWBAraBAreBAriBArmBArqBAruBAryBAr2BAr6BAr+BAsCB
3310 AsGBAsKBAsOBAsSBAsWBAsaBAseBAsiBAsmBAsqBAsuBAsyBAs2BAs6BAs+BAtCBAtGBAtKBAtOBAtSB
3311 AtWBAtaBAteBAtiBAtmBAtqBAtuBAtyBAt2BAt6BAt+BAuCBAuGBAuKBAuOBAuSBAuWBAuaBAueBAuiB
3312 AumBAuqBAuuBAuyBAu2BAu6BAu+BAvCBAvGBAvKBAvOBAvSBAvWBAvaBAveBAviBAvkQkBEBpRDkENEQ
3313 yhEBKxEBaRDxEQGeEH0QfBDpEH8RAawRAZ8Q4hDYENkRAWURAWsQxRDOEQFyEOsQHREBXBBLEQF0EQGk
3314 EGoRAV0QxhDDEQFiEQFsEQFaENsRAZcRAZYQORDPEJUQUREBcxEBmxCREI4QlhD1EIgQ1BEBvBDLEAUT
3315 //////////0RAWoRAWMRAasQwREBbREBuRDwEIIRAaYQbxEBoxEBgREBvhBQEBMQ3BDJEH4QShEBWxDf
3316 EFwRAV8QThDmEMgQzRAlENARASgQ4RBIEQF1EIEQTREBKREBmREBcREBvRBWEN0Q6BA4EFIRAScRAaIQ
3317 2hEBKhDnEDoQzBDEEQG0EIYRAW8QSREBZBEBmBDsENcQUxEBnRBXEQFuEQFoEQGhENIRASwQZxDHEQGc
3318 ENYQcBDTEQF2EQFwEBcQJxEBXhEBWRDgEQGgEQG4EI8RAZoRAbUQgxEBWBDjEQGtEO8Q1RDeEQGoEE8Q
3319 GNIADgA+AGkLuYA0oNIADgA+BgALvIEBh6DSAA4APgYAC7+BAYeg0gA3ADgLwQvCogvCADteTlNJQk9i
3320 amVjdERhdGEACAAZACIAJwAxADoAPwBEAFIAVABmBmYGbAa3Br4GxQbTBuUHAQcPBxsHJwc1B0AHTgdq
3321 B3gHiwedB7cHwQfOB9AH0wfWB9kH3AfeB+EH4wfmB+kH7AfvB/EH8wf2B/kH/Af/CAgIFAgWCBgIJggv
3322 CDgIQwhICFcIYAhzCHwIhwiJCIwIjgi7CMgI1QjrCPkJAwkRCR4JMAlECVAJUglUCVYJWAlaCV8JYQlj
3323 CWUJZwlpCYQJlwmgCb0JzwnaCeMJ7wn7Cf0J/woBCgQKBgoICgoKEwoVChoKHAoeCkcKTwpeCm0Kegp8
3324 Cn4KgAqCCoUKhwqJCosKjAqVCpcKnAqeCqAK2QrjCu8K/QsKCxQLJgs0CzYLOAs6CzwLPQs/C0ELQwtF
3325 C0cLSQtLC00LVgtYC1sLXQt6C3wLfguAC4ILhAuGC48LkQuUC5YLxwvTC9wL6Av2C/gL+gv8C/4MAQwD
3326 DAUMBwwJDAsMDQwWDBgMHwwhDCMMJQxaDGMMbAx2DIAMigyMDI4MkAySDJQMlgyYDJsMnQyfDKEMowyl
3327 DK4MsAyzDLUM6gz8DQYNEw0fDSkNMg09DT8NQQ1DDUUNRw1JDUsNTg1QDVINVA1WDVgNYQ1jDYgNig2M
3328 DY4NkA2SDZQNlg2YDZoNnA2eDaANog2kDaYNqA2qDcYN2w34DhkONQ5bDoEOnw67DtcO9A8MDyYPWg93
3329 D5MPwA/JD9AP3Q/jD/oQDxAZECQQLBA+EEAQQhBLEE0QYhB1EIMQjRCPEJEQkxCVEKIQqxCtEK8QsRC6
3330 EMQQxhDHENAQ1xDpEPIQ+xEXESwRNRE3EToRPBFFEUwRWxFjEWwRcRF6EX8RoBGoEcIR1RHpEgASFRIo
3331 EioSLxIxEjMSNRI3EjkSOxJIElUSWxJdEngSgRKGEo4SmxKjEqUSpxKqErcSvxLBEsYSyBLKEs8S0RLT
3332 EugS9BMCEwQTBhMIEwoTERMvEzwTPhNKE18TYRNjE2UTZxN7E4QTiROWE6MTpROqE6wTrhOzE7UTtxPD
3333 E9AT0hPZE+IT5xP+FAsUExQcFCcULhQ1FEEUWBRwFH0UfxSCFI8UmRSmFKgUqhSyFLsUwBTJFNIU3RUC
3334 FQsVFBUeFSAVIhUkFSYVLxUxFTMVNRU+FVYVYxVsFXcVghWMFbkVxBXGFcgVyhXMFc4V0BXSFdsV5BX/
3335 FhgWIRYqFjcWThZXFl4WaRZwFo0WmRakFq4WuxbHFswWzhbQFtIW1BbWFt4W7xb2Fv0XBhcIFxEXExcW
3336 FyMXLBcxFzgXTRdPF1EXUxdVF2sXeBd6F4gXkReaF6wXuRfAF8kX0hfYGBEYExgVGBcYGRgaGBwYHhgg
3337 GCIYJBgmGC8YMRg0GDYYUxhVGFcYWRhbGF0YXxhoGGoYbRhvGK4YuxjOGNsY3RjfGOEY4xjlGOcY6Rjr
3338 GP4ZABkCGQQZBhkIGREZExkeGSAZIhkkGSYZKBlVGVcZWRlbGV0ZXxlhGWMZZRlnGXAZchl1GXcZzhnw
3339 GfoaBxocGjYaUhptGncagxqVGqQawxrPGtEa0xrcGt4a4BrhGuMa7Br1Gvca+Br6Gvwa/hsAGwkbFBsx
3340 Gz0bPxtBG0MbRRtHG0kbdht4G3obfBt+G4AbghuEG4YbiBuSG5sbpBu4G9Eb0xvVG9cb2RvbG/Ib+xwE
3341 HBIcGxwdHCIcJBwmHE8cXhxrHHYchRyQHJscqBypHKscrRy2HLgcwRzKHMsczRzqHO8c8RzzHPUc9xz5
3342 HQIdDx0RHR0dMh00HTYdOB06HUwdVR1gHXQdlR2jHagdqh2sHa4dsB2yHbUdtx3BHdId1B3dHd8d4h33
3343 Hfkd+x39Hf8eGB4tHi8eMR4zHjUeSB5RHlYeZB6NHo4ekB6SHpsenR6eHqAevR6/HsEewx7FHscezR7u
3344 HvAe8h70HvYe+B76Hw8fER8THxUfFx8hHy4fMB81Hz4fSR9hH4YfiB+KH4wfjh+QH5IflB+dH7Yf3x/h
3345 H+Mf5R/nH+kf6x/tH/YgDiAXIBkgHCAeIDQgTSBkIH0gmiCcIJ4goCCiIKQgriC7IL0g1iD5IQIhCyEX
3346 IUAhSyFWIWAhbSFvIXEhcyF8IYUhiCGKIY0hjyGRIZYhmCGhIaYhsSHJIdIh2yHxIfwiFCInIjAiNSJI
3347 IlEiUyK0IrYiuCK6IrwiviLAIsIixCLGIsgiyiLMIs4i0CLTItYi2SLcIt8i4iLlIugi6yLuIvEi9CL3
3348 Ivoi/SMAIwMjBiMJIwwjDyMSIxUjGCMbIx4jISMkIycjKiMtIzAjMyNAI0kjUSNTI1UjVyN4I4AjlCOf
3349 I60jtyPEI8sjzSPPI9Qj1iPbI90j3yPhI/Ij/iQBJAQkByQKJBMkICQvJDEkMyQ1JD0kTyRYJF0kcCR9
3350 JH8kgSSDJJYknySkJK8kyCTRJNgk8CT/JQwlDiUQJRIlMyU1JTclOSU7JT0lPyVMJU8lUiVVJWQlZiV1
3351 JYIlhCWGJYglqSWrJa0lryWxJbMltSXCJcUlyCXLJdgl2iXhJe4l8CXyJfQmGSYfJiEmIyYoJiomLCYu
3352 JjAmPSZAJkMmRiZSJlQmdCaBJoMmhSaHJqgmqiasJq4msCayJrQmwSbEJscmyibPJtEm1ybkJuYm6Cbq
3353 JwsnDScPJxEnEycVJxcnJCcnJyonLSc8J0snWCdaJ1wnXid/J4EngyeFJ4cniSeLJ5gnmyeeJ6EnuCe6
3354 J8Qn0SfTJ9Un1yf4J/on/Cf+KAAoAigEKCIoQyhQKFIoVChWKHcoeSh7KH0ofyiBKIMojiiQKJsoqCiq
3355 KKworijPKNEo0yjVKNco2SjbKPkpEikfKSEpIyklKUYpSClKKUwpTilQKVIpXyliKWUpaCmPKbEpvinA
3356 KcIpxCnlKecp6SnuKfAp8in0KfYqAyoFKhsqKCoqKiwqLipPKlEqUypVKlcqWSpbKmAqYipwKoEqjyqS
3357 KpQqliqYKqEqoyqlKq4qsCqyKs8q2CrhKugq/ysMKw4rESsUKzkrOys+K0ErQytFK0crVCtWK3oriyuO
3358 K5ErkyuWK58roSukK70r0SveK+Ar4yvmLAcsCSwMLA8sESwTLBUsLCwuLDksRixILEssTixvLHEsdCx3
3359 LHkseyx+LI8skiyVLJgsmyykLKYsvCzJLMsszizRLPIs9Cz3LPos/Cz+LQAtBS0HLQ0tGi0cLR8tIi1D
3360 LUUtSC1LLU0tTy1RLW4tcC2CLY8tkS2ULZctuC26Lb0twC3CLcQtxy3ULdct2i3dLekt6y4DLhAuEi4V
3361 LhguOS47Lj4uQS5DLkUuRy5TLlUubi57Ln0ugC6DLqQupi6pLqwuri6wLrIuty65Lr8uzC7OLtEu1C75
3362 Lvsu/i8BLwMvBS8ILxUvGC8bLx4vKS8rL0UvUi9UL1cvWi97L30vgC+CL4Qvhi+IL5YvpC+1L7cvuS+8
3363 L78v3C/eL+Ev4y/lL+cv6TABMCEwLjAwMDMwNjBbMGUwZzBpMGwwbzBxMHMwdTCDMIUwlDClMKgwqzCt
3364 MK8wvDC+MMEwxDDlMOcw6jDtMO8w8TDzMPkw+zECMRMxFjEYMRoxHTE1MUIxRDFHMUoxazFtMXAxczF1
3365 MXcxejGOMZAxsDG9Mb8xwjHFMeYx6DHrMe0x7zHxMfQyBTIIMgsyDjIRMhwyNDJBMkMyRjJJMmoybDJv
3366 MnEyczJ1MncyfjKGMpMylTKYMpsyuDK6Mr0yvzLBMsMyxTLXMvAzATMEMwYzCTMMMxUzIjMkMyczKjNL
3367 M00zUDNSM1QzVjNZM24zgDONM48zkjOVM7YzuDO7M74zwDPCM8Qz2zPhM+4z8DPzM/Y0FzQZNBw0HjQg
3368 NCI0JTQqNDc0RDRGNEk0TDRxNHM0djR5NHs0fTR/NJI0rTS6NLw0vzTCNOM05TToNOs07TTvNPE1AjUE
3369 NRY1IzUlNSg1KzVMNU41UTVUNVY1WDVaNV41YDVlNXI1dDV3NXo1mzWdNaA1ozWlNac1qTWvNbE1vzXc
3370 NeY18DYPNhI2FDYXNho2HTYgNk02ajaBNo42mTaoNrc23Db3NxA3JDclNyg3KTcsNy03MDczNzQ3NTc2
3371 Nzk3QjdEN0s3TjdRN1Q3WTddN2M3bDdvN3I3dTeGN4w3lzejN6Y3qTesN603tje/N8Q31zfgN+U37jf5
3372 OBI4Jjg7OEg4dDiGOKE4qjixOMk45jjpOOw47zjyOPU4+DkUOSg5LzlMOU85UjlVOVg5WjldOXw5lDmx
3373 ObQ5tzm6Ob05vznCOd859ToSOhU6GDobOh46IDojOj86RzpaOmM6Zjs3Ozo7PDs/O0I7RTtHO0o7TTtP
3374 O1I7VTtYO1s7XjthO2M7ZTtnO2k7azttO3A7cjt0O3Y7eDt6O3w7fzuCO4Q7hjuIO4s7jTuQO5I7lTuY
3375 O5o7nTugO6I7pDunO6o7rTuwO7I7tTu4O7s7vjvAO8I7xDvHO8k7zDvOO9E71DvWO9g72zveO+E75Dvm
3376 O+k76zvuO/E78zv1O/g7+zv9PAA8AjwFPAc8CTwMPA88EjwVPBc8GjwdPCA8IzwmPCk8KzwuPDA8Mzw2
3377 PDk8PDw/PEI8azx5PIY8iDyKPIs8jTyOPJA8kjyUPL08xzzJPMw8zzzRPNM81TzYPNs87DzvPPI89Tz4
3378 PP89Dj0XPRk9Hj0hPSQ9RT1HPUo9TD1OPVA9Uz1ePWc9bD14PYE9gz2GPYk9oj3LPc090D3TPdU91z3Z
3379 Pds93j4HPgk+Cz4OPhA+Ej4UPhY+GT4wPjk+Oz5EPkc+ST5LPk0+dj54Pno+fT5/PoE+gz6GPok+jj6X
3380 Ppk+tD63Prk+vD6/PsI+xT7IPso+zT7QPtM+1j7ZPwI/BD8GPwc/CT8KPww/Dj8QPzk/Oz89Pz4/QD9B
3381 P0M/RT9HP3A/cj91P3g/ej98P34/gD+DP4g/kT+TP54/oT+kP6c/qj+tP9I/1D/XP9o/3D/eP+E/60AQ
3382 QBJAFUAXQBlAG0AeQCxAUUBTQFZAWUBbQF1AYEBiQHtAfUCmQKhAqkCtQK9AsUCzQLVAuEDGQM9A0UDY
3383 QNtA3kDgQQlBC0ENQRBBEkEUQRZBGEEbQSJBK0EtQTJBNEE3QUFBSkFMQVtBXkFhQWRBZ0FqQW1BcEGZ
3384 QZtBnUGgQaJBpEGmQalBrEG+QcdByUHgQeNB5kHpQexB70HyQfVB+EH6Qf1CAEIpQitCLUIuQjBCMUIz
3385 QjVCN0JYQlpCXUJgQmJCZEJmQn9CgUKqQqxCrkKvQrFCskK0QrZCuELhQuNC5kLpQutC7ULvQvFC9EL9
3386 Qw5DEUMUQxdDGkMjQyVDJkM4Q2FDY0NlQ2ZDaENpQ2tDbUNvQ3xDpUOnQ6lDrEOuQ7BDskO1Q7hDvUPG
3387 Q8hD30PiQ+VD6EPrQ+5D8EPzQ/ZD+UP8Q/5EH0QhRCREJ0QpRCtELUQxRDNEVERWRFlEXEReRGBEYkRt
3388 RG9EmESaRJxEnUSfRKBEokSkRKZEz0TRRNNE1ETWRNdE2UTbRN1FBkUIRQpFDUUPRRFFE0UWRRlFHkUn
3389 RSlFLkUwRTJFW0VdRWBFY0VlRWdFaUVsRW9FdkV/RYFFikWNRZBFk0WWRb9FwUXDRcRFxkXHRclFy0XO
3390 Rd1GBkYIRgpGDUYPRhFGE0YWRhlGHkYnRilGLEYuRjpGQ0ZGRxdHGUcbRx5HIEcjRyVHKEcqRyxHLkcx
3391 RzNHNUc3RzlHO0c9Rz9HQkdFR0dHSUdLR01HT0dRR1NHVkdYR1pHXUdfR2FHY0dlR2dHakdsR29HcUd0
3392 R3ZHeEd6R3xHfkeBR4RHhkeJR4tHjkeQR5JHlUeYR5tHnkegR6JHpEemR6hHqketR7BHske1R7dHuUe7
3393 R71Hv0fBR8NHxUfHR8lHy0fNR9BH0kfUR9dH2kfdR99H4UfjR+VH50fqR+xH70fxR/NH9Uf3R/pH/UgA
3394 SAJIBUgOSBFI5EjmSOlI7EjvSPJI9Ej3SPpI/Ej/SQJJBUkISQtJDkkQSRJJFEkWSRhJGkkdSR9JIUkj
3395 SSVJJ0kpSStJLUkwSTNJNUk4STpJPUk/SUJJRUlHSUpJTUlPSVFJVElWSVlJXElfSWJJZElnSWpJbUlv
3396 SXFJc0l1SXhJe0l9SYBJg0mFSYdJikmNSZBJk0mVSZhJmkmdSZ9JokmkSadJqkmsSa9JsUm0SbZJuEm7
3397 Sb5JwUnEScZJyUnMSc9J0knVSdhJ2kndSd9J4knlSehJ60nuSfFJ+kn9StBK00rWStlK3ErfSuJK5Uro
3398 SutK7krxSvRK90r6Sv1LAEsDSwZLCUsMSw9LEksVSxhLG0seSyFLJEsnSypLLUswSzNLNks5SzxLP0tC
3399 S0VLSEtLS05LUUtUS1dLWktdS2BLY0tmS2lLbEtvS3JLdUt4S3tLfkuBS4RLhkuJS4xLj0uSS5VLmEub
3400 S55LoUukS6dLqkutS7BLs0u2S7lLvEu/S8JLxUvIS8tLzkvRS9RL10vaS91L4EvjS+ZL6UvsS+9L8kv1
3401 S/hL+0v+TAFMBEwHTBJMHkxDTFhMbUyLTKBMukzaTP1NEE0jTS1NNk1STV9NYU1vTXFNiE2hTb1N104B
3402 Tg9OOU5LTmROgE6MTp9Oq07KTuRO+08ATw5PIk9DT09PYU97T55PqE/ET9FP00/oT+xP+FAVUC5QOlBV
3403 UFdQdVCBUI1QpVDKUM5Q0FDrUQFRJlFEUVdRWVFvUYJRwVHlUfJSKVJGUmlSbVKBUo1SoFKzUspS3lLs
3404 UwxTDlMgUzpTRlNJU15TdVOUU65TulPGU95T9VQOVCFUPVQ/VE1UVlRZVFpUY1RmVGdUcFRzVaRVp1Wp
3405 VaxVr1WyVbVVuFW7Vb1VwFXDVcVVyFXLVc1V0FXTVdZV2FXbVd5V4VXjVeZV6VXsVe5V8FXyVfRV9lX4
3406 VftV/VYAVgJWBFYGVghWClYNVhBWElYUVhZWGVYcVh5WIVYkViZWKVYsVi9WMVYzVjZWOVY8Vj5WQVZE
3407 VkdWSlZMVk5WUVZTVlZWWVZcVl5WYVZkVmZWaVZsVm9WcVZ0VnZWeFZ7Vn5WgFaCVoVWh1aKVo1Wj1aR
3408 VpRWl1aZVpxWnlahVqRWp1apVqxWrlawVrNWtla4VrpWvVbAVsNWxlbIVstWzVbQVtJW1VbXVtlW21be
3409 VuFW5FbnVulW7FbvVvJW9Fb3VvpW/VcAVwNXBlcIVwtXDlcQVxNXFlcZVxxXH1ciVyVXJ1cqVy1XMFc5
3410 VzxYbVhwWHNYdlh5WHxYf1iCWIVYiFiLWI5YkViUWJdYmlidWKBYo1imWKlYrFivWLJYtVi4WLtYvljB
3411 WMRYx1jKWM1Y0FjTWNZY2VjcWN9Y4ljlWOhY61juWPFY9Fj3WPpY/VkAWQNZBlkJWQxZD1kSWRVZGFkb
3412 WR5ZIVkkWSdZKlktWTBZM1k2WTlZPFk/WUJZRVlIWUtZTllRWVRZV1laWV1ZYFljWWZZaVlsWW9Zcll1
3413 WXhZe1l+WYFZhFmHWYpZjVmQWZNZllmZWZxZn1miWaVZqFmrWa5ZsVm0WbdZulm9WcBZw1nGWclZzFnP
3414 WdJZ1VnYWdtZ3lnhWeRZ51nqWe1Z8FnzWfZZ+Vn8Wf9aAloFWghaC1oOWhFaFFoXWhpaHVogWiNaJlop
3415 WixaL1oyWjRaN1o5WjtaPVpAWkNaRVpIWkpaTFpOWlBaU1pWWlhaWlpcWl9aYlpkWmZaaVprWm1acFpy
3416 WnVaeFp6Wn1af1qBWoRah1qKWoxaj1qSWpRallqYWppanVqgWqJapFqmWqhaqlqsWq9asVqzWrxav1rC
3417 WsVax1rKWs1az1rRWtRa1lrZWtxa31rhWuNa5VrnWula61ruWvBa8lr1Wvda+Vr7Wv1a/1sBWwRbBlsI
3418 WwtbDVsPWxJbFVsYWxtbHVsfWyFbI1slWyhbK1stWzBbMls0WzZbOFs7Wz1bQFtCW0VbSFtKW0xbTltR
3419 W1NbVltZW1xbXlthW2NbZVtoW2pbbFtuW3FbdFt2W3hbe1t+W4Bbg1uGW4hbi1uOW5Bbk1uVW5hbmluc
3420 W55boVujW6VbrluwW7Fbulu9W75bx1vKW8tb1FvZAAAAAAAAAgIAAAAAAAALwwAAAAAAAAAAAAAAAAAA
3421 W+g</bytes>
3422 </object>
3423 </data>
3424 </archive>
@@ -1,293 +0,0 b''
1 // !$*UTF8*$!
2 {
3 archiveVersion = 1;
4 classes = {
5 };
6 objectVersion = 44;
7 objects = {
8
9 /* Begin PBXBuildFile section */
10 77631A270C06C501005415CB /* Python.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 77631A260C06C501005415CB /* Python.framework */; };
11 77631A3F0C0748CF005415CB /* main.py in Resources */ = {isa = PBXBuildFile; fileRef = 77631A3E0C0748CF005415CB /* main.py */; };
12 7790198F0C07548A00326F66 /* IPython1SandboxAppDelegate.py in Resources */ = {isa = PBXBuildFile; fileRef = 7790198E0C07548A00326F66 /* IPython1SandboxAppDelegate.py */; };
13 77C8C1F90C07829500965286 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 77C8C1F70C07829500965286 /* MainMenu.xib */; };
14 8D11072B0486CEB800E47090 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 089C165CFE840E0CC02AAC07 /* InfoPlist.strings */; };
15 8D11072D0486CEB800E47090 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 29B97316FDCFA39411CA2CEA /* main.m */; settings = {ATTRIBUTES = (); }; };
16 8D11072F0486CEB800E47090 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */; };
17 /* End PBXBuildFile section */
18
19 /* Begin PBXFileReference section */
20 089C165DFE840E0CC02AAC07 /* English */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = English; path = English.lproj/InfoPlist.strings; sourceTree = "<group>"; };
21 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = /System/Library/Frameworks/Cocoa.framework; sourceTree = "<absolute>"; };
22 13E42FB307B3F0F600E4EEF1 /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = /System/Library/Frameworks/CoreData.framework; sourceTree = "<absolute>"; };
23 29B97316FDCFA39411CA2CEA /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
24 29B97324FDCFA39411CA2CEA /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = /System/Library/Frameworks/AppKit.framework; sourceTree = "<absolute>"; };
25 29B97325FDCFA39411CA2CEA /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = "<absolute>"; };
26 32CA4F630368D1EE00C91783 /* IPython1Sandbox_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IPython1Sandbox_Prefix.pch; sourceTree = "<group>"; };
27 4CA32F870D8879B100311764 /* IPythonCocoaController Tests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "IPythonCocoaController Tests-Info.plist"; sourceTree = "<group>"; };
28 77631A260C06C501005415CB /* Python.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Python.framework; path = /System/Library/Frameworks/Python.framework; sourceTree = "<absolute>"; };
29 77631A3E0C0748CF005415CB /* main.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = main.py; sourceTree = "<group>"; };
30 7790198E0C07548A00326F66 /* IPython1SandboxAppDelegate.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = IPython1SandboxAppDelegate.py; sourceTree = "<group>"; };
31 77C8C1F80C07829500965286 /* English */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = English; path = English.lproj/MainMenu.xib; sourceTree = "<group>"; };
32 8D1107310486CEB800E47090 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
33 8D1107320486CEB800E47090 /* IPython1Sandbox.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = IPython1Sandbox.app; sourceTree = BUILT_PRODUCTS_DIR; };
34 /* End PBXFileReference section */
35
36 /* Begin PBXFrameworksBuildPhase section */
37 8D11072E0486CEB800E47090 /* Frameworks */ = {
38 isa = PBXFrameworksBuildPhase;
39 buildActionMask = 2147483647;
40 files = (
41 8D11072F0486CEB800E47090 /* Cocoa.framework in Frameworks */,
42 77631A270C06C501005415CB /* Python.framework in Frameworks */,
43 );
44 runOnlyForDeploymentPostprocessing = 0;
45 };
46 /* End PBXFrameworksBuildPhase section */
47
48 /* Begin PBXGroup section */
49 080E96DDFE201D6D7F000001 /* Classes */ = {
50 isa = PBXGroup;
51 children = (
52 7790198E0C07548A00326F66 /* IPython1SandboxAppDelegate.py */,
53 );
54 name = Classes;
55 sourceTree = "<group>";
56 };
57 1058C7A0FEA54F0111CA2CBB /* Linked Frameworks */ = {
58 isa = PBXGroup;
59 children = (
60 77631A260C06C501005415CB /* Python.framework */,
61 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */,
62 );
63 name = "Linked Frameworks";
64 sourceTree = "<group>";
65 };
66 1058C7A2FEA54F0111CA2CBB /* Other Frameworks */ = {
67 isa = PBXGroup;
68 children = (
69 29B97324FDCFA39411CA2CEA /* AppKit.framework */,
70 13E42FB307B3F0F600E4EEF1 /* CoreData.framework */,
71 29B97325FDCFA39411CA2CEA /* Foundation.framework */,
72 );
73 name = "Other Frameworks";
74 sourceTree = "<group>";
75 };
76 19C28FACFE9D520D11CA2CBB /* Products */ = {
77 isa = PBXGroup;
78 children = (
79 8D1107320486CEB800E47090 /* IPython1Sandbox.app */,
80 );
81 name = Products;
82 sourceTree = "<group>";
83 };
84 29B97314FDCFA39411CA2CEA /* IPython1Sandbox */ = {
85 isa = PBXGroup;
86 children = (
87 080E96DDFE201D6D7F000001 /* Classes */,
88 29B97315FDCFA39411CA2CEA /* Other Sources */,
89 29B97317FDCFA39411CA2CEA /* Resources */,
90 29B97323FDCFA39411CA2CEA /* Frameworks */,
91 19C28FACFE9D520D11CA2CBB /* Products */,
92 4CA32F870D8879B100311764 /* IPythonCocoaController Tests-Info.plist */,
93 );
94 name = IPython1Sandbox;
95 sourceTree = "<group>";
96 };
97 29B97315FDCFA39411CA2CEA /* Other Sources */ = {
98 isa = PBXGroup;
99 children = (
100 32CA4F630368D1EE00C91783 /* IPython1Sandbox_Prefix.pch */,
101 29B97316FDCFA39411CA2CEA /* main.m */,
102 77631A3E0C0748CF005415CB /* main.py */,
103 );
104 name = "Other Sources";
105 sourceTree = "<group>";
106 };
107 29B97317FDCFA39411CA2CEA /* Resources */ = {
108 isa = PBXGroup;
109 children = (
110 77C8C1F70C07829500965286 /* MainMenu.xib */,
111 8D1107310486CEB800E47090 /* Info.plist */,
112 089C165CFE840E0CC02AAC07 /* InfoPlist.strings */,
113 );
114 name = Resources;
115 sourceTree = "<group>";
116 };
117 29B97323FDCFA39411CA2CEA /* Frameworks */ = {
118 isa = PBXGroup;
119 children = (
120 1058C7A0FEA54F0111CA2CBB /* Linked Frameworks */,
121 1058C7A2FEA54F0111CA2CBB /* Other Frameworks */,
122 );
123 name = Frameworks;
124 sourceTree = "<group>";
125 };
126 /* End PBXGroup section */
127
128 /* Begin PBXNativeTarget section */
129 8D1107260486CEB800E47090 /* IPython1Sandbox */ = {
130 isa = PBXNativeTarget;
131 buildConfigurationList = C01FCF4A08A954540054247B /* Build configuration list for PBXNativeTarget "IPython1Sandbox" */;
132 buildPhases = (
133 8D1107290486CEB800E47090 /* Resources */,
134 8D11072C0486CEB800E47090 /* Sources */,
135 8D11072E0486CEB800E47090 /* Frameworks */,
136 );
137 buildRules = (
138 );
139 dependencies = (
140 );
141 name = IPython1Sandbox;
142 productInstallPath = "$(HOME)/Applications";
143 productName = IPython1Sandbox;
144 productReference = 8D1107320486CEB800E47090 /* IPython1Sandbox.app */;
145 productType = "com.apple.product-type.application";
146 };
147 /* End PBXNativeTarget section */
148
149 /* Begin PBXProject section */
150 29B97313FDCFA39411CA2CEA /* Project object */ = {
151 isa = PBXProject;
152 buildConfigurationList = C01FCF4E08A954540054247B /* Build configuration list for PBXProject "IPython1Sandbox" */;
153 compatibilityVersion = "Xcode 3.0";
154 hasScannedForEncodings = 1;
155 mainGroup = 29B97314FDCFA39411CA2CEA /* IPython1Sandbox */;
156 projectDirPath = "";
157 projectRoot = "";
158 targets = (
159 8D1107260486CEB800E47090 /* IPython1Sandbox */,
160 );
161 };
162 /* End PBXProject section */
163
164 /* Begin PBXResourcesBuildPhase section */
165 8D1107290486CEB800E47090 /* Resources */ = {
166 isa = PBXResourcesBuildPhase;
167 buildActionMask = 2147483647;
168 files = (
169 8D11072B0486CEB800E47090 /* InfoPlist.strings in Resources */,
170 77631A3F0C0748CF005415CB /* main.py in Resources */,
171 7790198F0C07548A00326F66 /* IPython1SandboxAppDelegate.py in Resources */,
172 77C8C1F90C07829500965286 /* MainMenu.xib in Resources */,
173 );
174 runOnlyForDeploymentPostprocessing = 0;
175 };
176 /* End PBXResourcesBuildPhase section */
177
178 /* Begin PBXSourcesBuildPhase section */
179 8D11072C0486CEB800E47090 /* Sources */ = {
180 isa = PBXSourcesBuildPhase;
181 buildActionMask = 2147483647;
182 files = (
183 8D11072D0486CEB800E47090 /* main.m in Sources */,
184 );
185 runOnlyForDeploymentPostprocessing = 0;
186 };
187 /* End PBXSourcesBuildPhase section */
188
189 /* Begin PBXVariantGroup section */
190 089C165CFE840E0CC02AAC07 /* InfoPlist.strings */ = {
191 isa = PBXVariantGroup;
192 children = (
193 089C165DFE840E0CC02AAC07 /* English */,
194 );
195 name = InfoPlist.strings;
196 sourceTree = "<group>";
197 };
198 77C8C1F70C07829500965286 /* MainMenu.xib */ = {
199 isa = PBXVariantGroup;
200 children = (
201 77C8C1F80C07829500965286 /* English */,
202 );
203 name = MainMenu.xib;
204 sourceTree = "<group>";
205 };
206 /* End PBXVariantGroup section */
207
208 /* Begin XCBuildConfiguration section */
209 C01FCF4B08A954540054247B /* Debug */ = {
210 isa = XCBuildConfiguration;
211 buildSettings = {
212 COPY_PHASE_STRIP = NO;
213 CURRENT_PROJECT_VERSION = 1;
214 GCC_DYNAMIC_NO_PIC = NO;
215 GCC_ENABLE_FIX_AND_CONTINUE = YES;
216 GCC_MODEL_TUNING = G5;
217 GCC_OPTIMIZATION_LEVEL = 0;
218 GCC_PRECOMPILE_PREFIX_HEADER = YES;
219 GCC_PREFIX_HEADER = IPython1Sandbox_Prefix.pch;
220 INFOPLIST_FILE = Info.plist;
221 INSTALL_PATH = "$(HOME)/Applications";
222 PRODUCT_NAME = IPython1Sandbox;
223 VERSIONING_SYSTEM = "apple-generic";
224 WRAPPER_EXTENSION = app;
225 ZERO_LINK = YES;
226 };
227 name = Debug;
228 };
229 C01FCF4C08A954540054247B /* Release */ = {
230 isa = XCBuildConfiguration;
231 buildSettings = {
232 CURRENT_PROJECT_VERSION = 1;
233 DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
234 GCC_MODEL_TUNING = G5;
235 GCC_PRECOMPILE_PREFIX_HEADER = YES;
236 GCC_PREFIX_HEADER = IPython1Sandbox_Prefix.pch;
237 INFOPLIST_FILE = Info.plist;
238 INSTALL_PATH = "$(HOME)/Applications";
239 PRODUCT_NAME = IPython1Sandbox;
240 VERSIONING_SYSTEM = "apple-generic";
241 WRAPPER_EXTENSION = app;
242 };
243 name = Release;
244 };
245 C01FCF4F08A954540054247B /* Debug */ = {
246 isa = XCBuildConfiguration;
247 buildSettings = {
248 GCC_WARN_ABOUT_RETURN_TYPE = YES;
249 GCC_WARN_UNUSED_VARIABLE = YES;
250 PREBINDING = NO;
251 SDKROOT = "$(DEVELOPER_SDK_DIR)/MacOSX10.5.sdk";
252 };
253 name = Debug;
254 };
255 C01FCF5008A954540054247B /* Release */ = {
256 isa = XCBuildConfiguration;
257 buildSettings = {
258 ARCHS = (
259 ppc,
260 i386,
261 );
262 GCC_WARN_ABOUT_RETURN_TYPE = YES;
263 GCC_WARN_UNUSED_VARIABLE = YES;
264 PREBINDING = NO;
265 SDKROOT = "$(DEVELOPER_SDK_DIR)/MacOSX10.5.sdk";
266 };
267 name = Release;
268 };
269 /* End XCBuildConfiguration section */
270
271 /* Begin XCConfigurationList section */
272 C01FCF4A08A954540054247B /* Build configuration list for PBXNativeTarget "IPython1Sandbox" */ = {
273 isa = XCConfigurationList;
274 buildConfigurations = (
275 C01FCF4B08A954540054247B /* Debug */,
276 C01FCF4C08A954540054247B /* Release */,
277 );
278 defaultConfigurationIsVisible = 0;
279 defaultConfigurationName = Release;
280 };
281 C01FCF4E08A954540054247B /* Build configuration list for PBXProject "IPython1Sandbox" */ = {
282 isa = XCConfigurationList;
283 buildConfigurations = (
284 C01FCF4F08A954540054247B /* Debug */,
285 C01FCF5008A954540054247B /* Release */,
286 );
287 defaultConfigurationIsVisible = 0;
288 defaultConfigurationName = Release;
289 };
290 /* End XCConfigurationList section */
291 };
292 rootObject = 29B97313FDCFA39411CA2CEA /* Project object */;
293 }
@@ -1,39 +0,0 b''
1 #
2 # IPython1SandboxAppDelegate.py
3 # IPython1Sandbox
4 #
5 # Created by Barry Wark on 3/4/08.
6 # Copyright __MyCompanyName__ 2008. All rights reserved.
7 #
8
9 from Foundation import NSObject, NSPredicate
10 import objc
11 import threading
12
13 from PyObjCTools import AppHelper
14
15 from twisted.internet import reactor
16
17 class IPython1SandboxAppDelegate(NSObject):
18 ipythonController = objc.IBOutlet()
19
20 def applicationShouldTerminate_(self, sender):
21 if reactor.running:
22 reactor.addSystemEventTrigger(
23 'after', 'shutdown', AppHelper.stopEventLoop)
24 reactor.stop()
25 return False
26 return True
27
28
29 def applicationDidFinishLaunching_(self, sender):
30 reactor.interleave(AppHelper.callAfter)
31 assert(reactor.running)
32
33
34 def workspaceFilterPredicate(self):
35 return NSPredicate.predicateWithFormat_("NOT (self.value BEGINSWITH '<')")
36
37
38
39
@@ -1,7 +0,0 b''
1 //
2 // Prefix header for all source files of the 'IPython1Sandbox' target in the 'IPython1Sandbox' project
3 //
4
5 #ifdef __OBJC__
6 #import <Cocoa/Cocoa.h>
7 #endif
@@ -1,20 +0,0 b''
1 <?xml version="1.0" encoding="UTF-8"?>
2 <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3 <plist version="1.0">
4 <dict>
5 <key>CFBundleDevelopmentRegion</key>
6 <string>English</string>
7 <key>CFBundleExecutable</key>
8 <string>${EXECUTABLE_NAME}</string>
9 <key>CFBundleIdentifier</key>
10 <string>com.yourcompany.IPythonCocoaController Tests</string>
11 <key>CFBundleInfoDictionaryVersion</key>
12 <string>6.0</string>
13 <key>CFBundlePackageType</key>
14 <string>BNDL</string>
15 <key>CFBundleSignature</key>
16 <string>????</string>
17 <key>CFBundleVersion</key>
18 <string>1.0</string>
19 </dict>
20 </plist>
@@ -1,30 +0,0 b''
1 <?xml version="1.0" encoding="UTF-8"?>
2 <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3 <plist version="1.0">
4 <dict>
5 <key>CFBundleDevelopmentRegion</key>
6 <string>English</string>
7 <key>CFBundleExecutable</key>
8 <string>${EXECUTABLE_NAME}</string>
9 <key>CFBundleIconFile</key>
10 <string></string>
11 <key>CFBundleIdentifier</key>
12 <string>com.yourcompany.IPython1Sandbox</string>
13 <key>CFBundleInfoDictionaryVersion</key>
14 <string>6.0</string>
15 <key>CFBundleName</key>
16 <string>${PRODUCT_NAME}</string>
17 <key>CFBundlePackageType</key>
18 <string>APPL</string>
19 <key>CFBundleShortVersionString</key>
20 <string>0.1</string>
21 <key>CFBundleSignature</key>
22 <string>????</string>
23 <key>CFBundleVersion</key>
24 <string>1.0</string>
25 <key>NSMainNibFile</key>
26 <string>MainMenu</string>
27 <key>NSPrincipalClass</key>
28 <string>NSApplication</string>
29 </dict>
30 </plist>
@@ -1,49 +0,0 b''
1 //
2 // main.m
3 // IPython1Sandbox
4 //
5 // Created by Barry Wark on 3/4/08.
6 // Copyright __MyCompanyName__ 2008. All rights reserved.
7 //
8
9 #import <Python/Python.h>
10 #import <Cocoa/Cocoa.h>
11
12 int main(int argc, char *argv[])
13 {
14 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
15
16 NSBundle *mainBundle = [NSBundle mainBundle];
17 NSString *resourcePath = [mainBundle resourcePath];
18 NSArray *pythonPathArray = [NSArray arrayWithObjects: resourcePath, [resourcePath stringByAppendingPathComponent:@"PyObjC"], nil];
19
20 setenv("PYTHONPATH", [[pythonPathArray componentsJoinedByString:@":"] UTF8String], 1);
21
22 NSArray *possibleMainExtensions = [NSArray arrayWithObjects: @"py", @"pyc", @"pyo", nil];
23 NSString *mainFilePath = nil;
24
25 for (NSString *possibleMainExtension in possibleMainExtensions) {
26 mainFilePath = [mainBundle pathForResource: @"main" ofType: possibleMainExtension];
27 if ( mainFilePath != nil ) break;
28 }
29
30 if ( !mainFilePath ) {
31 [NSException raise: NSInternalInconsistencyException format: @"%s:%d main() Failed to find the Main.{py,pyc,pyo} file in the application wrapper's Resources directory.", __FILE__, __LINE__];
32 }
33
34 Py_SetProgramName("/usr/bin/python");
35 Py_Initialize();
36 PySys_SetArgv(argc, (char **)argv);
37
38 const char *mainFilePathPtr = [mainFilePath UTF8String];
39 FILE *mainFile = fopen(mainFilePathPtr, "r");
40 int result = PyRun_SimpleFile(mainFile, (char *)[[mainFilePath lastPathComponent] UTF8String]);
41
42 if ( result != 0 )
43 [NSException raise: NSInternalInconsistencyException
44 format: @"%s:%d main() PyRun_SimpleFile failed with file '%@'. See console for errors.", __FILE__, __LINE__, mainFilePath];
45
46 [pool drain];
47
48 return result;
49 }
@@ -1,24 +0,0 b''
1 #
2 # main.py
3 # IPython1Sandbox
4 #
5 # Created by Barry Wark on 3/4/08.
6 # Copyright __MyCompanyName__ 2008. All rights reserved.
7 #
8
9 #import modules required by application
10 import objc
11 import Foundation
12 import AppKit
13
14 from PyObjCTools import AppHelper
15
16 from twisted.internet import _threadedselect
17 reactor = _threadedselect.install()
18
19 # import modules containing classes required to start application and load MainMenu.nib
20 import IPython1SandboxAppDelegate
21 import IPython.frontend.cocoa.cocoa_frontend
22
23 # pass control to AppKit
24 AppHelper.runEventLoop()
@@ -1,256 +0,0 b''
1 // !$*UTF8*$!
2 {
3 archiveVersion = 1;
4 classes = {
5 };
6 objectVersion = 42;
7 objects = {
8
9 /* Begin PBXContainerItemProxy section */
10 4C5B7ADB0E1A0BCD006CB905 /* PBXContainerItemProxy */ = {
11 isa = PBXContainerItemProxy;
12 containerPortal = 4C96F4FE0E199AB500B03430 /* Project object */;
13 proxyType = 1;
14 remoteGlobalIDString = 4C96F50C0E199AF100B03430;
15 remoteInfo = "Cocoa Frontend Plugin";
16 };
17 /* End PBXContainerItemProxy section */
18
19 /* Begin PBXFileReference section */
20 4C5B7A8D0E1A0B4C006CB905 /* Plugin-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Plugin-Info.plist"; sourceTree = "<group>"; };
21 4C5B7AD30E1A0BC8006CB905 /* Placeholder (Do Not Use).bundle */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Placeholder (Do Not Use).bundle"; sourceTree = BUILT_PRODUCTS_DIR; };
22 4C5B7AD40E1A0BC8006CB905 /* Placeholder (Do Not Use)-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Placeholder (Do Not Use)-Info.plist"; sourceTree = "<group>"; };
23 /* End PBXFileReference section */
24
25 /* Begin PBXFrameworksBuildPhase section */
26 4C5B7AD10E1A0BC8006CB905 /* Frameworks */ = {
27 isa = PBXFrameworksBuildPhase;
28 buildActionMask = 2147483647;
29 files = (
30 );
31 runOnlyForDeploymentPostprocessing = 0;
32 };
33 /* End PBXFrameworksBuildPhase section */
34
35 /* Begin PBXGroup section */
36 4C5B7A8C0E1A0B4C006CB905 /* Products */ = {
37 isa = PBXGroup;
38 children = (
39 4C5B7AD30E1A0BC8006CB905 /* Placeholder (Do Not Use).bundle */,
40 );
41 name = Products;
42 sourceTree = "<group>";
43 };
44 4C96F4FC0E199AB500B03430 = {
45 isa = PBXGroup;
46 children = (
47 4C5B7A8C0E1A0B4C006CB905 /* Products */,
48 4C5B7A8D0E1A0B4C006CB905 /* Plugin-Info.plist */,
49 4C5B7AD40E1A0BC8006CB905 /* Placeholder (Do Not Use)-Info.plist */,
50 );
51 sourceTree = "<group>";
52 };
53 /* End PBXGroup section */
54
55 /* Begin PBXLegacyTarget section */
56 4C96F50C0E199AF100B03430 /* Cocoa Frontend Plugin */ = {
57 isa = PBXLegacyTarget;
58 buildArgumentsString = "$(ACTION)";
59 buildConfigurationList = 4C96F5110E199B3300B03430 /* Build configuration list for PBXLegacyTarget "Cocoa Frontend Plugin" */;
60 buildPhases = (
61 );
62 buildToolPath = /usr/bin/make;
63 buildWorkingDirectory = "";
64 dependencies = (
65 );
66 name = "Cocoa Frontend Plugin";
67 passBuildSettingsInEnvironment = 1;
68 productName = "Cocoa Frontend Plugin";
69 };
70 /* End PBXLegacyTarget section */
71
72 /* Begin PBXNativeTarget section */
73 4C5B7AD20E1A0BC8006CB905 /* Placeholder (Do Not Use) */ = {
74 isa = PBXNativeTarget;
75 buildConfigurationList = 4C5B7ADA0E1A0BC9006CB905 /* Build configuration list for PBXNativeTarget "Placeholder (Do Not Use)" */;
76 buildPhases = (
77 4C5B7ACF0E1A0BC8006CB905 /* Resources */,
78 4C5B7AD00E1A0BC8006CB905 /* Sources */,
79 4C5B7AD10E1A0BC8006CB905 /* Frameworks */,
80 );
81 buildRules = (
82 );
83 dependencies = (
84 4C5B7ADC0E1A0BCD006CB905 /* PBXTargetDependency */,
85 );
86 name = "Placeholder (Do Not Use)";
87 productName = "Placeholder (Do Not Use)";
88 productReference = 4C5B7AD30E1A0BC8006CB905 /* Placeholder (Do Not Use).bundle */;
89 productType = "com.apple.product-type.bundle";
90 };
91 /* End PBXNativeTarget section */
92
93 /* Begin PBXProject section */
94 4C96F4FE0E199AB500B03430 /* Project object */ = {
95 isa = PBXProject;
96 buildConfigurationList = 4C96F5010E199AB500B03430 /* Build configuration list for PBXProject "CocoaFrontendPlugin" */;
97 compatibilityVersion = "Xcode 2.4";
98 hasScannedForEncodings = 0;
99 mainGroup = 4C96F4FC0E199AB500B03430;
100 productRefGroup = 4C5B7A8C0E1A0B4C006CB905 /* Products */;
101 projectDirPath = "";
102 projectRoot = "";
103 targets = (
104 4C96F50C0E199AF100B03430 /* Cocoa Frontend Plugin */,
105 4C5B7AD20E1A0BC8006CB905 /* Placeholder (Do Not Use) */,
106 );
107 };
108 /* End PBXProject section */
109
110 /* Begin PBXResourcesBuildPhase section */
111 4C5B7ACF0E1A0BC8006CB905 /* Resources */ = {
112 isa = PBXResourcesBuildPhase;
113 buildActionMask = 2147483647;
114 files = (
115 );
116 runOnlyForDeploymentPostprocessing = 0;
117 };
118 /* End PBXResourcesBuildPhase section */
119
120 /* Begin PBXSourcesBuildPhase section */
121 4C5B7AD00E1A0BC8006CB905 /* Sources */ = {
122 isa = PBXSourcesBuildPhase;
123 buildActionMask = 2147483647;
124 files = (
125 );
126 runOnlyForDeploymentPostprocessing = 0;
127 };
128 /* End PBXSourcesBuildPhase section */
129
130 /* Begin PBXTargetDependency section */
131 4C5B7ADC0E1A0BCD006CB905 /* PBXTargetDependency */ = {
132 isa = PBXTargetDependency;
133 target = 4C96F50C0E199AF100B03430 /* Cocoa Frontend Plugin */;
134 targetProxy = 4C5B7ADB0E1A0BCD006CB905 /* PBXContainerItemProxy */;
135 };
136 /* End PBXTargetDependency section */
137
138 /* Begin XCBuildConfiguration section */
139 4C5B7AD50E1A0BC9006CB905 /* Debug */ = {
140 isa = XCBuildConfiguration;
141 buildSettings = {
142 COPY_PHASE_STRIP = NO;
143 GCC_DYNAMIC_NO_PIC = NO;
144 GCC_ENABLE_FIX_AND_CONTINUE = YES;
145 GCC_MODEL_TUNING = G5;
146 GCC_OPTIMIZATION_LEVEL = 0;
147 GCC_PRECOMPILE_PREFIX_HEADER = YES;
148 GCC_PREFIX_HEADER = "$(SYSTEM_LIBRARY_DIR)/Frameworks/AppKit.framework/Headers/AppKit.h";
149 INFOPLIST_FILE = "Placeholder (Do Not Use)-Info.plist";
150 INSTALL_PATH = "$(HOME)/Library/Bundles";
151 OTHER_LDFLAGS = (
152 "-framework",
153 Foundation,
154 "-framework",
155 AppKit,
156 );
157 PREBINDING = NO;
158 PRODUCT_NAME = "Placeholder (Do Not Use)";
159 WRAPPER_EXTENSION = bundle;
160 ZERO_LINK = YES;
161 };
162 name = Debug;
163 };
164 4C5B7AD60E1A0BC9006CB905 /* Release */ = {
165 isa = XCBuildConfiguration;
166 buildSettings = {
167 COPY_PHASE_STRIP = YES;
168 DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
169 GCC_ENABLE_FIX_AND_CONTINUE = NO;
170 GCC_MODEL_TUNING = G5;
171 GCC_PRECOMPILE_PREFIX_HEADER = YES;
172 GCC_PREFIX_HEADER = "$(SYSTEM_LIBRARY_DIR)/Frameworks/AppKit.framework/Headers/AppKit.h";
173 INFOPLIST_FILE = "Placeholder (Do Not Use)-Info.plist";
174 INSTALL_PATH = "$(HOME)/Library/Bundles";
175 OTHER_LDFLAGS = (
176 "-framework",
177 Foundation,
178 "-framework",
179 AppKit,
180 );
181 PREBINDING = NO;
182 PRODUCT_NAME = "Placeholder (Do Not Use)";
183 WRAPPER_EXTENSION = bundle;
184 ZERO_LINK = NO;
185 };
186 name = Release;
187 };
188 4C96F4FF0E199AB500B03430 /* Debug */ = {
189 isa = XCBuildConfiguration;
190 buildSettings = {
191 COPY_PHASE_STRIP = NO;
192 };
193 name = Debug;
194 };
195 4C96F5000E199AB500B03430 /* Release */ = {
196 isa = XCBuildConfiguration;
197 buildSettings = {
198 COPY_PHASE_STRIP = YES;
199 };
200 name = Release;
201 };
202 4C96F50D0E199AF100B03430 /* Debug */ = {
203 isa = XCBuildConfiguration;
204 buildSettings = {
205 COPY_PHASE_STRIP = NO;
206 GCC_DYNAMIC_NO_PIC = NO;
207 GCC_OPTIMIZATION_LEVEL = 0;
208 PRODUCT_NAME = "Cocoa Frontend Plugin";
209 };
210 name = Debug;
211 };
212 4C96F50E0E199AF100B03430 /* Release */ = {
213 isa = XCBuildConfiguration;
214 buildSettings = {
215 COPY_PHASE_STRIP = YES;
216 DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
217 GCC_ENABLE_FIX_AND_CONTINUE = NO;
218 PRODUCT_NAME = "Cocoa Frontend Plugin";
219 ZERO_LINK = NO;
220 };
221 name = Release;
222 };
223 /* End XCBuildConfiguration section */
224
225 /* Begin XCConfigurationList section */
226 4C5B7ADA0E1A0BC9006CB905 /* Build configuration list for PBXNativeTarget "Placeholder (Do Not Use)" */ = {
227 isa = XCConfigurationList;
228 buildConfigurations = (
229 4C5B7AD50E1A0BC9006CB905 /* Debug */,
230 4C5B7AD60E1A0BC9006CB905 /* Release */,
231 );
232 defaultConfigurationIsVisible = 0;
233 defaultConfigurationName = Release;
234 };
235 4C96F5010E199AB500B03430 /* Build configuration list for PBXProject "CocoaFrontendPlugin" */ = {
236 isa = XCConfigurationList;
237 buildConfigurations = (
238 4C96F4FF0E199AB500B03430 /* Debug */,
239 4C96F5000E199AB500B03430 /* Release */,
240 );
241 defaultConfigurationIsVisible = 0;
242 defaultConfigurationName = Release;
243 };
244 4C96F5110E199B3300B03430 /* Build configuration list for PBXLegacyTarget "Cocoa Frontend Plugin" */ = {
245 isa = XCConfigurationList;
246 buildConfigurations = (
247 4C96F50D0E199AF100B03430 /* Debug */,
248 4C96F50E0E199AF100B03430 /* Release */,
249 );
250 defaultConfigurationIsVisible = 0;
251 defaultConfigurationName = Release;
252 };
253 /* End XCConfigurationList section */
254 };
255 rootObject = 4C96F4FE0E199AB500B03430 /* Project object */;
256 }
@@ -1,25 +0,0 b''
1 # encoding: utf-8
2 """
3 Provides a namespace for loading the Cocoa frontend via a Cocoa plugin.
4
5 Author: Barry Wark
6 """
7 __docformat__ = "restructuredtext en"
8
9 #-----------------------------------------------------------------------------
10 # Copyright (C) 2008-2011 The IPython Development Team
11 #
12 # Distributed under the terms of the BSD License. The full license is in
13 # the file COPYING, distributed as part of this software.
14 #-----------------------------------------------------------------------------
15
16 from PyObjCTools import AppHelper
17 from twisted.internet import _threadedselect
18
19 #make sure _threadedselect is installed first
20 reactor = _threadedselect.install()
21
22 # load the Cocoa frontend controller
23 from IPython.frontend.cocoa.cocoa_frontend import IPythonCocoaController
24 reactor.interleave(AppHelper.callAfter)
25 assert(reactor.running)
@@ -1,6 +0,0 b''
1 include ./plugins.mk
2
3 all : dist/IPythonCocoaController.plugin
4
5 dist/IPythonCocoaController.plugin : ./IPythonCocoaFrontendLoader.py\
6 ./setup.py No newline at end of file
@@ -1,20 +0,0 b''
1 <?xml version="1.0" encoding="UTF-8"?>
2 <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3 <plist version="1.0">
4 <dict>
5 <key>CFBundleDevelopmentRegion</key>
6 <string>English</string>
7 <key>CFBundleExecutable</key>
8 <string>${EXECUTABLE_NAME}</string>
9 <key>CFBundleIdentifier</key>
10 <string>com.yourcompany.Placeholder (Do Not Use)</string>
11 <key>CFBundleInfoDictionaryVersion</key>
12 <string>6.0</string>
13 <key>CFBundlePackageType</key>
14 <string>BNDL</string>
15 <key>CFBundleSignature</key>
16 <string>????</string>
17 <key>CFBundleVersion</key>
18 <string>1.0</string>
19 </dict>
20 </plist>
@@ -1,21 +0,0 b''
1 %.plugin::
2 rm -rf dist/$(notdir $@)
3 rm -rf build dist && \
4 python setup.py py2app -s
5
6 %.py:
7 @echo "test -f $@"
8 @test -f %@
9
10 %.nib:
11 @echo "test -f $@"
12 @test -f %@
13
14 .DEFAULT_GOAL := all
15
16 .PHONY : all clean
17
18 clean :
19 rm -rf build dist
20
21
@@ -1,35 +0,0 b''
1 # encoding: utf-8
2 """
3 setup.py
4
5 Setuptools installer script for generating a Cocoa plugin for the
6 IPython cocoa frontend
7
8 Author: Barry Wark
9 """
10 __docformat__ = "restructuredtext en"
11
12 #-----------------------------------------------------------------------------
13 # Copyright (C) 2008-2011 The IPython Development Team
14 #
15 # Distributed under the terms of the BSD License. The full license is in
16 # the file COPYING, distributed as part of this software.
17 #-----------------------------------------------------------------------------
18
19 from setuptools import setup
20
21 infoPlist = dict(
22 CFBundleDevelopmentRegion='English',
23 CFBundleIdentifier='org.scipy.ipython.cocoa_frontend',
24 NSPrincipalClass='IPythonCocoaController',
25 )
26
27 setup(
28 plugin=['IPythonCocoaFrontendLoader.py'],
29 setup_requires=['py2app'],
30 options=dict(py2app=dict(
31 plist=infoPlist,
32 site_packages=True,
33 excludes=['IPython','twisted','PyObjCTools']
34 )),
35 ) No newline at end of file
@@ -1,100 +0,0 b''
1 # encoding: utf-8
2 """This file contains unittests for the
3 IPython.frontend.cocoa.cocoa_frontend module.
4 """
5 __docformat__ = "restructuredtext en"
6
7 #---------------------------------------------------------------------------
8 # Copyright (C) 2005-2011 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 # Tell nose to skip this module
19 __test__ = {}
20
21 from twisted.trial import unittest
22 from twisted.internet.defer import succeed
23
24 from IPython.kernel.core.interpreter import Interpreter
25 import IPython.kernel.engineservice as es
26
27 try:
28 from IPython.frontend.cocoa.cocoa_frontend import IPythonCocoaController
29 from Foundation import NSMakeRect
30 from AppKit import NSTextView, NSScrollView
31 except ImportError:
32 # This tells twisted.trial to skip this module if PyObjC is not found
33 skip = True
34
35 #---------------------------------------------------------------------------
36 # Tests
37 #---------------------------------------------------------------------------
38 class TestIPythonCocoaControler(unittest.TestCase):
39 """Tests for IPythonCocoaController"""
40
41 def setUp(self):
42 self.controller = IPythonCocoaController.alloc().init()
43 self.engine = es.EngineService()
44 self.engine.startService()
45
46 def tearDown(self):
47 self.controller = None
48 self.engine.stopService()
49
50 def testControllerExecutesCode(self):
51 code ="""5+5"""
52 expected = Interpreter().execute(code)
53 del expected['number']
54 def removeNumberAndID(result):
55 del result['number']
56 del result['id']
57 return result
58 d = self.controller.execute(code)
59 d.addCallback(removeNumberAndID)
60 d.addCallback(lambda r: self.assertEquals(r, expected))
61
62 def testControllerMirrorsUserNSWithValuesAsStrings(self):
63 code = """userns1=1;userns2=2"""
64 def testControllerUserNS(result):
65 self.assertEquals(self.controller.userNS['userns1'], 1)
66 self.assertEquals(self.controller.userNS['userns2'], 2)
67 self.controller.execute(code).addCallback(testControllerUserNS)
68
69 def testControllerInstantiatesIEngine(self):
70 self.assert_(es.IEngineBase.providedBy(self.controller.engine))
71
72 def testControllerCompletesToken(self):
73 code = """longNameVariable=10"""
74 def testCompletes(result):
75 self.assert_("longNameVariable" in result)
76
77 def testCompleteToken(result):
78 self.controller.complete("longNa").addCallback(testCompletes)
79
80 self.controller.execute(code).addCallback(testCompletes)
81
82
83 def testCurrentIndent(self):
84 """test that current_indent_string returns current indent or None.
85 Uses _indent_for_block for direct unit testing.
86 """
87
88 self.controller.tabUsesSpaces = True
89 self.assert_(self.controller._indent_for_block("""a=3""") == None)
90 self.assert_(self.controller._indent_for_block("") == None)
91 block = """def test():\n a=3"""
92 self.assert_(self.controller._indent_for_block(block) == \
93 ' ' * self.controller.tabSpaces)
94
95 block = """if(True):\n%sif(False):\n%spass""" % \
96 (' '*self.controller.tabSpaces,
97 2*' '*self.controller.tabSpaces)
98 self.assert_(self.controller._indent_for_block(block) == \
99 2*(' '*self.controller.tabSpaces))
100
@@ -1,343 +0,0 b''
1 # encoding: utf-8
2 # -*- test-case-name: IPython.frontend.tests.test_frontendbase -*-
3 """
4 frontendbase provides an interface and base class for GUI frontends for
5 IPython.kernel/IPython.kernel.core.
6
7 Frontend implementations will likely want to subclass FrontEndBase.
8
9 Author: Barry Wark
10 """
11 __docformat__ = "restructuredtext en"
12
13 #-------------------------------------------------------------------------------
14 # Copyright (C) 2008-2011 The IPython Development Team
15 #
16 # Distributed under the terms of the BSD License. The full license is in
17 # the file COPYING, distributed as part of this software.
18 #-------------------------------------------------------------------------------
19
20 #-------------------------------------------------------------------------------
21 # Imports
22 #-------------------------------------------------------------------------------
23 import string
24 import codeop
25 import uuid
26
27
28 from IPython.frontend.zopeinterface import (
29 Interface,
30 Attribute,
31 )
32 from IPython.kernel.core.history import FrontEndHistory
33 from IPython.kernel.core.util import Bunch
34
35 ##############################################################################
36 # TEMPORARY!!! fake configuration, while we decide whether to use tconfig or
37 # not
38
39 rc = Bunch()
40 rc.prompt_in1 = r'In [$number]: '
41 rc.prompt_in2 = r'...'
42 rc.prompt_out = r'Out [$number]: '
43
44 ##############################################################################
45 # Interface definitions
46 ##############################################################################
47
48 class IFrontEndFactory(Interface):
49 """Factory interface for frontends."""
50
51 def __call__(engine=None, history=None):
52 """
53 Parameters:
54 interpreter : IPython.kernel.engineservice.IEngineCore
55 """
56
57 pass
58
59
60 class IFrontEnd(Interface):
61 """Interface for frontends. All methods return t.i.d.Deferred"""
62
63 Attribute("input_prompt_template", "string.Template instance\
64 substituteable with execute result.")
65 Attribute("output_prompt_template", "string.Template instance\
66 substituteable with execute result.")
67 Attribute("continuation_prompt_template", "string.Template instance\
68 substituteable with execute result.")
69
70 def update_cell_prompt(result, blockID=None):
71 """Subclass may override to update the input prompt for a block.
72
73 In asynchronous frontends, this method will be called as a
74 twisted.internet.defer.Deferred's callback/errback.
75 Implementations should thus return result when finished.
76
77 Result is a result dict in case of success, and a
78 twisted.python.util.failure.Failure in case of an error
79 """
80
81 pass
82
83 def render_result(result):
84 """Render the result of an execute call. Implementors may choose the
85 method of rendering.
86 For example, a notebook-style frontend might render a Chaco plot
87 inline.
88
89 Parameters:
90 result : dict (result of IEngineBase.execute )
91 blockID = result['blockID']
92
93 Result:
94 Output of frontend rendering
95 """
96
97 pass
98
99 def render_error(failure):
100 """Subclasses must override to render the failure.
101
102 In asynchronous frontend, since this method will be called as a
103 twisted.internet.defer.Deferred's callback. Implementations
104 should thus return result when finished.
105
106 blockID = failure.blockID
107 """
108
109 pass
110
111 def input_prompt(number=''):
112 """Returns the input prompt by subsituting into
113 self.input_prompt_template
114 """
115 pass
116
117 def output_prompt(number=''):
118 """Returns the output prompt by subsituting into
119 self.output_prompt_template
120 """
121
122 pass
123
124 def continuation_prompt():
125 """Returns the continuation prompt by subsituting into
126 self.continuation_prompt_template
127 """
128
129 pass
130
131 def is_complete(block):
132 """Returns True if block is complete, False otherwise."""
133
134 pass
135
136
137 def get_history_previous(current_block):
138 """Returns the block previous in the history. Saves currentBlock if
139 the history_cursor is currently at the end of the input history"""
140 pass
141
142 def get_history_next():
143 """Returns the next block in the history."""
144
145 pass
146
147 def complete(self, line):
148 """Returns the list of possible completions, and the completed
149 line.
150
151 The input argument is the full line to be completed. This method
152 returns both the line completed as much as possible, and the list
153 of further possible completions (full words).
154 """
155 pass
156
157
158 ##############################################################################
159 # Base class for all the frontends.
160 ##############################################################################
161
162 class FrontEndBase(object):
163 """
164 FrontEndBase manages the state tasks for a CLI frontend:
165 - Input and output history management
166 - Input/continuation and output prompt generation
167
168 Some issues (due to possibly unavailable engine):
169 - How do we get the current cell number for the engine?
170 - How do we handle completions?
171 """
172
173 history_cursor = 0
174
175 input_prompt_template = string.Template(rc.prompt_in1)
176 output_prompt_template = string.Template(rc.prompt_out)
177 continuation_prompt_template = string.Template(rc.prompt_in2)
178
179 def __init__(self, shell=None, history=None):
180 self.shell = shell
181 if history is None:
182 self.history = FrontEndHistory(input_cache=[''])
183 else:
184 self.history = history
185
186
187 def input_prompt(self, number=''):
188 """Returns the current input prompt
189
190 It would be great to use ipython1.core.prompts.Prompt1 here
191 """
192 return self.input_prompt_template.safe_substitute({'number':number})
193
194
195 def continuation_prompt(self):
196 """Returns the current continuation prompt"""
197
198 return self.continuation_prompt_template.safe_substitute()
199
200 def output_prompt(self, number=''):
201 """Returns the output prompt for result"""
202
203 return self.output_prompt_template.safe_substitute({'number':number})
204
205
206 def is_complete(self, block):
207 """Determine if block is complete.
208
209 Parameters
210 block : string
211
212 Result
213 True if block can be sent to the engine without compile errors.
214 False otherwise.
215 """
216
217 try:
218 is_complete = codeop.compile_command(block.rstrip() + '\n\n',
219 "<string>", "exec")
220 except:
221 return False
222
223 lines = block.split('\n')
224 return ((is_complete is not None)
225 and (len(lines)==1 or str(lines[-1])==''))
226
227
228 def execute(self, block, blockID=None):
229 """Execute the block and return the result.
230
231 Parameters:
232 block : {str, AST}
233 blockID : any
234 Caller may provide an ID to identify this block.
235 result['blockID'] := blockID
236
237 Result:
238 Deferred result of self.interpreter.execute
239 """
240
241 if(not self.is_complete(block)):
242 raise Exception("Block is not compilable")
243
244 if(blockID == None):
245 blockID = uuid.uuid4()
246
247 try:
248 result = self.shell.execute(block)
249 except Exception as e:
250 e = self._add_block_id_for_failure(e, blockID=blockID)
251 e = self.update_cell_prompt(e, blockID=blockID)
252 e = self.render_error(e)
253 else:
254 result = self._add_block_id_for_result(result, blockID=blockID)
255 result = self.update_cell_prompt(result, blockID=blockID)
256 result = self.render_result(result)
257
258 return result
259
260
261 def _add_block_id_for_result(self, result, blockID):
262 """Add the blockID to result or failure. Unfortunatley, we have to
263 treat failures differently than result dicts.
264 """
265
266 result['blockID'] = blockID
267
268 return result
269
270 def _add_block_id_for_failure(self, failure, blockID):
271 """_add_block_id_for_failure"""
272 failure.blockID = blockID
273 return failure
274
275
276 def _add_history(self, result, block=None):
277 """Add block to the history"""
278
279 assert(block != None)
280 self.history.add_items([block])
281 self.history_cursor += 1
282
283 return result
284
285
286 def get_history_previous(self, current_block):
287 """ Returns previous history string and decrement history cursor.
288 """
289 command = self.history.get_history_item(self.history_cursor - 1)
290
291 if command is not None:
292 if(self.history_cursor+1 == len(self.history.input_cache)):
293 self.history.input_cache[self.history_cursor] = current_block
294 self.history_cursor -= 1
295 return command
296
297
298 def get_history_next(self):
299 """ Returns next history string and increment history cursor.
300 """
301 command = self.history.get_history_item(self.history_cursor+1)
302
303 if command is not None:
304 self.history_cursor += 1
305 return command
306
307 ###
308 # Subclasses probably want to override these methods...
309 ###
310
311 def update_cell_prompt(self, result, blockID=None):
312 """Subclass may override to update the input prompt for a block.
313
314 This method only really makes sens in asyncrhonous frontend.
315 Since this method will be called as a
316 twisted.internet.defer.Deferred's callback, implementations should
317 return result when finished.
318 """
319
320 raise NotImplementedError
321
322
323 def render_result(self, result):
324 """Subclasses must override to render result.
325
326 In asynchronous frontends, this method will be called as a
327 twisted.internet.defer.Deferred's callback. Implementations
328 should thus return result when finished.
329 """
330
331 raise NotImplementedError
332
333
334 def render_error(self, failure):
335 """Subclasses must override to render the failure.
336
337 In asynchronous frontends, this method will be called as a
338 twisted.internet.defer.Deferred's callback. Implementations
339 should thus return result when finished.
340 """
341
342 raise NotImplementedError
343
@@ -1,373 +0,0 b''
1 """
2 Base front end class for all line-oriented frontends, rather than
3 block-oriented.
4
5 Currently this focuses on synchronous frontends.
6 """
7 __docformat__ = "restructuredtext en"
8
9 #-------------------------------------------------------------------------------
10 # Copyright (C) 2008-2011 The IPython Development Team
11 #
12 # Distributed under the terms of the BSD License. The full license is in
13 # the file COPYING, distributed as part of this software.
14 #-------------------------------------------------------------------------------
15
16 #-------------------------------------------------------------------------------
17 # Imports
18 #-------------------------------------------------------------------------------
19 import re
20
21 import sys
22 import codeop
23
24 from frontendbase import FrontEndBase
25 from IPython.kernel.core.interpreter import Interpreter
26
27 def common_prefix(strings):
28 """ Given a list of strings, return the common prefix between all
29 these strings.
30 """
31 ref = strings[0]
32 prefix = ''
33 for size in range(len(ref)):
34 test_prefix = ref[:size+1]
35 for string in strings[1:]:
36 if not string.startswith(test_prefix):
37 return prefix
38 prefix = test_prefix
39
40 return prefix
41
42 #-----------------------------------------------------------------------------
43 # Base class for the line-oriented front ends
44 #-----------------------------------------------------------------------------
45
46 class LineFrontEndBase(FrontEndBase):
47 """ Concrete implementation of the FrontEndBase class. This is meant
48 to be the base class behind all the frontend that are line-oriented,
49 rather than block-oriented.
50 """
51
52 # We need to keep the prompt number, to be able to increment
53 # it when there is an exception.
54 prompt_number = 1
55
56 # We keep a reference to the last result: it helps testing and
57 # programatic control of the frontend.
58 last_result = dict(number=0)
59
60 # The last prompt displayed. Useful for continuation prompts.
61 last_prompt = ''
62
63 # The input buffer being edited
64 input_buffer = ''
65
66 # Set to true for debug output
67 debug = False
68
69 # A banner to print at startup
70 banner = None
71
72 #--------------------------------------------------------------------------
73 # FrontEndBase interface
74 #--------------------------------------------------------------------------
75
76 def __init__(self, shell=None, history=None, banner=None, *args, **kwargs):
77 if shell is None:
78 shell = Interpreter()
79 FrontEndBase.__init__(self, shell=shell, history=history)
80
81 if banner is not None:
82 self.banner = banner
83
84 def start(self):
85 """ Put the frontend in a state where it is ready for user
86 interaction.
87 """
88 if self.banner is not None:
89 self.write(self.banner, refresh=False)
90
91 self.new_prompt(self.input_prompt_template.substitute(number=1))
92
93
94 def complete(self, line):
95 """Complete line in engine's user_ns
96
97 Parameters
98 ----------
99 line : string
100
101 Returns
102 -------
103 The replacement for the line and the list of possible completions.
104 """
105 completions = self.shell.complete(line)
106 complete_sep = re.compile('[\s\{\}\[\]\(\)\=]')
107 if completions:
108 prefix = common_prefix(completions)
109 residual = complete_sep.split(line)[:-1]
110 line = line[:-len(residual)] + prefix
111 return line, completions
112
113
114 def render_result(self, result):
115 """ Frontend-specific rendering of the result of a calculation
116 that has been sent to an engine.
117 """
118 if 'stdout' in result and result['stdout']:
119 self.write('\n' + result['stdout'])
120 if 'display' in result and result['display']:
121 self.write("%s%s\n" % (
122 self.output_prompt_template.substitute(
123 number=result['number']),
124 result['display']['pprint']
125 ) )
126
127
128 def render_error(self, failure):
129 """ Frontend-specific rendering of error.
130 """
131 self.write('\n\n'+str(failure)+'\n\n')
132 return failure
133
134
135 def is_complete(self, string):
136 """ Check if a string forms a complete, executable set of
137 commands.
138
139 For the line-oriented frontend, multi-line code is not executed
140 as soon as it is complete: the users has to enter two line
141 returns.
142 """
143 if string in ('', '\n'):
144 # Prefiltering, eg through ipython0, may return an empty
145 # string although some operations have been accomplished. We
146 # thus want to consider an empty string as a complete
147 # statement.
148 return True
149 elif ( len(self.input_buffer.split('\n'))>2
150 and not re.findall(r"\n[\t ]*\n[\t ]*$", string)):
151 return False
152 else:
153 self.capture_output()
154 try:
155 # Add line returns here, to make sure that the statement is
156 # complete (except if '\' was used).
157 # This should probably be done in a different place (like
158 # maybe 'prefilter_input' method? For now, this works.
159 clean_string = string.rstrip('\n')
160 if not clean_string.endswith('\\'): clean_string +='\n\n'
161 is_complete = codeop.compile_command(clean_string,
162 "<string>", "exec")
163 self.release_output()
164 except Exception as e:
165 # XXX: Hack: return True so that the
166 # code gets executed and the error captured.
167 is_complete = True
168 return is_complete
169
170
171 def write(self, string, refresh=True):
172 """ Write some characters to the display.
173
174 Subclass should overide this method.
175
176 The refresh keyword argument is used in frontends with an
177 event loop, to choose whether the write should trigget an UI
178 refresh, and thus be syncrhonous, or not.
179 """
180 print >>sys.__stderr__, string
181
182
183 def execute(self, python_string, raw_string=None):
184 """ Stores the raw_string in the history, and sends the
185 python string to the interpreter.
186 """
187 if raw_string is None:
188 raw_string = python_string
189 # Create a false result, in case there is an exception
190 self.last_result = dict(number=self.prompt_number)
191
192 try:
193 try:
194 self.history.input_cache[-1] = raw_string.rstrip()
195 result = self.shell.execute(python_string)
196 self.last_result = result
197 self.render_result(result)
198 except:
199 self.show_traceback()
200 finally:
201 self.after_execute()
202
203
204 #--------------------------------------------------------------------------
205 # LineFrontEndBase interface
206 #--------------------------------------------------------------------------
207
208 def prefilter_input(self, string):
209 """ Prefilter the input to turn it in valid python.
210 """
211 string = string.replace('\r\n', '\n')
212 string = string.replace('\t', 4*' ')
213 # Clean the trailing whitespace
214 string = '\n'.join(l.rstrip() for l in string.split('\n'))
215 return string
216
217
218 def after_execute(self):
219 """ All the operations required after an execution to put the
220 terminal back in a shape where it is usable.
221 """
222 self.prompt_number += 1
223 self.new_prompt(self.input_prompt_template.substitute(
224 number=(self.last_result['number'] + 1)))
225 # Start a new empty history entry
226 self._add_history(None, '')
227 self.history_cursor = len(self.history.input_cache) - 1
228
229
230 def complete_current_input(self):
231 """ Do code completion on current line.
232 """
233 if self.debug:
234 print >>sys.__stdout__, "complete_current_input",
235 line = self.input_buffer
236 new_line, completions = self.complete(line)
237 if len(completions)>1:
238 self.write_completion(completions, new_line=new_line)
239 elif not line == new_line:
240 self.input_buffer = new_line
241 if self.debug:
242 print >>sys.__stdout__, 'line', line
243 print >>sys.__stdout__, 'new_line', new_line
244 print >>sys.__stdout__, completions
245
246
247 def get_line_width(self):
248 """ Return the width of the line in characters.
249 """
250 return 80
251
252
253 def write_completion(self, possibilities, new_line=None):
254 """ Write the list of possible completions.
255
256 new_line is the completed input line that should be displayed
257 after the completion are writen. If None, the input_buffer
258 before the completion is used.
259 """
260 if new_line is None:
261 new_line = self.input_buffer
262
263 self.write('\n')
264 max_len = len(max(possibilities, key=len)) + 1
265
266 # Now we check how much symbol we can put on a line...
267 chars_per_line = self.get_line_width()
268 symbols_per_line = max(1, chars_per_line/max_len)
269
270 pos = 1
271 completion_string = []
272 for symbol in possibilities:
273 if pos < symbols_per_line:
274 completion_string.append(symbol.ljust(max_len))
275 pos += 1
276 else:
277 completion_string.append(symbol.rstrip() + '\n')
278 pos = 1
279 self.write(''.join(completion_string))
280 self.new_prompt(self.input_prompt_template.substitute(
281 number=self.last_result['number'] + 1))
282 self.input_buffer = new_line
283
284
285 def new_prompt(self, prompt):
286 """ Prints a prompt and starts a new editing buffer.
287
288 Subclasses should use this method to make sure that the
289 terminal is put in a state favorable for a new line
290 input.
291 """
292 self.input_buffer = ''
293 self.write(prompt)
294
295
296 def continuation_prompt(self):
297 """Returns the current continuation prompt.
298 """
299 return ("."*(len(self.last_prompt)-2) + ': ')
300
301
302 def execute_command(self, command, hidden=False):
303 """ Execute a command, not only in the model, but also in the
304 view, if any.
305 """
306 return self.shell.execute(command)
307
308 #--------------------------------------------------------------------------
309 # Private API
310 #--------------------------------------------------------------------------
311
312 def _on_enter(self, new_line_pos=0):
313 """ Called when the return key is pressed in a line editing
314 buffer.
315
316 Parameters
317 ----------
318 new_line_pos : integer, optional
319 Position of the new line to add, starting from the
320 end (0 adds a new line after the last line, -1 before
321 the last line...)
322
323 Returns
324 -------
325 True if execution is triggered
326 """
327 current_buffer = self.input_buffer
328 # XXX: This string replace is ugly, but there should be no way it
329 # fails.
330 prompt_less_buffer = re.sub('^' + self.continuation_prompt(),
331 '', current_buffer).replace('\n' + self.continuation_prompt(),
332 '\n')
333 cleaned_buffer = self.prefilter_input(prompt_less_buffer)
334 if self.is_complete(cleaned_buffer):
335 self.execute(cleaned_buffer, raw_string=current_buffer)
336 return True
337 else:
338 # Start a new line.
339 new_line_pos = -new_line_pos
340 lines = current_buffer.split('\n')[:-1]
341 prompt_less_lines = prompt_less_buffer.split('\n')
342 # Create the new line, with the continuation prompt, and the
343 # same amount of indent than the line above it.
344 new_line = self.continuation_prompt() + \
345 self._get_indent_string('\n'.join(
346 prompt_less_lines[:new_line_pos-1]))
347 if len(lines) == 1:
348 # We are starting a first continuation line. Indent it.
349 new_line += '\t'
350 elif current_buffer[:-1].split('\n')[-1].rstrip().endswith(':'):
351 # The last line ends with ":", autoindent the new line.
352 new_line += '\t'
353
354 if new_line_pos == 0:
355 lines.append(new_line)
356 else:
357 lines.insert(new_line_pos, new_line)
358 self.input_buffer = '\n'.join(lines)
359
360
361 def _get_indent_string(self, string):
362 """ Return the string of whitespace that prefixes a line. Used to
363 add the right amount of indendation when creating a new line.
364 """
365 string = string.replace('\t', ' '*4)
366 string = string.split('\n')[-1]
367 indent_chars = len(string) - len(string.lstrip())
368 indent_string = '\t'*(indent_chars // 4) + \
369 ' '*(indent_chars % 4)
370
371 return indent_string
372
373
@@ -1,256 +0,0 b''
1 """
2 Frontend class that uses IPython0 to prefilter the inputs.
3
4 Using the IPython0 mechanism gives us access to the magics.
5
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
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
10 refactoring.
11 """
12
13 #-------------------------------------------------------------------------------
14 # Copyright (C) 2008-2011 The IPython Development Team
15 #
16 # Distributed under the terms of the BSD License. The full license is in
17 # the file COPYING, distributed as part of this software.
18 #-------------------------------------------------------------------------------
19
20 #-------------------------------------------------------------------------------
21 # Imports
22 #-------------------------------------------------------------------------------
23 import sys
24 import pydoc
25 import os
26 import re
27 import __builtin__
28
29 from IPython.core.iplib import InteractiveShell
30 from IPython.kernel.core.redirector_output_trap import RedirectorOutputTrap
31
32 from IPython.kernel.core.sync_traceback_trap import SyncTracebackTrap
33
34 import IPython.utils.io
35
36 from linefrontendbase import LineFrontEndBase, common_prefix
37
38 #-----------------------------------------------------------------------------
39 # Utility functions
40 #-----------------------------------------------------------------------------
41
42 def mk_system_call(system_call_function, command):
43 """ given a os.system replacement, and a leading string command,
44 returns a function that will execute the command with the given
45 argument string.
46 """
47 def my_system_call(args):
48 system_call_function("%s %s" % (command, args))
49
50 my_system_call.__doc__ = "Calls %s" % command
51 return my_system_call
52
53 #-----------------------------------------------------------------------------
54 # Frontend class using ipython0 to do the prefiltering.
55 #-----------------------------------------------------------------------------
56
57 class PrefilterFrontEnd(LineFrontEndBase):
58 """ Class that uses ipython0 to do prefilter the input, do the
59 completion and the magics.
60
61 The core trick is to use an ipython0 instance to prefilter the
62 input, and share the namespace between the interpreter instance used
63 to execute the statements and the ipython0 used for code
64 completion...
65 """
66
67 debug = False
68
69 def __init__(self, ipython0=None, *args, **kwargs):
70 """ Parameters
71 ----------
72
73 ipython0: an optional ipython0 instance to use for command
74 prefiltering and completion.
75 """
76 LineFrontEndBase.__init__(self, *args, **kwargs)
77 self.shell.output_trap = RedirectorOutputTrap(
78 out_callback=self.write,
79 err_callback=self.write,
80 )
81 self.shell.traceback_trap = SyncTracebackTrap(
82 formatters=self.shell.traceback_trap.formatters,
83 )
84
85 # Start the ipython0 instance:
86 self.save_output_hooks()
87 if ipython0 is None:
88 # Instanciate an IPython0 InteractiveShell to be able to use the
89 # prefiltering.
90 # Suppress all key input, to avoid waiting
91 def my_rawinput(x=None):
92 return '\n'
93 old_rawinput = __builtin__.raw_input
94 __builtin__.raw_input = my_rawinput
95 ipython0 = InteractiveShell(
96 parent=None, user_ns=self.shell.user_ns,
97 user_global_ns=self.shell.user_global_ns
98 )
99 __builtin__.raw_input = old_rawinput
100 self.ipython0 = ipython0
101 # Set the pager:
102 self.ipython0.set_hook('show_in_pager',
103 lambda s, string: self.write("\n" + string))
104 self.ipython0.write = self.write
105 self._ip = _ip = self.ipython0
106 # Make sure the raw system call doesn't get called, as we don't
107 # have a stdin accessible.
108 self._ip.system = self.system_call
109 # XXX: Muck around with magics so that they work better
110 # in our environment
111 if not sys.platform.startswith('win'):
112 self.ipython0.magic_ls = mk_system_call(self.system_call,
113 'ls -CF')
114 # And now clean up the mess created by ipython0
115 self.release_output()
116
117
118 if not 'banner' in kwargs and self.banner is None:
119 self.banner = self.ipython0.banner
120
121 # FIXME: __init__ and start should be two different steps
122 self.start()
123
124 #--------------------------------------------------------------------------
125 # FrontEndBase interface
126 #--------------------------------------------------------------------------
127
128 def show_traceback(self):
129 """ Use ipython0 to capture the last traceback and display it.
130 """
131 # Don't do the capture; the except_hook has already done some
132 # modifications to the IO streams, if we store them, we'll be
133 # storing the wrong ones.
134 #self.capture_output()
135 self.ipython0.showtraceback(tb_offset=-1)
136 self.release_output()
137
138
139 def execute(self, python_string, raw_string=None):
140 if self.debug:
141 print 'Executing Python code:', repr(python_string)
142 self.capture_output()
143 LineFrontEndBase.execute(self, python_string,
144 raw_string=raw_string)
145 self.release_output()
146
147
148 def save_output_hooks(self):
149 """ Store all the output hooks we can think of, to be able to
150 restore them.
151
152 We need to do this early, as starting the ipython0 instance will
153 screw ouput hooks.
154 """
155 self.__old_cout_write = Term.cout.write
156 self.__old_cerr_write = Term.cerr.write
157 self.__old_stdout = sys.stdout
158 self.__old_stderr= sys.stderr
159 self.__old_help_output = pydoc.help.output
160 self.__old_display_hook = sys.displayhook
161
162
163 def capture_output(self):
164 """ Capture all the output mechanisms we can think of.
165 """
166 self.save_output_hooks()
167 Term.cout.write = self.write
168 Term.cerr.write = self.write
169 sys.stdout = Term.cout
170 sys.stderr = Term.cerr
171 pydoc.help.output = self.shell.output_trap.out
172
173
174 def release_output(self):
175 """ Release all the different captures we have made.
176 """
177 Term.cout.write = self.__old_cout_write
178 Term.cerr.write = self.__old_cerr_write
179 sys.stdout = self.__old_stdout
180 sys.stderr = self.__old_stderr
181 pydoc.help.output = self.__old_help_output
182 sys.displayhook = self.__old_display_hook
183
184
185 def complete(self, line):
186 # FIXME: This should be factored out in the linefrontendbase
187 # method.
188 word = self._get_completion_text(line)
189 completions = self.ipython0.complete(word)
190 # FIXME: The proper sort should be done in the complete method.
191 key = lambda x: x.replace('_', '')
192 completions.sort(key=key)
193 if completions:
194 prefix = common_prefix(completions)
195 line = line[:-len(word)] + prefix
196 return line, completions
197
198 #--------------------------------------------------------------------------
199 # LineFrontEndBase interface
200 #--------------------------------------------------------------------------
201
202 def prefilter_input(self, input_string):
203 """ Using IPython0 to prefilter the commands to turn them
204 in executable statements that are valid Python strings.
205 """
206 input_string = LineFrontEndBase.prefilter_input(self, input_string)
207 filtered_lines = []
208 # The IPython0 prefilters sometime produce output. We need to
209 # capture it.
210 self.capture_output()
211 self.last_result = dict(number=self.prompt_number)
212
213 try:
214 try:
215 for line in input_string.split('\n'):
216 pf = self.ipython0.prefilter_manager.prefilter_lines
217 filtered_lines.append(pf(line, False).rstrip())
218 except:
219 # XXX: probably not the right thing to do.
220 self.ipython0.showsyntaxerror()
221 self.after_execute()
222 finally:
223 self.release_output()
224
225 # Clean up the trailing whitespace, to avoid indentation errors
226 filtered_string = '\n'.join(filtered_lines)
227 return filtered_string
228
229 #--------------------------------------------------------------------------
230 # PrefilterFrontEnd interface
231 #--------------------------------------------------------------------------
232
233 def system_call(self, command_string):
234 """ Allows for frontend to define their own system call, to be
235 able capture output and redirect input.
236 """
237 return os.system(command_string)
238
239 def do_exit(self):
240 """ Exit the shell, cleanup and save the history.
241 """
242 self.ipython0.atexit_operations()
243
244 def _get_completion_text(self, line):
245 """ Returns the text to be completed by breaking the line at specified
246 delimiters.
247 """
248 # Break at: spaces, '=', all parentheses (except if balanced).
249 # FIXME2: In the future, we need to make the implementation similar to
250 # that in the 'pyreadline' module (modes/basemode.py) where we break at
251 # each delimiter and try to complete the residual line, until we get a
252 # successful list of completions.
253 expression = '\s|=|,|:|\((?!.*\))|\[(?!.*\])|\{(?!.*\})'
254 complete_sep = re.compile(expression)
255 text = complete_sep.split(line)[-1]
256 return text
@@ -1,19 +0,0 b''
1 """
2 Package for dealing for process execution in a callback environment, in a
3 portable way.
4
5 killable_process.py is a wrapper of subprocess.Popen that allows the
6 subprocess and its children to be killed in a reliable way, including
7 under windows.
8
9 winprocess.py is required by killable_process.py to kill processes under
10 windows.
11
12 piped_process.py wraps process execution with callbacks to print output,
13 in a non-blocking way. It can be used to interact with a subprocess in eg
14 a GUI event loop.
15 """
16
17 from pipedprocess import PipedProcess
18
19
@@ -1,184 +0,0 b''
1 # Addapted from killableprocess.py.
2 #______________________________________________________________________________
3 #
4 # killableprocess - subprocesses which can be reliably killed
5 #
6 # Parts of this module are copied from the subprocess.py file contained
7 # in the Python distribution.
8 #
9 # Copyright (c) 2003-2004 by Peter Astrand <astrand@lysator.liu.se>
10 #
11 # Additions and modifications written by Benjamin Smedberg
12 # <benjamin@smedbergs.us> are Copyright (c) 2006 by the Mozilla Foundation
13 # <http://www.mozilla.org/>
14 #
15 # By obtaining, using, and/or copying this software and/or its
16 # associated documentation, you agree that you have read, understood,
17 # and will comply with the following terms and conditions:
18 #
19 # Permission to use, copy, modify, and distribute this software and
20 # its associated documentation for any purpose and without fee is
21 # hereby granted, provided that the above copyright notice appears in
22 # all copies, and that both that copyright notice and this permission
23 # notice appear in supporting documentation, and that the name of the
24 # author not be used in advertising or publicity pertaining to
25 # distribution of the software without specific, written prior
26 # permission.
27 #
28 # THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
29 # INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS.
30 # IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, INDIRECT OR
31 # CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
32 # OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
33 # NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
34 # WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
35
36 r"""killableprocess - Subprocesses which can be reliably killed
37
38 This module is a subclass of the builtin "subprocess" module. It allows
39 processes that launch subprocesses to be reliably killed on Windows (via the Popen.kill() method.
40
41 It also adds a timeout argument to Wait() for a limited period of time before
42 forcefully killing the process.
43
44 Note: On Windows, this module requires Windows 2000 or higher (no support for
45 Windows 95, 98, or NT 4.0). It also requires ctypes, which is bundled with
46 Python 2.5+ or available from http://python.net/crew/theller/ctypes/
47 """
48
49 import subprocess
50 from subprocess import PIPE
51 import sys
52 import os
53 import types
54
55 try:
56 from subprocess import CalledProcessError
57 except ImportError:
58 # Python 2.4 doesn't implement CalledProcessError
59 class CalledProcessError(Exception):
60 """This exception is raised when a process run by check_call() returns
61 a non-zero exit status. The exit status will be stored in the
62 returncode attribute."""
63 def __init__(self, returncode, cmd):
64 self.returncode = returncode
65 self.cmd = cmd
66 def __str__(self):
67 return "Command '%s' returned non-zero exit status %d" % (self.cmd, self.returncode)
68
69 mswindows = (sys.platform == "win32")
70
71 skip = False
72
73 if mswindows:
74 import platform
75 if platform.uname()[3] == '' or platform.uname()[3] > '6.0.6000':
76 # Killable process does not work under vista when starting for
77 # something else than cmd.
78 skip = True
79 else:
80 import winprocess
81 else:
82 import signal
83
84 if not mswindows:
85 def DoNothing(*args):
86 pass
87
88
89 if skip:
90 Popen = subprocess.Popen
91 else:
92 class Popen(subprocess.Popen):
93 if not mswindows:
94 # Override __init__ to set a preexec_fn
95 def __init__(self, *args, **kwargs):
96 if len(args) >= 7:
97 raise Exception("Arguments preexec_fn and after must be passed by keyword.")
98
99 real_preexec_fn = kwargs.pop("preexec_fn", None)
100 def setpgid_preexec_fn():
101 os.setpgid(0, 0)
102 if real_preexec_fn:
103 apply(real_preexec_fn)
104
105 kwargs['preexec_fn'] = setpgid_preexec_fn
106
107 subprocess.Popen.__init__(self, *args, **kwargs)
108
109 if mswindows:
110 def _execute_child(self, args, executable, preexec_fn, close_fds,
111 cwd, env, universal_newlines, startupinfo,
112 creationflags, shell,
113 p2cread, p2cwrite,
114 c2pread, c2pwrite,
115 errread, errwrite):
116 if not isinstance(args, types.StringTypes):
117 args = subprocess.list2cmdline(args)
118
119 if startupinfo is None:
120 startupinfo = winprocess.STARTUPINFO()
121
122 if None not in (p2cread, c2pwrite, errwrite):
123 startupinfo.dwFlags |= winprocess.STARTF_USESTDHANDLES
124
125 startupinfo.hStdInput = int(p2cread)
126 startupinfo.hStdOutput = int(c2pwrite)
127 startupinfo.hStdError = int(errwrite)
128 if shell:
129 startupinfo.dwFlags |= winprocess.STARTF_USESHOWWINDOW
130 startupinfo.wShowWindow = winprocess.SW_HIDE
131 comspec = os.environ.get("COMSPEC", "cmd.exe")
132 args = comspec + " /c " + args
133
134 # We create a new job for this process, so that we can kill
135 # the process and any sub-processes
136 self._job = winprocess.CreateJobObject()
137
138 creationflags |= winprocess.CREATE_SUSPENDED
139 creationflags |= winprocess.CREATE_UNICODE_ENVIRONMENT
140
141 hp, ht, pid, tid = winprocess.CreateProcess(
142 executable, args,
143 None, None, # No special security
144 1, # Must inherit handles!
145 creationflags,
146 winprocess.EnvironmentBlock(env),
147 cwd, startupinfo)
148
149 self._child_created = True
150 self._handle = hp
151 self._thread = ht
152 self.pid = pid
153
154 # XXX: A try/except to fix UAC-related problems under
155 # Windows Vista, when reparenting jobs.
156 try:
157 winprocess.AssignProcessToJobObject(self._job, hp)
158 except WindowsError:
159 pass
160 winprocess.ResumeThread(ht)
161
162 if p2cread is not None:
163 p2cread.Close()
164 if c2pwrite is not None:
165 c2pwrite.Close()
166 if errwrite is not None:
167 errwrite.Close()
168
169 def kill(self, group=True):
170 """Kill the process. If group=True, all sub-processes will also be killed."""
171 if mswindows:
172 if group:
173 winprocess.TerminateJobObject(self._job, 127)
174 else:
175 winprocess.TerminateProcess(self._handle, 127)
176 self.returncode = 127
177 else:
178 if group:
179 os.killpg(self.pid, signal.SIGKILL)
180 else:
181 os.kill(self.pid, signal.SIGKILL)
182 self.returncode = -9
183
184
@@ -1,74 +0,0 b''
1 # encoding: utf-8
2 """
3 Object for encapsulating process execution by using callbacks for stdout,
4 stderr and stdin.
5 """
6 __docformat__ = "restructuredtext en"
7
8 #-------------------------------------------------------------------------------
9 # Copyright (C) 2008-2011 The IPython Development Team
10 #
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
13 #-------------------------------------------------------------------------------
14
15 #-------------------------------------------------------------------------------
16 # Imports
17 #-------------------------------------------------------------------------------
18 from killableprocess import Popen, PIPE
19 from threading import Thread
20 from time import sleep
21 import os
22
23 class PipedProcess(Thread):
24 """ Class that encapsulates process execution by using callbacks for
25 stdout, stderr and stdin, and providing a reliable way of
26 killing it.
27 """
28
29 def __init__(self, command_string, out_callback,
30 end_callback=None,):
31 """ command_string: the command line executed to start the
32 process.
33
34 out_callback: the python callable called on stdout/stderr.
35
36 end_callback: an optional callable called when the process
37 finishes.
38
39 These callbacks are called from a different thread as the
40 thread from which is started.
41 """
42 self.command_string = command_string
43 self.out_callback = out_callback
44 self.end_callback = end_callback
45 Thread.__init__(self)
46
47
48 def run(self):
49 """ Start the process and hook up the callbacks.
50 """
51 env = os.environ
52 env['TERM'] = 'xterm'
53 process = Popen(self.command_string + ' 2>&1', shell=True,
54 env=env,
55 universal_newlines=True,
56 stdout=PIPE, stdin=PIPE, )
57 self.process = process
58 while True:
59 out_char = process.stdout.read(1)
60 if out_char == '':
61 if process.poll() is not None:
62 # The process has finished
63 break
64 else:
65 # The process is not giving any interesting
66 # output. No use polling it immediatly.
67 sleep(0.1)
68 else:
69 self.out_callback(out_char)
70
71 if self.end_callback is not None:
72 self.end_callback()
73
74
@@ -1,264 +0,0 b''
1 # A module to expose various thread/process/job related structures and
2 # methods from kernel32
3 #
4 # The MIT License
5 #
6 # Copyright (c) 2006 the Mozilla Foundation <http://www.mozilla.org>
7 #
8 # Permission is hereby granted, free of charge, to any person obtaining a
9 # copy of this software and associated documentation files (the "Software"),
10 # to deal in the Software without restriction, including without limitation
11 # the rights to use, copy, modify, merge, publish, distribute, sublicense,
12 # and/or sell copies of the Software, and to permit persons to whom the
13 # Software is furnished to do so, subject to the following conditions:
14 #
15 # The above copyright notice and this permission notice shall be included in
16 # all copies or substantial portions of the Software.
17 #
18 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23 # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
24 # DEALINGS IN THE SOFTWARE.
25
26 from ctypes import c_void_p, POINTER, sizeof, Structure, windll, WinError, WINFUNCTYPE
27 from ctypes.wintypes import BOOL, BYTE, DWORD, HANDLE, LPCWSTR, LPWSTR, UINT, WORD
28
29 LPVOID = c_void_p
30 LPBYTE = POINTER(BYTE)
31 LPDWORD = POINTER(DWORD)
32
33 SW_HIDE = 0
34
35 def ErrCheckBool(result, func, args):
36 """errcheck function for Windows functions that return a BOOL True
37 on success"""
38 if not result:
39 raise WinError()
40 return args
41
42 # CloseHandle()
43
44 CloseHandleProto = WINFUNCTYPE(BOOL, HANDLE)
45 CloseHandle = CloseHandleProto(("CloseHandle", windll.kernel32))
46 CloseHandle.errcheck = ErrCheckBool
47
48 # AutoHANDLE
49
50 class AutoHANDLE(HANDLE):
51 """Subclass of HANDLE which will call CloseHandle() on deletion."""
52 def Close(self):
53 if self.value:
54 CloseHandle(self)
55 self.value = 0
56
57 def __del__(self):
58 self.Close()
59
60 def __int__(self):
61 return self.value
62
63 def ErrCheckHandle(result, func, args):
64 """errcheck function for Windows functions that return a HANDLE."""
65 if not result:
66 raise WinError()
67 return AutoHANDLE(result)
68
69 # PROCESS_INFORMATION structure
70
71 class PROCESS_INFORMATION(Structure):
72 _fields_ = [("hProcess", HANDLE),
73 ("hThread", HANDLE),
74 ("dwProcessID", DWORD),
75 ("dwThreadID", DWORD)]
76
77 def __init__(self):
78 Structure.__init__(self)
79
80 self.cb = sizeof(self)
81
82 LPPROCESS_INFORMATION = POINTER(PROCESS_INFORMATION)
83
84 # STARTUPINFO structure
85
86 class STARTUPINFO(Structure):
87 _fields_ = [("cb", DWORD),
88 ("lpReserved", LPWSTR),
89 ("lpDesktop", LPWSTR),
90 ("lpTitle", LPWSTR),
91 ("dwX", DWORD),
92 ("dwY", DWORD),
93 ("dwXSize", DWORD),
94 ("dwYSize", DWORD),
95 ("dwXCountChars", DWORD),
96 ("dwYCountChars", DWORD),
97 ("dwFillAttribute", DWORD),
98 ("dwFlags", DWORD),
99 ("wShowWindow", WORD),
100 ("cbReserved2", WORD),
101 ("lpReserved2", LPBYTE),
102 ("hStdInput", HANDLE),
103 ("hStdOutput", HANDLE),
104 ("hStdError", HANDLE)
105 ]
106 LPSTARTUPINFO = POINTER(STARTUPINFO)
107
108 STARTF_USESHOWWINDOW = 0x01
109 STARTF_USESIZE = 0x02
110 STARTF_USEPOSITION = 0x04
111 STARTF_USECOUNTCHARS = 0x08
112 STARTF_USEFILLATTRIBUTE = 0x10
113 STARTF_RUNFULLSCREEN = 0x20
114 STARTF_FORCEONFEEDBACK = 0x40
115 STARTF_FORCEOFFFEEDBACK = 0x80
116 STARTF_USESTDHANDLES = 0x100
117
118 # EnvironmentBlock
119
120 class EnvironmentBlock:
121 """An object which can be passed as the lpEnv parameter of CreateProcess.
122 It is initialized with a dictionary."""
123
124 def __init__(self, dict):
125 if not dict:
126 self._as_parameter_ = None
127 else:
128 values = ["%s=%s" % (key, value)
129 for (key, value) in dict.iteritems()]
130 values.append("")
131 self._as_parameter_ = LPCWSTR("\0".join(values))
132
133 # CreateProcess()
134
135 CreateProcessProto = WINFUNCTYPE(BOOL, # Return type
136 LPCWSTR, # lpApplicationName
137 LPWSTR, # lpCommandLine
138 LPVOID, # lpProcessAttributes
139 LPVOID, # lpThreadAttributes
140 BOOL, # bInheritHandles
141 DWORD, # dwCreationFlags
142 LPVOID, # lpEnvironment
143 LPCWSTR, # lpCurrentDirectory
144 LPSTARTUPINFO, # lpStartupInfo
145 LPPROCESS_INFORMATION # lpProcessInformation
146 )
147
148 CreateProcessFlags = ((1, "lpApplicationName", None),
149 (1, "lpCommandLine"),
150 (1, "lpProcessAttributes", None),
151 (1, "lpThreadAttributes", None),
152 (1, "bInheritHandles", True),
153 (1, "dwCreationFlags", 0),
154 (1, "lpEnvironment", None),
155 (1, "lpCurrentDirectory", None),
156 (1, "lpStartupInfo"),
157 (2, "lpProcessInformation"))
158
159 def ErrCheckCreateProcess(result, func, args):
160 ErrCheckBool(result, func, args)
161 # return a tuple (hProcess, hThread, dwProcessID, dwThreadID)
162 pi = args[9]
163 return AutoHANDLE(pi.hProcess), AutoHANDLE(pi.hThread), pi.dwProcessID, pi.dwThreadID
164
165 CreateProcess = CreateProcessProto(("CreateProcessW", windll.kernel32),
166 CreateProcessFlags)
167 CreateProcess.errcheck = ErrCheckCreateProcess
168
169 CREATE_BREAKAWAY_FROM_JOB = 0x01000000
170 CREATE_DEFAULT_ERROR_MODE = 0x04000000
171 CREATE_NEW_CONSOLE = 0x00000010
172 CREATE_NEW_PROCESS_GROUP = 0x00000200
173 CREATE_NO_WINDOW = 0x08000000
174 CREATE_SUSPENDED = 0x00000004
175 CREATE_UNICODE_ENVIRONMENT = 0x00000400
176 DEBUG_ONLY_THIS_PROCESS = 0x00000002
177 DEBUG_PROCESS = 0x00000001
178 DETACHED_PROCESS = 0x00000008
179
180 # CreateJobObject()
181
182 CreateJobObjectProto = WINFUNCTYPE(HANDLE, # Return type
183 LPVOID, # lpJobAttributes
184 LPCWSTR # lpName
185 )
186
187 CreateJobObjectFlags = ((1, "lpJobAttributes", None),
188 (1, "lpName", None))
189
190 CreateJobObject = CreateJobObjectProto(("CreateJobObjectW", windll.kernel32),
191 CreateJobObjectFlags)
192 CreateJobObject.errcheck = ErrCheckHandle
193
194 # AssignProcessToJobObject()
195
196 AssignProcessToJobObjectProto = WINFUNCTYPE(BOOL, # Return type
197 HANDLE, # hJob
198 HANDLE # hProcess
199 )
200 AssignProcessToJobObjectFlags = ((1, "hJob"),
201 (1, "hProcess"))
202 AssignProcessToJobObject = AssignProcessToJobObjectProto(
203 ("AssignProcessToJobObject", windll.kernel32),
204 AssignProcessToJobObjectFlags)
205 AssignProcessToJobObject.errcheck = ErrCheckBool
206
207 # ResumeThread()
208
209 def ErrCheckResumeThread(result, func, args):
210 if result == -1:
211 raise WinError()
212
213 return args
214
215 ResumeThreadProto = WINFUNCTYPE(DWORD, # Return type
216 HANDLE # hThread
217 )
218 ResumeThreadFlags = ((1, "hThread"),)
219 ResumeThread = ResumeThreadProto(("ResumeThread", windll.kernel32),
220 ResumeThreadFlags)
221 ResumeThread.errcheck = ErrCheckResumeThread
222
223 # TerminateJobObject()
224
225 TerminateJobObjectProto = WINFUNCTYPE(BOOL, # Return type
226 HANDLE, # hJob
227 UINT # uExitCode
228 )
229 TerminateJobObjectFlags = ((1, "hJob"),
230 (1, "uExitCode", 127))
231 TerminateJobObject = TerminateJobObjectProto(
232 ("TerminateJobObject", windll.kernel32),
233 TerminateJobObjectFlags)
234 TerminateJobObject.errcheck = ErrCheckBool
235
236 # WaitForSingleObject()
237
238 WaitForSingleObjectProto = WINFUNCTYPE(DWORD, # Return type
239 HANDLE, # hHandle
240 DWORD, # dwMilliseconds
241 )
242 WaitForSingleObjectFlags = ((1, "hHandle"),
243 (1, "dwMilliseconds", -1))
244 WaitForSingleObject = WaitForSingleObjectProto(
245 ("WaitForSingleObject", windll.kernel32),
246 WaitForSingleObjectFlags)
247
248 INFINITE = -1
249 WAIT_TIMEOUT = 0x0102
250 WAIT_OBJECT_0 = 0x0
251 WAIT_ABANDONED = 0x0080
252
253 # GetExitCodeProcess()
254
255 GetExitCodeProcessProto = WINFUNCTYPE(BOOL, # Return type
256 HANDLE, # hProcess
257 LPDWORD, # lpExitCode
258 )
259 GetExitCodeProcessFlags = ((1, "hProcess"),
260 (2, "lpExitCode"))
261 GetExitCodeProcess = GetExitCodeProcessProto(
262 ("GetExitCodeProcess", windll.kernel32),
263 GetExitCodeProcessFlags)
264 GetExitCodeProcess.errcheck = ErrCheckBool
@@ -1,106 +0,0 b''
1 # encoding: utf-8
2 """This file contains unittests for the asyncfrontendbase module."""
3
4 #---------------------------------------------------------------------------
5 # Copyright (C) 2008-2011 The IPython Development Team
6 #
7 # Distributed under the terms of the BSD License. The full license is in
8 # the file COPYING, distributed as part of this software.
9 #---------------------------------------------------------------------------
10
11 #---------------------------------------------------------------------------
12 # Imports
13 #---------------------------------------------------------------------------
14
15 from twisted.trial import unittest
16
17 from IPython.frontend.asyncfrontendbase import AsyncFrontEndBase
18 from IPython.frontend import frontendbase
19 from IPython.kernel.engineservice import EngineService
20 from IPython.testing.parametric import Parametric, parametric
21
22 #-----------------------------------------------------------------------------
23 # Classes and functions
24 #-----------------------------------------------------------------------------
25
26 class FrontEndCallbackChecker(AsyncFrontEndBase):
27 """FrontEndBase subclass for checking callbacks"""
28 def __init__(self, engine=None, history=None):
29 super(FrontEndCallbackChecker, self).__init__(engine=engine,
30 history=history)
31 self.updateCalled = False
32 self.renderResultCalled = False
33 self.renderErrorCalled = False
34
35 def update_cell_prompt(self, result, blockID=None):
36 self.updateCalled = True
37 return result
38
39 def render_result(self, result):
40 self.renderResultCalled = True
41 return result
42
43 def render_error(self, failure):
44 self.renderErrorCalled = True
45 return failure
46
47
48 class TestAsyncFrontendBase(unittest.TestCase):
49 def setUp(self):
50 """Setup the EngineService and FrontEndBase"""
51
52 self.fb = FrontEndCallbackChecker(engine=EngineService())
53
54 def test_implements_IFrontEnd(self):
55 self.assert_(frontendbase.IFrontEnd.implementedBy(
56 AsyncFrontEndBase))
57
58 def test_is_complete_returns_False_for_incomplete_block(self):
59 block = """def test(a):"""
60 self.assert_(self.fb.is_complete(block) == False)
61
62 def test_is_complete_returns_True_for_complete_block(self):
63 block = """def test(a): pass"""
64 self.assert_(self.fb.is_complete(block))
65 block = """a=3"""
66 self.assert_(self.fb.is_complete(block))
67
68 def test_blockID_added_to_result(self):
69 block = """3+3"""
70 d = self.fb.execute(block, blockID='TEST_ID')
71 d.addCallback(lambda r: self.assert_(r['blockID']=='TEST_ID'))
72 return d
73
74 def test_blockID_added_to_failure(self):
75 block = "raise Exception()"
76 d = self.fb.execute(block,blockID='TEST_ID')
77 d.addErrback(lambda f: self.assert_(f.blockID=='TEST_ID'))
78 return d
79
80 def test_callbacks_added_to_execute(self):
81 d = self.fb.execute("10+10")
82 d.addCallback(lambda r: self.assert_(self.fb.updateCalled and self.fb.renderResultCalled))
83 return d
84
85 def test_error_callback_added_to_execute(self):
86 """Test that render_error called on execution error."""
87
88 d = self.fb.execute("raise Exception()")
89 d.addErrback(lambda f: self.assert_(self.fb.renderErrorCalled))
90 return d
91
92 def test_history_returns_expected_block(self):
93 """Make sure history browsing doesn't fail."""
94
95 blocks = ["a=1","a=2","a=3"]
96 d = self.fb.execute(blocks[0])
97 d.addCallback(lambda _: self.fb.execute(blocks[1]))
98 d.addCallback(lambda _: self.fb.execute(blocks[2]))
99 d.addCallback(lambda _: self.assert_(self.fb.get_history_previous("")==blocks[-2]))
100 d.addCallback(lambda _: self.assert_(self.fb.get_history_previous("")==blocks[-3]))
101 d.addCallback(lambda _: self.assert_(self.fb.get_history_next()==blocks[-2]))
102 return d
103
104 def test_history_returns_none_at_startup(self):
105 self.assert_(self.fb.get_history_previous("")==None)
106 self.assert_(self.fb.get_history_next()==None)
@@ -1,32 +0,0 b''
1 # encoding: utf-8
2 """
3 Test the basic functionality of frontendbase.
4 """
5
6 __docformat__ = "restructuredtext en"
7
8 #-------------------------------------------------------------------------------
9 # Copyright (C) 2008-2011 The IPython Development Team
10 #
11 # Distributed under the terms of the BSD License. The full license is
12 # in the file COPYING, distributed as part of this software.
13 #-------------------------------------------------------------------------------
14
15 from IPython.frontend.frontendbase import FrontEndBase
16
17 def test_iscomplete():
18 """ Check that is_complete works.
19 """
20 f = FrontEndBase()
21 assert f.is_complete('(a + a)')
22 assert not f.is_complete('(a + a')
23 assert f.is_complete('1')
24 assert not f.is_complete('1 + ')
25 assert not f.is_complete('1 + \n\n')
26 assert f.is_complete('if True:\n print 1\n')
27 assert not f.is_complete('if True:\n print 1')
28 assert f.is_complete('def f():\n print 1\n')
29
30 if __name__ == '__main__':
31 test_iscomplete()
32
@@ -1,37 +0,0 b''
1 # encoding: utf-8
2 """
3 Test the LineFrontEnd
4 """
5
6 __docformat__ = "restructuredtext en"
7
8 #-------------------------------------------------------------------------------
9 # Copyright (C) 2008-2011 The IPython Development Team
10 #
11 # Distributed under the terms of the BSD License. The full license is
12 # in the file COPYING, distributed as part of this software.
13 #-------------------------------------------------------------------------------
14
15 from IPython.frontend.linefrontendbase import LineFrontEndBase
16 from copy import deepcopy
17 import nose.tools as nt
18
19 class ConcreteLineFrontEnd(LineFrontEndBase):
20 """ A concrete class to test the LineFrontEndBase.
21 """
22 def capture_output(self):
23 pass
24
25 def release_output(self):
26 pass
27
28
29 def test_is_complete():
30 """ Tests line completion heuristic.
31 """
32 frontend = ConcreteLineFrontEnd()
33 yield nt.assert_true, not frontend.is_complete('for x in \\')
34 yield nt.assert_true, not frontend.is_complete('for x in (1, ):')
35 yield nt.assert_true, frontend.is_complete('for x in (1, ):\n pass')
36
37
@@ -1,268 +0,0 b''
1 # encoding: utf-8
2 """
3 Test process execution and IO redirection.
4 """
5
6 __docformat__ = "restructuredtext en"
7
8 #-------------------------------------------------------------------------------
9 # Copyright (C) 2008-2011 The IPython Development Team
10 #
11 # Distributed under the terms of the BSD License. The full license is
12 # in the file COPYING, distributed as part of this software.
13 #-------------------------------------------------------------------------------
14
15 from copy import copy, deepcopy
16 from cStringIO import StringIO
17 import string
18 import sys
19
20 from nose.tools import assert_equal
21
22 from IPython.frontend.prefilterfrontend import PrefilterFrontEnd
23 from IPython.testing.globalipapp import get_ipython
24
25 #-----------------------------------------------------------------------------
26 # Support utilities
27 #-----------------------------------------------------------------------------
28
29 class TestPrefilterFrontEnd(PrefilterFrontEnd):
30
31 input_prompt_template = string.Template('')
32 output_prompt_template = string.Template('')
33 banner = ''
34
35 def __init__(self):
36 self.out = StringIO()
37 PrefilterFrontEnd.__init__(self)
38 # Some more code for isolation (yeah, crazy)
39 self._on_enter()
40 self.out.flush()
41 self.out.reset()
42 self.out.truncate()
43
44 def write(self, string, *args, **kwargs):
45 self.out.write(string)
46
47 def _on_enter(self):
48 self.input_buffer += '\n'
49 PrefilterFrontEnd._on_enter(self)
50
51
52 def isolate_ipython0(func):
53 """ Decorator to isolate execution that involves an iptyhon0.
54
55 Notes
56 -----
57
58 Apply only to functions with no arguments. Nose skips functions
59 with arguments.
60 """
61 def my_func():
62 ip0 = get_ipython()
63 if ip0 is None:
64 return func()
65 # We have a real ipython running...
66 user_ns = ip0.user_ns
67 user_global_ns = ip0.user_global_ns
68
69 # Previously the isolation was attempted with a deep copy of the user
70 # dicts, but we found cases where this didn't work correctly. I'm not
71 # quite sure why, but basically it did damage the user namespace, such
72 # that later tests stopped working correctly. Instead we use a simpler
73 # approach, just computing the list of added keys to the namespace and
74 # eliminating those afterwards. Existing keys that may have been
75 # modified remain modified. So far this has proven to be robust.
76
77 # Compute set of old local/global keys
78 old_locals = set(user_ns.keys())
79 old_globals = set(user_global_ns.keys())
80 try:
81 out = func()
82 finally:
83 # Find new keys, and if any, remove them
84 new_locals = set(user_ns.keys()) - old_locals
85 new_globals = set(user_global_ns.keys()) - old_globals
86 for k in new_locals:
87 del user_ns[k]
88 for k in new_globals:
89 del user_global_ns[k]
90 return out
91
92 my_func.__name__ = func.__name__
93 return my_func
94
95 #-----------------------------------------------------------------------------
96 # Tests
97 #-----------------------------------------------------------------------------
98
99 @isolate_ipython0
100 def test_execution():
101 """ Test execution of a command.
102 """
103 f = TestPrefilterFrontEnd()
104 f.input_buffer = 'print(1)'
105 f._on_enter()
106 out_value = f.out.getvalue()
107 assert_equal(out_value, '1\n')
108
109
110 @isolate_ipython0
111 def test_multiline():
112 """ Test execution of a multiline command.
113 """
114 f = TestPrefilterFrontEnd()
115 f.input_buffer = 'if True:'
116 f._on_enter()
117 f.input_buffer += 'print 1'
118 f._on_enter()
119 out_value = f.out.getvalue()
120 yield assert_equal, out_value, ''
121 f._on_enter()
122 out_value = f.out.getvalue()
123 yield assert_equal, out_value, '1\n'
124 f = TestPrefilterFrontEnd()
125 f.input_buffer='(1 +'
126 f._on_enter()
127 f.input_buffer += '0)'
128 f._on_enter()
129 out_value = f.out.getvalue()
130 yield assert_equal, out_value, ''
131 f._on_enter()
132 out_value = f.out.getvalue()
133 yield assert_equal, out_value, '1\n'
134
135
136 @isolate_ipython0
137 def test_capture():
138 """ Test the capture of output in different channels.
139 """
140 # Test on the OS-level stdout, stderr.
141 f = TestPrefilterFrontEnd()
142 f.input_buffer = \
143 'import os; out=os.fdopen(1, "w"); out.write("1") ; out.flush()'
144 f._on_enter()
145 out_value = f.out.getvalue()
146 yield assert_equal, out_value, '1'
147 f = TestPrefilterFrontEnd()
148 f.input_buffer = \
149 'import os; out=os.fdopen(2, "w"); out.write("1") ; out.flush()'
150 f._on_enter()
151 out_value = f.out.getvalue()
152 yield assert_equal, out_value, '1'
153
154
155 @isolate_ipython0
156 def test_magic():
157 """ Test the magic expansion and history.
158
159 This test is fairly fragile and will break when magics change.
160 """
161 f = TestPrefilterFrontEnd()
162 # Before checking the interactive namespace, make sure it's clear (it can
163 # otherwise pick up things stored in the user's local db)
164 f.input_buffer += '%reset -f'
165 f._on_enter()
166 f.complete_current_input()
167 # Now, run the %who magic and check output
168 f.input_buffer += '%who'
169 f._on_enter()
170 out_value = f.out.getvalue()
171 assert_equal(out_value, 'Interactive namespace is empty.\n')
172
173
174 @isolate_ipython0
175 def test_help():
176 """ Test object inspection.
177 """
178 f = TestPrefilterFrontEnd()
179 f.input_buffer += "def f():"
180 f._on_enter()
181 f.input_buffer += "'foobar'"
182 f._on_enter()
183 f.input_buffer += "pass"
184 f._on_enter()
185 f._on_enter()
186 f.input_buffer += "f?"
187 f._on_enter()
188 assert 'traceback' not in f.last_result
189 ## XXX: ipython doctest magic breaks this. I have no clue why
190 #out_value = f.out.getvalue()
191 #assert out_value.split()[-1] == 'foobar'
192
193
194 @isolate_ipython0
195 def test_completion_simple():
196 """ Test command-line completion on trivial examples.
197 """
198 f = TestPrefilterFrontEnd()
199 f.input_buffer = 'zzza = 1'
200 f._on_enter()
201 f.input_buffer = 'zzzb = 2'
202 f._on_enter()
203 f.input_buffer = 'zz'
204 f.complete_current_input()
205 out_value = f.out.getvalue()
206 yield assert_equal, out_value, '\nzzza zzzb '
207 yield assert_equal, f.input_buffer, 'zzz'
208
209
210 @isolate_ipython0
211 def test_completion_parenthesis():
212 """ Test command-line completion when a parenthesis is open.
213 """
214 f = TestPrefilterFrontEnd()
215 f.input_buffer = 'zzza = 1'
216 f._on_enter()
217 f.input_buffer = 'zzzb = 2'
218 f._on_enter()
219 f.input_buffer = 'map(zz'
220 f.complete_current_input()
221 out_value = f.out.getvalue()
222 yield assert_equal, out_value, '\nzzza zzzb '
223 yield assert_equal, f.input_buffer, 'map(zzz'
224
225
226 @isolate_ipython0
227 def test_completion_indexing():
228 """ Test command-line completion when indexing on objects.
229 """
230 f = TestPrefilterFrontEnd()
231 f.input_buffer = 'a = [0]'
232 f._on_enter()
233 f.input_buffer = 'a[0].'
234 f.complete_current_input()
235
236 if sys.version_info[:2] >= (2,6):
237 # In Python 2.6, ints picked up a few non __ methods, so now there are
238 # no completions.
239 assert_equal(f.input_buffer, 'a[0].')
240 else:
241 # Right answer for 2.4/2.5
242 assert_equal(f.input_buffer, 'a[0].__')
243
244
245 @isolate_ipython0
246 def test_completion_equal():
247 """ Test command-line completion when the delimiter is "=", not " ".
248 """
249 f = TestPrefilterFrontEnd()
250 f.input_buffer = 'a=1.'
251 f.complete_current_input()
252 if sys.version_info[:2] >= (2,6):
253 # In Python 2.6, ints picked up a few non __ methods, so now there are
254 # no completions.
255 assert_equal(f.input_buffer, 'a=1.')
256 else:
257 # Right answer for 2.4/2.5
258 assert_equal(f.input_buffer, 'a=1.__')
259
260
261 if __name__ == '__main__':
262 test_magic()
263 test_help()
264 test_execution()
265 test_multiline()
266 test_capture()
267 test_completion_simple()
268 test_completion_complex()
@@ -1,68 +0,0 b''
1 # encoding: utf-8
2 """
3 Test process execution and IO redirection.
4 """
5
6 __docformat__ = "restructuredtext en"
7
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2008-2011 The IPython Development Team
10 #
11 # Distributed under the terms of the BSD License. The full license is
12 # in the file COPYING, distributed as part of this software.
13 #-----------------------------------------------------------------------------
14
15 from cStringIO import StringIO
16 from time import sleep
17 import sys
18
19 from IPython.frontend.process import PipedProcess
20 from IPython.testing import decorators as dec
21
22
23 def test_capture_out():
24 """ A simple test to see if we can execute a process and get the output.
25 """
26 s = StringIO()
27 p = PipedProcess('echo 1', out_callback=s.write, )
28 p.start()
29 p.join()
30 result = s.getvalue().rstrip()
31 assert result == '1'
32
33
34 def test_io():
35 """ Checks that we can send characters on stdin to the process.
36 """
37 s = StringIO()
38 p = PipedProcess(sys.executable + ' -c "a = raw_input(); print a"',
39 out_callback=s.write, )
40 p.start()
41 test_string = '12345\n'
42 while not hasattr(p, 'process'):
43 sleep(0.1)
44 p.process.stdin.write(test_string)
45 p.join()
46 result = s.getvalue()
47 assert result == test_string
48
49
50 @dec.skip_win32
51 def test_kill():
52 """ Check that we can kill a process, and its subprocess.
53 """
54 s = StringIO()
55 p = PipedProcess(sys.executable + ' -c "a = raw_input();"',
56 out_callback=s.write, )
57 p.start()
58 while not hasattr(p, 'process'):
59 sleep(0.1)
60 p.process.kill()
61 assert p.process.poll() is not None
62
63
64 if __name__ == '__main__':
65 test_capture_out()
66 test_io()
67 test_kill()
68
This diff has been collapsed as it changes many lines, (625 lines changed) Show them Hide them
@@ -1,625 +0,0 b''
1 # encoding: utf-8
2 """
3 A Wx widget to act as a console and input commands.
4
5 This widget deals with prompts and provides an edit buffer
6 restricted to after the last prompt.
7 """
8
9 __docformat__ = "restructuredtext en"
10
11 #-------------------------------------------------------------------------------
12 # Copyright (C) 2008-2011 The IPython Development Team
13 #
14 # Distributed under the terms of the BSD License. The full license is
15 # in the file COPYING, distributed as part of this software.
16 #-------------------------------------------------------------------------------
17
18 #-------------------------------------------------------------------------------
19 # Imports
20 #-------------------------------------------------------------------------------
21
22 import wx
23 import wx.stc as stc
24
25 from wx.py import editwindow
26 import time
27 import sys
28 import string
29
30 LINESEP = '\n'
31 if sys.platform == 'win32':
32 LINESEP = '\n\r'
33
34 import re
35
36 # FIXME: Need to provide an API for non user-generated display on the
37 # screen: this should not be editable by the user.
38 #-------------------------------------------------------------------------------
39 # Constants
40 #-------------------------------------------------------------------------------
41 _COMPLETE_BUFFER_MARKER = 31
42 _ERROR_MARKER = 30
43 _INPUT_MARKER = 29
44
45 _DEFAULT_SIZE = 10
46 if sys.platform == 'darwin':
47 _DEFAULT_SIZE = 12
48
49 _DEFAULT_STYLE = {
50 #background definition
51 'default' : 'size:%d' % _DEFAULT_SIZE,
52 'bracegood' : 'fore:#00AA00,back:#000000,bold',
53 'bracebad' : 'fore:#FF0000,back:#000000,bold',
54
55 # Edge column: a number of None
56 'edge_column' : -1,
57
58 # properties for the various Python lexer styles
59 'comment' : 'fore:#007F00',
60 'number' : 'fore:#007F7F',
61 'string' : 'fore:#7F007F,italic',
62 'char' : 'fore:#7F007F,italic',
63 'keyword' : 'fore:#00007F,bold',
64 'triple' : 'fore:#7F0000',
65 'tripledouble' : 'fore:#7F0000',
66 'class' : 'fore:#0000FF,bold,underline',
67 'def' : 'fore:#007F7F,bold',
68 'operator' : 'bold',
69
70 # Default colors
71 'trace' : '#FAFAF1', # Nice green
72 'stdout' : '#FDFFD3', # Nice yellow
73 'stderr' : '#FFF1F1', # Nice red
74
75 # Default scintilla settings
76 'antialiasing' : True,
77 'carret_color' : 'BLACK',
78 'background_color' :'WHITE',
79
80 #prompt definition
81 'prompt_in1' : \
82 '\n\x01\x1b[0;34m\x02In [\x01\x1b[1;34m\x02$number\x01\x1b[0;34m\x02]: \x01\x1b[0m\x02',
83
84 'prompt_out': \
85 '\x01\x1b[0;31m\x02Out[\x01\x1b[1;31m\x02$number\x01\x1b[0;31m\x02]: \x01\x1b[0m\x02',
86 }
87
88 # new style numbers
89 _STDOUT_STYLE = 15
90 _STDERR_STYLE = 16
91 _TRACE_STYLE = 17
92
93
94 # system colors
95 #SYS_COLOUR_BACKGROUND = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BACKGROUND)
96
97 # Translation table from ANSI escape sequences to color.
98 ANSI_STYLES = {'0;30': [0, 'BLACK'], '0;31': [1, 'RED'],
99 '0;32': [2, 'GREEN'], '0;33': [3, 'BROWN'],
100 '0;34': [4, 'BLUE'], '0;35': [5, 'PURPLE'],
101 '0;36': [6, 'CYAN'], '0;37': [7, 'LIGHT GREY'],
102 '1;30': [8, 'DARK GREY'], '1;31': [9, 'RED'],
103 '1;32': [10, 'SEA GREEN'], '1;33': [11, 'YELLOW'],
104 '1;34': [12, 'LIGHT BLUE'], '1;35':
105 [13, 'MEDIUM VIOLET RED'],
106 '1;36': [14, 'LIGHT STEEL BLUE'], '1;37': [15, 'YELLOW']}
107
108 # XXX: Maybe one day we should factor this code with coloransi. Right now
109 # coloransi is hard to reuse and makes our code more complex.
110
111 #we define platform specific fonts
112 if wx.Platform == '__WXMSW__':
113 FACES = { 'times': 'Times New Roman',
114 'mono' : 'Courier New',
115 'helv' : 'Arial',
116 'other': 'Comic Sans MS',
117 'size' : 10,
118 'size2': 8,
119 }
120 elif wx.Platform == '__WXMAC__':
121 FACES = { 'times': 'Times New Roman',
122 'mono' : 'Monaco',
123 'helv' : 'Arial',
124 'other': 'Comic Sans MS',
125 'size' : 10,
126 'size2': 8,
127 }
128 else:
129 FACES = { 'times': 'Times',
130 'mono' : 'Courier',
131 'helv' : 'Helvetica',
132 'other': 'new century schoolbook',
133 'size' : 10,
134 'size2': 8,
135 }
136
137
138 #-----------------------------------------------------------------------------
139 # The console widget class
140 #-----------------------------------------------------------------------------
141
142 class ConsoleWidget(editwindow.EditWindow):
143 """ Specialized styled text control view for console-like workflow.
144
145 This widget is mainly interested in dealing with the prompt and
146 keeping the cursor inside the editing line.
147 """
148
149 # This is where the title captured from the ANSI escape sequences are
150 # stored.
151 title = 'Console'
152
153 # Last prompt printed
154 last_prompt = ''
155
156 # The buffer being edited.
157 def _set_input_buffer(self, string):
158 self.SetSelection(self.current_prompt_pos, self.GetLength())
159 self.ReplaceSelection(string)
160 self.GotoPos(self.GetLength())
161
162 def _get_input_buffer(self):
163 """ Returns the text in current edit buffer.
164 """
165 input_buffer = self.GetTextRange(self.current_prompt_pos,
166 self.GetLength())
167 input_buffer = input_buffer.replace(LINESEP, '\n')
168 return input_buffer
169
170 input_buffer = property(_get_input_buffer, _set_input_buffer)
171
172 style = _DEFAULT_STYLE.copy()
173
174 # Translation table from ANSI escape sequences to color. Override
175 # this to specify your colors.
176 ANSI_STYLES = ANSI_STYLES.copy()
177
178 # Font faces
179 faces = FACES.copy()
180
181 # Store the last time a refresh was done
182 _last_refresh_time = 0
183
184 #--------------------------------------------------------------------------
185 # Public API
186 #--------------------------------------------------------------------------
187
188 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
189 size=wx.DefaultSize, style=wx.WANTS_CHARS, ):
190 editwindow.EditWindow.__init__(self, parent, id, pos, size, style)
191 self.configure_scintilla()
192 # Track if 'enter' key as ever been processed
193 # This variable will only be reallowed until key goes up
194 self.enter_catched = False
195 self.current_prompt_pos = 0
196
197 self.Bind(wx.EVT_KEY_DOWN, self._on_key_down)
198 self.Bind(wx.EVT_KEY_UP, self._on_key_up)
199
200
201 def write(self, text, refresh=True):
202 """ Write given text to buffer, while translating the ansi escape
203 sequences.
204 """
205 # XXX: do not put print statements to sys.stdout/sys.stderr in
206 # this method, the print statements will call this method, as
207 # you will end up with an infinit loop
208 title = self.title_pat.split(text)
209 if len(title)>1:
210 self.title = title[-2]
211
212 text = self.title_pat.sub('', text)
213 segments = self.color_pat.split(text)
214 segment = segments.pop(0)
215 self.GotoPos(self.GetLength())
216 self.StartStyling(self.GetLength(), 0xFF)
217 try:
218 self.AppendText(segment)
219 except UnicodeDecodeError:
220 # XXX: Do I really want to skip the exception?
221 pass
222
223 if segments:
224 for ansi_tag, text in zip(segments[::2], segments[1::2]):
225 self.StartStyling(self.GetLength(), 0xFF)
226 try:
227 self.AppendText(text)
228 except UnicodeDecodeError:
229 # XXX: Do I really want to skip the exception?
230 pass
231
232 if ansi_tag not in self.ANSI_STYLES:
233 style = 0
234 else:
235 style = self.ANSI_STYLES[ansi_tag][0]
236
237 self.SetStyling(len(text), style)
238
239 self.GotoPos(self.GetLength())
240 if refresh:
241 current_time = time.time()
242 if current_time - self._last_refresh_time > 0.03:
243 if sys.platform == 'win32':
244 wx.SafeYield()
245 else:
246 wx.Yield()
247 # self.ProcessEvent(wx.PaintEvent())
248 self._last_refresh_time = current_time
249
250
251 def new_prompt(self, prompt):
252 """ Prints a prompt at start of line, and move the start of the
253 current block there.
254
255 The prompt can be given with ascii escape sequences.
256 """
257 self.write(prompt, refresh=False)
258 # now we update our cursor giving end of prompt
259 self.current_prompt_pos = self.GetLength()
260 self.current_prompt_line = self.GetCurrentLine()
261 self.EnsureCaretVisible()
262 self.last_prompt = prompt
263
264
265 def continuation_prompt(self):
266 """ Returns the current continuation prompt.
267 We need to implement this method here to deal with the
268 ascii escape sequences cleaning up.
269 """
270 # ASCII-less prompt
271 ascii_less = ''.join(self.color_pat.split(self.last_prompt)[2::2])
272 return "."*(len(ascii_less)-2) + ': '
273
274
275 def scroll_to_bottom(self):
276 maxrange = self.GetScrollRange(wx.VERTICAL)
277 self.ScrollLines(maxrange)
278
279
280 def pop_completion(self, possibilities, offset=0):
281 """ Pops up an autocompletion menu. Offset is the offset
282 in characters of the position at which the menu should
283 appear, relativ to the cursor.
284 """
285 self.AutoCompSetIgnoreCase(False)
286 self.AutoCompSetAutoHide(False)
287 self.AutoCompSetMaxHeight(len(possibilities))
288 self.AutoCompShow(offset, " ".join(possibilities))
289
290
291 def get_line_width(self):
292 """ Return the width of the line in characters.
293 """
294 return self.GetSize()[0]/self.GetCharWidth()
295
296
297 def configure_scintilla(self):
298 """ Set up all the styling option of the embedded scintilla
299 widget.
300 """
301 p = self.style.copy()
302
303 # Marker for complete buffer.
304 self.MarkerDefine(_COMPLETE_BUFFER_MARKER, stc.STC_MARK_BACKGROUND,
305 background=p['trace'])
306
307 # Marker for current input buffer.
308 self.MarkerDefine(_INPUT_MARKER, stc.STC_MARK_BACKGROUND,
309 background=p['stdout'])
310 # Marker for tracebacks.
311 self.MarkerDefine(_ERROR_MARKER, stc.STC_MARK_BACKGROUND,
312 background=p['stderr'])
313
314 self.SetEOLMode(stc.STC_EOL_LF)
315
316 # Ctrl"+" or Ctrl "-" can be used to zoomin/zoomout the text inside
317 # the widget
318 self.CmdKeyAssign(ord('+'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMIN)
319 self.CmdKeyAssign(ord('-'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMOUT)
320 # Also allow Ctrl Shift "=" for poor non US keyboard users.
321 self.CmdKeyAssign(ord('='), stc.STC_SCMOD_CTRL|stc.STC_SCMOD_SHIFT,
322 stc.STC_CMD_ZOOMIN)
323
324 # Keys: we need to clear some of the keys the that don't play
325 # well with a console.
326 self.CmdKeyClear(ord('D'), stc.STC_SCMOD_CTRL)
327 self.CmdKeyClear(ord('L'), stc.STC_SCMOD_CTRL)
328 self.CmdKeyClear(ord('T'), stc.STC_SCMOD_CTRL)
329 self.CmdKeyClear(ord('A'), stc.STC_SCMOD_CTRL)
330
331 self.SetEOLMode(stc.STC_EOL_CRLF)
332 self.SetWrapMode(stc.STC_WRAP_CHAR)
333 self.SetWrapMode(stc.STC_WRAP_WORD)
334 self.SetBufferedDraw(True)
335
336 self.SetUseAntiAliasing(p['antialiasing'])
337
338 self.SetLayoutCache(stc.STC_CACHE_PAGE)
339 self.SetUndoCollection(False)
340 self.SetUseTabs(True)
341 self.SetIndent(4)
342 self.SetTabWidth(4)
343
344 # we don't want scintilla's autocompletion to choose
345 # automaticaly out of a single choice list, as we pop it up
346 # automaticaly
347 self.AutoCompSetChooseSingle(False)
348 self.AutoCompSetMaxHeight(10)
349 # XXX: this doesn't seem to have an effect.
350 self.AutoCompSetFillUps('\n')
351
352 self.SetMargins(3, 3) #text is moved away from border with 3px
353 # Suppressing Scintilla margins
354 self.SetMarginWidth(0, 0)
355 self.SetMarginWidth(1, 0)
356 self.SetMarginWidth(2, 0)
357
358 # Xterm escape sequences
359 self.color_pat = re.compile('\x01?\x1b\[(.*?)m\x02?')
360 self.title_pat = re.compile('\x1b]0;(.*?)\x07')
361
362 # styles
363
364 self.SetCaretForeground(p['carret_color'])
365
366 background_color = p['background_color']
367
368 if 'default' in p:
369 if 'back' not in p['default']:
370 p['default'] += ',back:%s' % background_color
371 if 'size' not in p['default']:
372 p['default'] += ',size:%s' % self.faces['size']
373 if 'face' not in p['default']:
374 p['default'] += ',face:%s' % self.faces['mono']
375
376 self.StyleSetSpec(stc.STC_STYLE_DEFAULT, p['default'])
377 else:
378 self.StyleSetSpec(stc.STC_STYLE_DEFAULT,
379 "fore:%s,back:%s,size:%d,face:%s"
380 % (self.ANSI_STYLES['0;30'][1],
381 background_color,
382 self.faces['size'], self.faces['mono']))
383
384 self.StyleClearAll()
385
386 # XXX: two lines below are usefull if not using the lexer
387 #for style in self.ANSI_STYLES.values():
388 # self.StyleSetSpec(style[0], "bold,fore:%s" % style[1])
389
390 # prompt definition
391 self.prompt_in1 = p['prompt_in1']
392 self.prompt_out = p['prompt_out']
393
394 self.output_prompt_template = string.Template(self.prompt_out)
395 self.input_prompt_template = string.Template(self.prompt_in1)
396
397 self.StyleSetSpec(_STDOUT_STYLE, p['stdout'])
398 self.StyleSetSpec(_STDERR_STYLE, p['stderr'])
399 self.StyleSetSpec(_TRACE_STYLE, p['trace'])
400 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT, p['bracegood'])
401 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD, p['bracebad'])
402 self.StyleSetSpec(stc.STC_P_COMMENTLINE, p['comment'])
403 self.StyleSetSpec(stc.STC_P_NUMBER, p['number'])
404 self.StyleSetSpec(stc.STC_P_STRING, p['string'])
405 self.StyleSetSpec(stc.STC_P_CHARACTER, p['char'])
406 self.StyleSetSpec(stc.STC_P_WORD, p['keyword'])
407 self.StyleSetSpec(stc.STC_P_WORD2, p['keyword'])
408 self.StyleSetSpec(stc.STC_P_TRIPLE, p['triple'])
409 self.StyleSetSpec(stc.STC_P_TRIPLEDOUBLE, p['tripledouble'])
410 self.StyleSetSpec(stc.STC_P_CLASSNAME, p['class'])
411 self.StyleSetSpec(stc.STC_P_DEFNAME, p['def'])
412 self.StyleSetSpec(stc.STC_P_OPERATOR, p['operator'])
413 self.StyleSetSpec(stc.STC_P_COMMENTBLOCK, p['comment'])
414
415 edge_column = p['edge_column']
416 if edge_column is not None and edge_column > 0:
417 #we add a vertical line to console widget
418 self.SetEdgeMode(stc.STC_EDGE_LINE)
419 self.SetEdgeColumn(edge_column)
420
421
422 #--------------------------------------------------------------------------
423 # EditWindow API
424 #--------------------------------------------------------------------------
425
426 def OnUpdateUI(self, event):
427 """ Override the OnUpdateUI of the EditWindow class, to prevent
428 syntax highlighting both for faster redraw, and for more
429 consistent look and feel.
430 """
431
432
433 #--------------------------------------------------------------------------
434 # Private API
435 #--------------------------------------------------------------------------
436
437 def _on_key_down(self, event, skip=True):
438 """ Key press callback used for correcting behavior for
439 console-like interfaces: the cursor is constraint to be after
440 the last prompt.
441
442 Return True if event as been catched.
443 """
444 catched = True
445 # XXX: Would the right way to do this be to have a
446 # dictionary at the instance level associating keys with
447 # callbacks? How would we deal with inheritance? And Do the
448 # different callbacks share local variables?
449
450 # Intercept some specific keys.
451 key_code = event.GetKeyCode()
452 if key_code == ord('L') and event.ControlDown() :
453 self.scroll_to_bottom()
454 elif key_code == ord('K') and event.ControlDown() :
455 self.input_buffer = ''
456 elif key_code == ord('A') and event.ControlDown() :
457 self.GotoPos(self.GetLength())
458 self.SetSelectionStart(self.current_prompt_pos)
459 self.SetSelectionEnd(self.GetCurrentPos())
460 catched = True
461 elif key_code == ord('E') and event.ControlDown() :
462 self.GotoPos(self.GetLength())
463 catched = True
464 elif key_code == wx.WXK_PAGEUP:
465 self.ScrollPages(-1)
466 elif key_code == wx.WXK_PAGEDOWN:
467 self.ScrollPages(1)
468 elif key_code == wx.WXK_HOME:
469 self.GotoPos(self.GetLength())
470 elif key_code == wx.WXK_END:
471 self.GotoPos(self.GetLength())
472 elif key_code == wx.WXK_UP and event.ShiftDown():
473 self.ScrollLines(-1)
474 elif key_code == wx.WXK_DOWN and event.ShiftDown():
475 self.ScrollLines(1)
476 else:
477 catched = False
478
479 if self.AutoCompActive():
480 event.Skip()
481 else:
482 if key_code in (13, wx.WXK_NUMPAD_ENTER):
483 # XXX: not catching modifiers, to be wx2.6-compatible
484 catched = True
485 if not self.enter_catched:
486 self.CallTipCancel()
487 if event.ShiftDown():
488 # Try to force execution
489 self.GotoPos(self.GetLength())
490 self.write('\n' + self.continuation_prompt(),
491 refresh=False)
492 self._on_enter()
493 else:
494 self._on_enter()
495 self.enter_catched = True
496
497 elif key_code == wx.WXK_HOME:
498 if not event.ShiftDown():
499 self.GotoPos(self.current_prompt_pos)
500 catched = True
501 else:
502 # FIXME: This behavior is not ideal: if the selection
503 # is already started, it will jump.
504 self.SetSelectionStart(self.current_prompt_pos)
505 self.SetSelectionEnd(self.GetCurrentPos())
506 catched = True
507
508 elif key_code == wx.WXK_UP:
509 if self.GetCurrentLine() > self.current_prompt_line:
510 if self.GetCurrentLine() == self.current_prompt_line + 1 \
511 and self.GetColumn(self.GetCurrentPos()) < \
512 self.GetColumn(self.current_prompt_pos):
513 self.GotoPos(self.current_prompt_pos)
514 else:
515 event.Skip()
516 catched = True
517
518 elif key_code in (wx.WXK_LEFT, wx.WXK_BACK):
519 if not self._keep_cursor_in_buffer(self.GetCurrentPos() - 1):
520 event.Skip()
521 catched = True
522
523 elif key_code == wx.WXK_RIGHT:
524 if not self._keep_cursor_in_buffer(self.GetCurrentPos() + 1):
525 event.Skip()
526 catched = True
527
528
529 elif key_code == wx.WXK_DELETE:
530 if not self._keep_cursor_in_buffer(self.GetCurrentPos() - 1):
531 event.Skip()
532 catched = True
533
534 if skip and not catched:
535 # Put the cursor back in the edit region
536 if not self._keep_cursor_in_buffer():
537 if not (self.GetCurrentPos() == self.GetLength()
538 and key_code == wx.WXK_DELETE):
539 event.Skip()
540 catched = True
541
542 return catched
543
544
545 def _on_key_up(self, event, skip=True):
546 """ If cursor is outside the editing region, put it back.
547 """
548 if skip:
549 event.Skip()
550 self._keep_cursor_in_buffer()
551
552
553 # XXX: I need to avoid the problem of having an empty glass;
554 def _keep_cursor_in_buffer(self, pos=None):
555 """ Checks if the cursor is where it is allowed to be. If not,
556 put it back.
557
558 Returns
559 -------
560 cursor_moved: Boolean
561 whether or not the cursor was moved by this routine.
562
563 Notes
564 ------
565 WARNING: This does proper checks only for horizontal
566 movements.
567 """
568 if pos is None:
569 current_pos = self.GetCurrentPos()
570 else:
571 current_pos = pos
572 if current_pos < self.current_prompt_pos:
573 self.GotoPos(self.current_prompt_pos)
574 return True
575 line_num = self.LineFromPosition(current_pos)
576 if not current_pos > self.GetLength():
577 line_pos = self.GetColumn(current_pos)
578 else:
579 line_pos = self.GetColumn(self.GetLength())
580 line = self.GetLine(line_num)
581 # Jump the continuation prompt
582 continuation_prompt = self.continuation_prompt()
583 if ( line.startswith(continuation_prompt)
584 and line_pos < len(continuation_prompt)):
585 if line_pos < 2:
586 # We are at the beginning of the line, trying to move
587 # forward: jump forward.
588 self.GotoPos(current_pos + 1 +
589 len(continuation_prompt) - line_pos)
590 else:
591 # Jump back up
592 self.GotoPos(self.GetLineEndPosition(line_num-1))
593 return True
594 elif ( current_pos > self.GetLineEndPosition(line_num)
595 and not current_pos == self.GetLength()):
596 # Jump to next line
597 self.GotoPos(current_pos + 1 +
598 len(continuation_prompt))
599 return True
600
601 # We re-allow enter event processing
602 self.enter_catched = False
603 return False
604
605
606 if __name__ == '__main__':
607 # Some simple code to test the console widget.
608 class MainWindow(wx.Frame):
609 def __init__(self, parent, id, title):
610 wx.Frame.__init__(self, parent, id, title, size=(300, 250))
611 self._sizer = wx.BoxSizer(wx.VERTICAL)
612 self.console_widget = ConsoleWidget(self)
613 self._sizer.Add(self.console_widget, 1, wx.EXPAND)
614 self.SetSizer(self._sizer)
615 self.SetAutoLayout(1)
616 self.Show(True)
617
618 app = wx.PySimpleApp()
619 w = MainWindow(None, wx.ID_ANY, 'ConsoleWidget')
620 w.SetSize((780, 460))
621 w.Show()
622
623 app.MainLoop()
624
625
@@ -1,118 +0,0 b''
1 """
2 Entry point for a simple application giving a graphical frontend to
3 ipython.
4 """
5
6 try:
7 import wx
8 except ImportError as e:
9 e.args[0] = """%s
10 ________________________________________________________________________________
11 You need wxPython to run this application.
12 """ % e.args[0]
13 raise e
14
15 from wx_frontend import WxController
16 import __builtin__
17
18
19 class IPythonXController(WxController):
20 """ Sub class of WxController that adds some application-specific
21 bindings.
22 """
23
24 debug = False
25
26 def __init__(self, *args, **kwargs):
27 WxController.__init__(self, *args, **kwargs)
28 self.ipython0.ask_exit = self.do_exit
29 # Scroll to top
30 maxrange = self.GetScrollRange(wx.VERTICAL)
31 self.ScrollLines(-maxrange)
32
33
34 def _on_key_down(self, event, skip=True):
35 # Intercept Ctrl-D to quit
36 if event.KeyCode == ord('D') and event.ControlDown() and \
37 self.input_buffer == '' and \
38 self._input_state == 'readline':
39 wx.CallAfter(self.ask_exit)
40 else:
41 WxController._on_key_down(self, event, skip=skip)
42
43
44 def ask_exit(self):
45 """ Ask the user whether to exit.
46 """
47 self._input_state = 'subprocess'
48 self.write('\n', refresh=False)
49 self.capture_output()
50 self.ipython0.exit()
51 self.release_output()
52 if not self.ipython0.exit_now:
53 wx.CallAfter(self.new_prompt,
54 self.input_prompt_template.substitute(
55 number=self.last_result['number'] + 1))
56 else:
57 wx.CallAfter(wx.GetApp().Exit)
58 self.write('Exiting ...', refresh=False)
59
60
61 def do_exit(self):
62 """ Exits the interpreter, kills the windows.
63 """
64 WxController.do_exit(self)
65 self.release_output()
66 wx.CallAfter(wx.Exit)
67
68
69
70 class IPythonX(wx.Frame):
71 """ Main frame of the IPythonX app.
72 """
73
74 def __init__(self, parent, id, title, debug=False):
75 wx.Frame.__init__(self, parent, id, title, size=(300,250))
76 self._sizer = wx.BoxSizer(wx.VERTICAL)
77 self.shell = IPythonXController(self, debug=debug)
78 self._sizer.Add(self.shell, 1, wx.EXPAND)
79 self.SetSizer(self._sizer)
80 self.SetAutoLayout(1)
81 self.Show(True)
82 wx.EVT_CLOSE(self, self.on_close)
83
84
85 def on_close(self, event):
86 """ Called on closing the windows.
87
88 Stops the event loop, to close all the child windows.
89 """
90 wx.CallAfter(wx.Exit)
91
92
93 def main():
94 from optparse import OptionParser
95 usage = """usage: %prog [options]
96
97 Simple graphical frontend to IPython, using WxWidgets."""
98 parser = OptionParser(usage=usage)
99 parser.add_option("-d", "--debug",
100 action="store_true", dest="debug", default=False,
101 help="Enable debug message for the wx frontend.")
102
103 options, args = parser.parse_args()
104
105 # Clear the options, to avoid having the ipython0 instance complain
106 import sys
107 sys.argv = sys.argv[:1]
108
109 app = wx.PySimpleApp()
110 frame = IPythonX(None, wx.ID_ANY, 'IPythonX', debug=options.debug)
111 frame.shell.SetFocus()
112 frame.shell.app = app
113 frame.SetSize((680, 460))
114
115 app.MainLoop()
116
117 if __name__ == '__main__':
118 main()
This diff has been collapsed as it changes many lines, (602 lines changed) Show them Hide them
@@ -1,602 +0,0 b''
1 # encoding: utf-8 -*- test-case-name:
2 # FIXME: Need to add tests.
3 # ipython1.frontend.wx.tests.test_wx_frontend -*-
4
5 """Classes to provide a Wx frontend to the
6 IPython.kernel.core.interpreter.
7
8 This class inherits from ConsoleWidget, that provides a console-like
9 widget to provide a text-rendering widget suitable for a terminal.
10 """
11
12 __docformat__ = "restructuredtext en"
13
14 #-------------------------------------------------------------------------------
15 # Copyright (C) 2008-2011 The IPython Development Team
16 #
17 # Distributed under the terms of the BSD License. The full license is in
18 # the file COPYING, distributed as part of this software.
19 #-------------------------------------------------------------------------------
20
21 #-------------------------------------------------------------------------------
22 # Imports
23 #-------------------------------------------------------------------------------
24
25 # Major library imports
26 import re
27 import __builtin__
28 import sys
29 from threading import Lock
30
31 import wx
32 from wx import stc
33
34 # Ipython-specific imports.
35 from IPython.frontend.process import PipedProcess
36 from console_widget import ConsoleWidget, _COMPLETE_BUFFER_MARKER, \
37 _ERROR_MARKER, _INPUT_MARKER
38 from IPython.frontend.prefilterfrontend import PrefilterFrontEnd
39
40 #-------------------------------------------------------------------------------
41 # Classes to implement the Wx frontend
42 #-------------------------------------------------------------------------------
43 class WxController(ConsoleWidget, PrefilterFrontEnd):
44 """Classes to provide a Wx frontend to the
45 IPython.kernel.core.interpreter.
46
47 This class inherits from ConsoleWidget, that provides a console-like
48 widget to provide a text-rendering widget suitable for a terminal.
49 """
50
51 # Print debug info on what is happening to the console.
52 debug = False
53
54 # The title of the terminal, as captured through the ANSI escape
55 # sequences.
56 def _set_title(self, title):
57 return self.Parent.SetTitle(title)
58
59 def _get_title(self):
60 return self.Parent.GetTitle()
61
62 title = property(_get_title, _set_title)
63
64
65 # The buffer being edited.
66 # We are duplicating the definition here because of multiple
67 # inheritence
68 def _set_input_buffer(self, string):
69 ConsoleWidget._set_input_buffer(self, string)
70 self._colorize_input_buffer()
71
72 def _get_input_buffer(self):
73 """ Returns the text in current edit buffer.
74 """
75 return ConsoleWidget._get_input_buffer(self)
76
77 input_buffer = property(_get_input_buffer, _set_input_buffer)
78
79
80 #--------------------------------------------------------------------------
81 # Private Attributes
82 #--------------------------------------------------------------------------
83
84 # A flag governing the behavior of the input. Can be:
85 #
86 # 'readline' for readline-like behavior with a prompt
87 # and an edit buffer.
88 # 'raw_input' similar to readline, but triggered by a raw-input
89 # call. Can be used by subclasses to act differently.
90 # 'subprocess' for sending the raw input directly to a
91 # subprocess.
92 # 'buffering' for buffering of the input, that will be used
93 # when the input state switches back to another state.
94 _input_state = 'readline'
95
96 # Attribute to store reference to the pipes of a subprocess, if we
97 # are running any.
98 _running_process = False
99
100 # A queue for writing fast streams to the screen without flooding the
101 # event loop
102 _out_buffer = []
103
104 # A lock to lock the _out_buffer to make sure we don't empty it
105 # while it is being swapped
106 _out_buffer_lock = Lock()
107
108 # The different line markers used to higlight the prompts.
109 _markers = dict()
110
111 #--------------------------------------------------------------------------
112 # Public API
113 #--------------------------------------------------------------------------
114
115 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
116 size=wx.DefaultSize,
117 style=wx.CLIP_CHILDREN|wx.WANTS_CHARS,
118 styledef=None,
119 *args, **kwds):
120 """ Create Shell instance.
121
122 Parameters
123 -----------
124 styledef : dict, optional
125 styledef is the dictionary of options used to define the
126 style.
127 """
128 if styledef is not None:
129 self.style = styledef
130 ConsoleWidget.__init__(self, parent, id, pos, size, style)
131 PrefilterFrontEnd.__init__(self, **kwds)
132
133 # Stick in our own raw_input:
134 self.ipython0.raw_input = self.raw_input
135
136 # A time for flushing the write buffer
137 BUFFER_FLUSH_TIMER_ID = 100
138 self._buffer_flush_timer = wx.Timer(self, BUFFER_FLUSH_TIMER_ID)
139 wx.EVT_TIMER(self, BUFFER_FLUSH_TIMER_ID, self._buffer_flush)
140
141 if 'debug' in kwds:
142 self.debug = kwds['debug']
143 kwds.pop('debug')
144
145 # Inject self in namespace, for debug
146 if self.debug:
147 self.shell.user_ns['self'] = self
148 # Inject our own raw_input in namespace
149 self.shell.user_ns['raw_input'] = self.raw_input
150
151 def raw_input(self, prompt=''):
152 """ A replacement from python's raw_input.
153 """
154 self.new_prompt(prompt)
155 self._input_state = 'raw_input'
156 if hasattr(self, '_cursor'):
157 del self._cursor
158 self.SetCursor(wx.StockCursor(wx.CURSOR_CROSS))
159 self.__old_on_enter = self._on_enter
160 event_loop = wx.EventLoop()
161 def my_on_enter():
162 event_loop.Exit()
163 self._on_enter = my_on_enter
164 # XXX: Running a separate event_loop. Ugly.
165 event_loop.Run()
166 self._on_enter = self.__old_on_enter
167 self._input_state = 'buffering'
168 self._cursor = wx.BusyCursor()
169 return self.input_buffer.rstrip('\n')
170
171
172 def system_call(self, command_string):
173 self._input_state = 'subprocess'
174 event_loop = wx.EventLoop()
175 def _end_system_call():
176 self._input_state = 'buffering'
177 self._running_process = False
178 event_loop.Exit()
179
180 self._running_process = PipedProcess(command_string,
181 out_callback=self.buffered_write,
182 end_callback = _end_system_call)
183 self._running_process.start()
184 # XXX: Running a separate event_loop. Ugly.
185 event_loop.Run()
186 # Be sure to flush the buffer.
187 self._buffer_flush(event=None)
188
189
190 def do_calltip(self):
191 """ Analyse current and displays useful calltip for it.
192 """
193 if self.debug:
194 print >>sys.__stdout__, "do_calltip"
195 separators = re.compile('[\s\{\}\[\]\(\)\= ,:]')
196 symbol = self.input_buffer
197 symbol_string = separators.split(symbol)[-1]
198 base_symbol_string = symbol_string.split('.')[0]
199 if base_symbol_string in self.shell.user_ns:
200 symbol = self.shell.user_ns[base_symbol_string]
201 elif base_symbol_string in self.shell.user_global_ns:
202 symbol = self.shell.user_global_ns[base_symbol_string]
203 elif base_symbol_string in __builtin__.__dict__:
204 symbol = __builtin__.__dict__[base_symbol_string]
205 else:
206 return False
207 try:
208 for name in symbol_string.split('.')[1:] + ['__doc__']:
209 symbol = getattr(symbol, name)
210 self.AutoCompCancel()
211 # Check that the symbol can indeed be converted to a string:
212 symbol += ''
213 wx.CallAfter(self.CallTipShow, self.GetCurrentPos(), symbol)
214 except:
215 # The retrieve symbol couldn't be converted to a string
216 pass
217
218
219 def _popup_completion(self, create=False):
220 """ Updates the popup completion menu if it exists. If create is
221 true, open the menu.
222 """
223 if self.debug:
224 print >>sys.__stdout__, "_popup_completion"
225 line = self.input_buffer
226 if (self.AutoCompActive() and line and not line[-1] == '.') \
227 or create==True:
228 suggestion, completions = self.complete(line)
229 if completions:
230 offset = len(self._get_completion_text(line))
231 self.pop_completion(completions, offset=offset)
232 if self.debug:
233 print >>sys.__stdout__, completions
234
235
236 def buffered_write(self, text):
237 """ A write method for streams, that caches the stream in order
238 to avoid flooding the event loop.
239
240 This can be called outside of the main loop, in separate
241 threads.
242 """
243 self._out_buffer_lock.acquire()
244 self._out_buffer.append(text)
245 self._out_buffer_lock.release()
246 if not self._buffer_flush_timer.IsRunning():
247 wx.CallAfter(self._buffer_flush_timer.Start,
248 milliseconds=100, oneShot=True)
249
250
251 def clear_screen(self):
252 """ Empty completely the widget.
253 """
254 self.ClearAll()
255 self.new_prompt(self.input_prompt_template.substitute(
256 number=(self.last_result['number'] + 1)))
257
258
259 #--------------------------------------------------------------------------
260 # LineFrontEnd interface
261 #--------------------------------------------------------------------------
262
263 def execute(self, python_string, raw_string=None):
264 self._input_state = 'buffering'
265 self.CallTipCancel()
266 self._cursor = wx.BusyCursor()
267 if raw_string is None:
268 raw_string = python_string
269 end_line = self.current_prompt_line \
270 + max(1, len(raw_string.split('\n'))-1)
271 for i in range(self.current_prompt_line, end_line):
272 if i in self._markers:
273 self.MarkerDeleteHandle(self._markers[i])
274 self._markers[i] = self.MarkerAdd(i, _COMPLETE_BUFFER_MARKER)
275 # Use a callafter to update the display robustly under windows
276 def callback():
277 self.GotoPos(self.GetLength())
278 PrefilterFrontEnd.execute(self, python_string,
279 raw_string=raw_string)
280 wx.CallAfter(callback)
281
282
283 def execute_command(self, command, hidden=False):
284 """ Execute a command, not only in the model, but also in the
285 view.
286 """
287 # XXX: This method needs to be integrated in the base fronted
288 # interface
289 if hidden:
290 return self.shell.execute(command)
291 else:
292 # XXX: we are not storing the input buffer previous to the
293 # execution, as this forces us to run the execution
294 # input_buffer a yield, which is not good.
295 ##current_buffer = self.shell.control.input_buffer
296 command = command.rstrip()
297 if len(command.split('\n')) > 1:
298 # The input command is several lines long, we need to
299 # force the execution to happen
300 command += '\n'
301 cleaned_command = self.prefilter_input(command)
302 self.input_buffer = command
303 # Do not use wx.Yield() (aka GUI.process_events()) to avoid
304 # recursive yields.
305 self.ProcessEvent(wx.PaintEvent())
306 self.write('\n')
307 if not self.is_complete(cleaned_command + '\n'):
308 self._colorize_input_buffer()
309 self.render_error('Incomplete or invalid input')
310 self.new_prompt(self.input_prompt_template.substitute(
311 number=(self.last_result['number'] + 1)))
312 return False
313 self._on_enter()
314 return True
315
316
317 def save_output_hooks(self):
318 self.__old_raw_input = __builtin__.raw_input
319 PrefilterFrontEnd.save_output_hooks(self)
320
321 def capture_output(self):
322 self.SetLexer(stc.STC_LEX_NULL)
323 PrefilterFrontEnd.capture_output(self)
324 __builtin__.raw_input = self.raw_input
325
326
327 def release_output(self):
328 __builtin__.raw_input = self.__old_raw_input
329 PrefilterFrontEnd.release_output(self)
330 self.SetLexer(stc.STC_LEX_PYTHON)
331
332
333 def after_execute(self):
334 PrefilterFrontEnd.after_execute(self)
335 # Clear the wait cursor
336 if hasattr(self, '_cursor'):
337 del self._cursor
338 self.SetCursor(wx.StockCursor(wx.CURSOR_CHAR))
339
340
341 def show_traceback(self):
342 start_line = self.GetCurrentLine()
343 PrefilterFrontEnd.show_traceback(self)
344 self.ProcessEvent(wx.PaintEvent())
345 #wx.Yield()
346 for i in range(start_line, self.GetCurrentLine()):
347 self._markers[i] = self.MarkerAdd(i, _ERROR_MARKER)
348
349
350 #--------------------------------------------------------------------------
351 # FrontEndBase interface
352 #--------------------------------------------------------------------------
353
354 def render_error(self, e):
355 start_line = self.GetCurrentLine()
356 self.write('\n' + e + '\n')
357 for i in range(start_line, self.GetCurrentLine()):
358 self._markers[i] = self.MarkerAdd(i, _ERROR_MARKER)
359
360
361 #--------------------------------------------------------------------------
362 # ConsoleWidget interface
363 #--------------------------------------------------------------------------
364
365 def new_prompt(self, prompt):
366 """ Display a new prompt, and start a new input buffer.
367 """
368 self._input_state = 'readline'
369 ConsoleWidget.new_prompt(self, prompt)
370 i = self.current_prompt_line
371 self._markers[i] = self.MarkerAdd(i, _INPUT_MARKER)
372
373
374 def continuation_prompt(self, *args, **kwargs):
375 # Avoid multiple inheritence, be explicit about which
376 # parent method class gets called
377 return ConsoleWidget.continuation_prompt(self, *args, **kwargs)
378
379
380 def write(self, *args, **kwargs):
381 # Avoid multiple inheritence, be explicit about which
382 # parent method class gets called
383 return ConsoleWidget.write(self, *args, **kwargs)
384
385
386 def _on_key_down(self, event, skip=True):
387 """ Capture the character events, let the parent
388 widget handle them, and put our logic afterward.
389 """
390 # FIXME: This method needs to be broken down in smaller ones.
391 current_line_num = self.GetCurrentLine()
392 key_code = event.GetKeyCode()
393 if key_code in (ord('c'), ord('C')) and event.ControlDown():
394 # Capture Control-C
395 if self._input_state == 'subprocess':
396 if self.debug:
397 print >>sys.__stderr__, 'Killing running process'
398 if hasattr(self._running_process, 'process'):
399 self._running_process.process.kill()
400 elif self._input_state == 'buffering':
401 if self.debug:
402 print >>sys.__stderr__, 'Raising KeyboardInterrupt'
403 raise KeyboardInterrupt
404 # XXX: We need to make really sure we
405 # get back to a prompt.
406 elif self._input_state == 'subprocess' and (
407 ( key_code <256 and not event.ControlDown() )
408 or
409 ( key_code in (ord('d'), ord('D')) and
410 event.ControlDown())):
411 # We are running a process, we redirect keys.
412 ConsoleWidget._on_key_down(self, event, skip=skip)
413 char = chr(key_code)
414 # Deal with some inconsistency in wx keycodes:
415 if char == '\r':
416 char = '\n'
417 elif not event.ShiftDown():
418 char = char.lower()
419 if event.ControlDown() and key_code in (ord('d'), ord('D')):
420 char = '\04'
421 self._running_process.process.stdin.write(char)
422 self._running_process.process.stdin.flush()
423 elif key_code in (ord('('), 57, 53):
424 # Calltips
425 event.Skip()
426 self.do_calltip()
427 elif self.AutoCompActive() and not key_code == ord('\t'):
428 event.Skip()
429 if key_code in (wx.WXK_BACK, wx.WXK_DELETE):
430 wx.CallAfter(self._popup_completion, create=True)
431 elif not key_code in (wx.WXK_UP, wx.WXK_DOWN, wx.WXK_LEFT,
432 wx.WXK_RIGHT, wx.WXK_ESCAPE):
433 wx.CallAfter(self._popup_completion)
434 else:
435 # Up history
436 if key_code == wx.WXK_UP and (
437 event.ControlDown() or
438 current_line_num == self.current_prompt_line
439 ):
440 new_buffer = self.get_history_previous(
441 self.input_buffer)
442 if new_buffer is not None:
443 self.input_buffer = new_buffer
444 if self.GetCurrentLine() > self.current_prompt_line:
445 # Go to first line, for seemless history up.
446 self.GotoPos(self.current_prompt_pos)
447 # Down history
448 elif key_code == wx.WXK_DOWN and (
449 event.ControlDown() or
450 current_line_num == self.LineCount -1
451 ):
452 new_buffer = self.get_history_next()
453 if new_buffer is not None:
454 self.input_buffer = new_buffer
455 # Tab-completion
456 elif key_code == ord('\t'):
457 current_line, current_line_num = self.CurLine
458 if not re.match(r'^%s\s*$' % self.continuation_prompt(),
459 current_line):
460 self.complete_current_input()
461 if self.AutoCompActive():
462 wx.CallAfter(self._popup_completion, create=True)
463 else:
464 event.Skip()
465 elif key_code == wx.WXK_BACK:
466 # If characters where erased, check if we have to
467 # remove a line.
468 # XXX: What about DEL?
469 # FIXME: This logics should be in ConsoleWidget, as it is
470 # independant of IPython
471 current_line, _ = self.CurLine
472 current_pos = self.GetCurrentPos()
473 current_line_num = self.LineFromPosition(current_pos)
474 current_col = self.GetColumn(current_pos)
475 len_prompt = len(self.continuation_prompt())
476 if ( current_line.startswith(self.continuation_prompt())
477 and current_col == len_prompt):
478 new_lines = []
479 for line_num, line in enumerate(
480 self.input_buffer.split('\n')):
481 if (line_num + self.current_prompt_line ==
482 current_line_num):
483 new_lines.append(line[len_prompt:])
484 else:
485 new_lines.append('\n'+line)
486 # The first character is '\n', due to the above
487 # code:
488 self.input_buffer = ''.join(new_lines)[1:]
489 self.GotoPos(current_pos - 1 - len_prompt)
490 else:
491 ConsoleWidget._on_key_down(self, event, skip=skip)
492 else:
493 ConsoleWidget._on_key_down(self, event, skip=skip)
494
495
496
497 def _on_key_up(self, event, skip=True):
498 """ Called when any key is released.
499 """
500 if event.GetKeyCode() in (59, ord('.')):
501 # Intercepting '.'
502 event.Skip()
503 wx.CallAfter(self._popup_completion, create=True)
504 else:
505 ConsoleWidget._on_key_up(self, event, skip=skip)
506 # Make sure the continuation_prompts are always followed by a
507 # whitespace
508 new_lines = []
509 if self._input_state == 'readline':
510 position = self.GetCurrentPos()
511 continuation_prompt = self.continuation_prompt()[:-1]
512 for line in self.input_buffer.split('\n'):
513 if not line == continuation_prompt:
514 new_lines.append(line)
515 self.input_buffer = '\n'.join(new_lines)
516 self.GotoPos(position)
517
518
519 def _on_enter(self):
520 """ Called on return key down, in readline input_state.
521 """
522 last_line_num = self.LineFromPosition(self.GetLength())
523 current_line_num = self.LineFromPosition(self.GetCurrentPos())
524 new_line_pos = (last_line_num - current_line_num)
525 if self.debug:
526 print >>sys.__stdout__, repr(self.input_buffer)
527 self.write('\n', refresh=False)
528 # Under windows scintilla seems to be doing funny
529 # stuff to the line returns here, but the getter for
530 # input_buffer filters this out.
531 if sys.platform == 'win32':
532 self.input_buffer = self.input_buffer
533 old_prompt_num = self.current_prompt_pos
534 has_executed = PrefilterFrontEnd._on_enter(self,
535 new_line_pos=new_line_pos)
536 if old_prompt_num == self.current_prompt_pos:
537 # No execution has happened
538 self.GotoPos(self.GetLineEndPosition(current_line_num + 1))
539 return has_executed
540
541
542 #--------------------------------------------------------------------------
543 # EditWindow API
544 #--------------------------------------------------------------------------
545
546 def OnUpdateUI(self, event):
547 """ Override the OnUpdateUI of the EditWindow class, to prevent
548 syntax highlighting both for faster redraw, and for more
549 consistent look and feel.
550 """
551 if not self._input_state == 'readline':
552 ConsoleWidget.OnUpdateUI(self, event)
553
554 #--------------------------------------------------------------------------
555 # Private API
556 #--------------------------------------------------------------------------
557
558 def _buffer_flush(self, event):
559 """ Called by the timer to flush the write buffer.
560
561 This is always called in the mainloop, by the wx timer.
562 """
563 self._out_buffer_lock.acquire()
564 _out_buffer = self._out_buffer
565 self._out_buffer = []
566 self._out_buffer_lock.release()
567 self.write(''.join(_out_buffer), refresh=False)
568
569
570 def _colorize_input_buffer(self):
571 """ Keep the input buffer lines at a bright color.
572 """
573 if not self._input_state in ('readline', 'raw_input'):
574 return
575 end_line = self.GetCurrentLine()
576 if not sys.platform == 'win32':
577 end_line += 1
578 for i in range(self.current_prompt_line, end_line):
579 if i in self._markers:
580 self.MarkerDeleteHandle(self._markers[i])
581 self._markers[i] = self.MarkerAdd(i, _INPUT_MARKER)
582
583
584 if __name__ == '__main__':
585 class MainWindow(wx.Frame):
586 def __init__(self, parent, id, title):
587 wx.Frame.__init__(self, parent, id, title, size=(300,250))
588 self._sizer = wx.BoxSizer(wx.VERTICAL)
589 self.shell = WxController(self)
590 self._sizer.Add(self.shell, 1, wx.EXPAND)
591 self.SetSizer(self._sizer)
592 self.SetAutoLayout(1)
593 self.Show(True)
594
595 app = wx.PySimpleApp()
596 frame = MainWindow(None, wx.ID_ANY, 'Ipython')
597 frame.shell.SetFocus()
598 frame.SetSize((680, 460))
599 self = frame.shell
600
601 app.MainLoop()
602
@@ -1,27 +0,0 b''
1 # encoding: utf-8
2 # -*- test-case-name: IPython.frontend.tests.test_frontendbase -*-
3 """
4 zope.interface mock. If zope is installed, this module provides a zope
5 interface classes, if not it provides mocks for them.
6
7 Classes provided:
8 Interface, Attribute, implements, classProvides
9 """
10 __docformat__ = "restructuredtext en"
11
12 #-------------------------------------------------------------------------------
13 # Copyright (C) 2008-2011 The IPython Development Team
14 #
15 # Distributed under the terms of the BSD License. The full license is in
16 # the file COPYING, distributed as part of this software.
17 #-------------------------------------------------------------------------------
18
19 try:
20 from zope.interface import Interface, Attribute, implements, classProvides
21 except ImportError:
22 #zope.interface is not available
23 Interface = object
24 def Attribute(name, doc): pass
25 def implements(interface): pass
26 def classProvides(interface): pass
27
@@ -1,258 +0,0 b''
1 # -*- coding: utf-8 -*-
2 """Class to trap stdout and stderr and log them separately.
3 """
4
5 #*****************************************************************************
6 # Copyright (C) 2001-2004 Fernando Perez <fperez@colorado.edu>
7 #
8 # Distributed under the terms of the BSD License. The full license is in
9 # the file COPYING, distributed as part of this software.
10 #*****************************************************************************
11
12 import exceptions
13 import sys
14 from cStringIO import StringIO
15
16 class OutputTrapError(exceptions.Exception):
17 """Exception for OutputTrap class."""
18
19 def __init__(self,args=None):
20 exceptions.Exception.__init__(self)
21 self.args = args
22
23 class OutputTrap:
24
25 """Class to trap standard output and standard error. They get logged in
26 StringIO objects which are available as <instance>.out and
27 <instance>.err. The class also offers summary methods which format this
28 data a bit.
29
30 A word of caution: because it blocks messages, using this class can make
31 debugging very tricky. If you are having bizarre problems silently, try
32 turning your output traps off for a while. You can call the constructor
33 with the parameter debug=1 for these cases. This turns actual trapping
34 off, but you can keep the rest of your code unchanged (this has already
35 been a life saver).
36
37 Example:
38
39 # config: trapper with a line of dots as log separator (final '\\n' needed)
40 config = OutputTrap('Config','Out ','Err ','.'*80+'\\n')
41
42 # start trapping output
43 config.trap_all()
44
45 # now all output is logged ...
46 # do stuff...
47
48 # output back to normal:
49 config.release_all()
50
51 # print all that got logged:
52 print config.summary()
53
54 # print individual raw data:
55 print config.out.getvalue()
56 print config.err.getvalue()
57 """
58
59 def __init__(self,name='Generic Output Trap',
60 out_head='Standard Output. ',err_head='Standard Error. ',
61 sum_sep='\n',debug=0,trap_out=0,trap_err=0,
62 quiet_out=0,quiet_err=0):
63 self.name = name
64 self.out_head = out_head
65 self.err_head = err_head
66 self.sum_sep = sum_sep
67 self.out = StringIO()
68 self.err = StringIO()
69 self.out_save = None
70 self.err_save = None
71 self.debug = debug
72 self.quiet_out = quiet_out
73 self.quiet_err = quiet_err
74 if trap_out:
75 self.trap_out()
76 if trap_err:
77 self.trap_err()
78
79 def trap_out(self):
80 """Trap and log stdout."""
81 if sys.stdout is self.out:
82 raise OutputTrapError,'You are already trapping stdout.'
83 if not self.debug:
84 self._out_save = sys.stdout
85 sys.stdout = self.out
86
87 def release_out(self):
88 """Release stdout."""
89 if not self.debug:
90 if not sys.stdout is self.out:
91 raise OutputTrapError,'You are not trapping stdout.'
92 sys.stdout = self._out_save
93 self.out_save = None
94
95 def summary_out(self):
96 """Return as a string the log from stdout."""
97 out = self.out.getvalue()
98 if out:
99 if self.quiet_out:
100 return out
101 else:
102 return self.out_head + 'Log by '+ self.name + ':\n' + out
103 else:
104 return ''
105
106 def flush_out(self):
107 """Flush the stdout log. All data held in the log is lost."""
108
109 self.out.close()
110 self.out = StringIO()
111
112 def trap_err(self):
113 """Trap and log stderr."""
114 if sys.stderr is self.err:
115 raise OutputTrapError,'You are already trapping stderr.'
116 if not self.debug:
117 self._err_save = sys.stderr
118 sys.stderr = self.err
119
120 def release_err(self):
121 """Release stderr."""
122 if not self.debug:
123 if not sys.stderr is self.err:
124 raise OutputTrapError,'You are not trapping stderr.'
125 sys.stderr = self._err_save
126 self.err_save = None
127
128 def summary_err(self):
129 """Return as a string the log from stderr."""
130 err = self.err.getvalue()
131 if err:
132 if self.quiet_err:
133 return err
134 else:
135 return self.err_head + 'Log by '+ self.name + ':\n' + err
136 else:
137 return ''
138
139 def flush_err(self):
140 """Flush the stdout log. All data held in the log is lost."""
141
142 self.err.close()
143 self.err = StringIO()
144
145 def trap_all(self):
146 """Trap and log both stdout and stderr.
147
148 Cacthes and discards OutputTrapError exceptions raised."""
149 try:
150 self.trap_out()
151 except OutputTrapError:
152 pass
153 try:
154 self.trap_err()
155 except OutputTrapError:
156 pass
157
158 def release_all(self):
159 """Release both stdout and stderr.
160
161 Cacthes and discards OutputTrapError exceptions raised."""
162 try:
163 self.release_out()
164 except OutputTrapError:
165 pass
166 try:
167 self.release_err()
168 except OutputTrapError:
169 pass
170
171 def summary_all(self):
172 """Return as a string the log from stdout and stderr, prepending a separator
173 to each (defined in __init__ as sum_sep)."""
174 sum = ''
175 sout = self.summary_out()
176 if sout:
177 sum += self.sum_sep + sout
178 serr = self.summary_err()
179 if serr:
180 sum += '\n'+self.sum_sep + serr
181 return sum
182
183 def flush_all(self):
184 """Flush stdout and stderr"""
185 self.flush_out()
186 self.flush_err()
187
188 # a few shorthands
189 trap = trap_all
190 release = release_all
191 summary = summary_all
192 flush = flush_all
193 # end OutputTrap
194
195
196 #****************************************************************************
197 # Module testing. Incomplete, I'm lazy...
198
199 def _test_all():
200
201 """Module testing functions, activated when the module is called as a
202 script (not imported)."""
203
204 # Put tests for this module in here.
205 # Define them as nested functions so they don't clobber the
206 # pydoc-generated docs
207
208 def _test_():
209 name = ''
210 print '#'*50+'\nRunning test for ' + name
211 # ...
212 print 'Finished test for '+ name +'\n'+'#'*50
213
214 def _test_OutputTrap():
215 trap = OutputTrap(name = 'Test Trap', sum_sep = '.'*50+'\n',
216 out_head = 'SOut. ', err_head = 'SErr. ')
217
218 name = 'OutputTrap class'
219 print '#'*50+'\nRunning test for ' + name
220 print 'Trapping out'
221 trap.trap_out()
222 print >>sys.stdout, '>>stdout. stdout is trapped.'
223 print >>sys.stderr, '>>stderr. stdout is trapped.'
224 trap.release_out()
225 print trap.summary_out()
226
227 print 'Trapping err'
228 trap.trap_err()
229 print >>sys.stdout, '>>stdout. stderr is trapped.'
230 print >>sys.stderr, '>>stderr. stderr is trapped.'
231 trap.release_err()
232 print trap.summary_err()
233
234 print 'Trapping all (no flushing)'
235 trap.trap_all()
236 print >>sys.stdout, '>>stdout. stdout/err is trapped.'
237 print >>sys.stderr, '>>stderr. stdout/err is trapped.'
238 trap.release_all()
239 print trap.summary_all()
240
241 print 'Trapping all (flushing first)'
242 trap.flush()
243 trap.trap_all()
244 print >>sys.stdout, '>>stdout. stdout/err is trapped.'
245 print >>sys.stderr, '>>stderr. stdout/err is trapped.'
246 trap.release_all()
247 print trap.summary_all()
248 print 'Finished test for '+ name +'\n'+'#'*50
249
250 # call the actual tests here:
251 _test_OutputTrap()
252
253
254 if __name__=="__main__":
255 # _test_all() # XXX BROKEN.
256 pass
257
258 #************************ end of file <OutputTrap.py> ************************
@@ -1,47 +0,0 b''
1 # coding: utf-8
2 """
3 A simple class for quitting IPython.
4
5 Authors
6 -------
7 * Fernando Perez
8 * Brian Granger
9 """
10
11 #-----------------------------------------------------------------------------
12 # Copyright (C) 2008-2011 The IPython Development Team
13 #
14 # Distributed under the terms of the BSD License. The full license is in
15 # the file COPYING, distributed as part of this software.
16 #-----------------------------------------------------------------------------
17
18 #-----------------------------------------------------------------------------
19 # Imports
20 #-----------------------------------------------------------------------------
21
22
23 #-----------------------------------------------------------------------------
24 # Code
25 #-----------------------------------------------------------------------------
26
27
28 class Quitter(object):
29 """Simple class to handle exit, similar to Python 2.5's.
30
31 It handles exiting in an ipython-safe manner, which the one in Python 2.5
32 doesn't do (obviously, since it doesn't know about ipython)."""
33
34 def __init__(self, shell, name):
35 self.shell = shell
36 self.name = name
37
38 def __str__(self):
39 return 'Type %s() to exit.' % self.name
40
41 def __call__(self):
42 self.shell.ask_exit()
43
44 # Repr MUST return a string, else display like pprint hooks get confused
45 def __repr__(self):
46 self.shell.ask_exit()
47 return ''
@@ -1,26 +0,0 b''
1 import win32api
2 import win32gui
3 import win32con
4
5 import struct
6 import array
7
8 def findWindows():
9 ret = []
10 sdi = win32api.RegisterWindowMessage("SciTEDirectorInterface")
11 w = win32gui.GetWindow(win32gui.GetDesktopWindow(), win32con.GW_CHILD)
12 while w:
13 res = win32gui.SendMessage(w, sdi, 0, 0)
14 if res == sdi:
15 ret.append(w)
16 w = win32gui.GetWindow(w, win32con.GW_HWNDNEXT)
17
18 return ret
19
20 def sendCommand(w, message):
21 CopyDataStruct = "IIP"
22 char_buffer = array.array('c', message)
23 char_buffer_address = char_buffer.buffer_info()[0]
24 char_buffer_size = char_buffer.buffer_info()[1]
25 cds = struct.pack(CopyDataStruct, 0, char_buffer_size, char_buffer_address)
26 win32gui.SendMessage(w, win32con.WM_COPYDATA, 0, cds)
@@ -1,440 +0,0 b''
1 """
2 Test which prefilter transformations get called for various input lines.
3 Note that this does *not* test the transformations themselves -- it's just
4 verifying that a particular combination of, e.g. config options and escape
5 chars trigger the proper handle_X transform of the input line.
6
7 Usage: run from the command line with *normal* python, not ipython:
8 > python test_prefilter.py
9
10 Fairly quiet output by default. Pass in -v to get everyone's favorite dots.
11 """
12
13 # The prefilter always ends in a call to some self.handle_X method. We swap
14 # all of those out so that we can capture which one was called.
15
16 import sys
17 sys.path.append('..')
18 import IPython
19 import IPython.ipapi
20
21 verbose = False
22 if len(sys.argv) > 1:
23 if sys.argv[1] == '-v':
24 sys.argv = sys.argv[:-1] # IPython is confused by -v, apparently
25 verbose = True
26
27 IPython.Shell.start()
28
29 ip = IPython.ipapi.get()
30
31 # Collect failed tests + stats and print them at the end
32 failures = []
33 num_tests = 0
34
35 # Store the results in module vars as we go
36 last_line = None
37 handler_called = None
38 def install_mock_handler(name):
39 """Swap out one of the IP.handle_x methods with a function which can
40 record which handler was called and what line was produced. The mock
41 handler func always returns '', which causes ipython to cease handling
42 the string immediately. That way, that it doesn't echo output, raise
43 exceptions, etc. But do note that testing multiline strings thus gets
44 a bit hard."""
45 def mock_handler(self, line, continue_prompt=None,
46 pre=None,iFun=None,theRest=None,
47 obj=None):
48 #print "Inside %s with '%s'" % (name, line)
49 global last_line, handler_called
50 last_line = line
51 handler_called = name
52 return ''
53 mock_handler.name = name
54 setattr(IPython.iplib.InteractiveShell, name, mock_handler)
55
56 install_mock_handler('handle_normal')
57 install_mock_handler('handle_auto')
58 install_mock_handler('handle_magic')
59 install_mock_handler('handle_help')
60 install_mock_handler('handle_shell_escape')
61 install_mock_handler('handle_alias')
62 install_mock_handler('handle_emacs')
63
64
65 def reset_esc_handlers():
66 """The escape handlers are stored in a hash (as an attribute of the
67 InteractiveShell *instance*), so we have to rebuild that hash to get our
68 new handlers in there."""
69 s = ip.IP
70 s.esc_handlers = {s.ESC_PAREN : s.handle_auto,
71 s.ESC_QUOTE : s.handle_auto,
72 s.ESC_QUOTE2 : s.handle_auto,
73 s.ESC_MAGIC : s.handle_magic,
74 s.ESC_HELP : s.handle_help,
75 s.ESC_SHELL : s.handle_shell_escape,
76 s.ESC_SH_CAP : s.handle_shell_escape,
77 }
78 reset_esc_handlers()
79
80 # This is so I don't have to quote over and over. Gotta be a better way.
81 handle_normal = 'handle_normal'
82 handle_auto = 'handle_auto'
83 handle_magic = 'handle_magic'
84 handle_help = 'handle_help'
85 handle_shell_escape = 'handle_shell_escape'
86 handle_alias = 'handle_alias'
87 handle_emacs = 'handle_emacs'
88
89 def check(assertion, failure_msg):
90 """Check a boolean assertion and fail with a message if necessary. Store
91 an error essage in module-level failures list in case of failure. Print
92 '.' or 'F' if module var Verbose is true.
93 """
94 global num_tests
95 num_tests += 1
96 if assertion:
97 if verbose:
98 sys.stdout.write('.')
99 sys.stdout.flush()
100 else:
101 if verbose:
102 sys.stdout.write('F')
103 sys.stdout.flush()
104 failures.append(failure_msg)
105
106
107 def check_handler(expected_handler, line):
108 """Verify that the expected hander was called (for the given line,
109 passed in for failure reporting).
110
111 Pulled out to its own function so that tests which don't use
112 run_handler_tests can still take advantage of it."""
113 check(handler_called == expected_handler,
114 "Expected %s to be called for %s, "
115 "instead %s called" % (expected_handler,
116 repr(line),
117 handler_called))
118
119
120 def run_handler_tests(h_tests):
121 """Loop through a series of (input_line, handler_name) pairs, verifying
122 that, for each ip calls the given handler for the given line.
123
124 The verbose complaint includes the line passed in, so if that line can
125 include enough info to find the error, the tests are modestly
126 self-documenting.
127 """
128 for ln, expected_handler in h_tests:
129 global handler_called
130 handler_called = None
131 ip.runlines(ln)
132 check_handler(expected_handler, ln)
133
134 def run_one_test(ln, expected_handler):
135 run_handler_tests([(ln, expected_handler)])
136
137
138 # =========================================
139 # Tests
140 # =========================================
141
142
143 # Fundamental escape characters + whitespace & misc
144 # =================================================
145 esc_handler_tests = [
146 ( '?thing', handle_help, ),
147 ( 'thing?', handle_help ), # '?' can trail...
148 ( 'thing!', handle_normal), # but only '?' can trail
149 ( ' ?thing', handle_normal), # leading whitespace turns off esc chars
150 ( '!ls', handle_shell_escape),
151 ( '! true', handle_shell_escape),
152 ( '!! true', handle_shell_escape),
153 ( '%magic', handle_magic),
154 # XXX Possibly, add test for /,; once those are unhooked from %autocall
155 ( 'emacs_mode # PYTHON-MODE', handle_emacs ),
156 ( ' ', handle_normal),
157
158 # Trailing qmark combos. Odd special cases abound
159
160 # ! always takes priority!
161 ( '!thing?', handle_shell_escape),
162 ( '!thing arg?', handle_shell_escape),
163 ( '!!thing?', handle_shell_escape),
164 ( '!!thing arg?', handle_shell_escape),
165 ( ' !!thing arg?', handle_shell_escape),
166
167 # For all other leading esc chars, we always trigger help
168 ( '%cmd?', handle_help),
169 ( '%cmd ?', handle_help),
170 ( '/cmd?', handle_help),
171 ( '/cmd ?', handle_help),
172 ( ';cmd?', handle_help),
173 ( ',cmd?', handle_help),
174 ]
175 run_handler_tests(esc_handler_tests)
176
177
178
179 # Shell Escapes in Multi-line statements
180 # ======================================
181 #
182 # We can't test this via runlines, since the hacked-over-for-testing
183 # handlers all return None, so continue_prompt never becomes true. Instead
184 # we drop into prefilter directly and pass in continue_prompt.
185
186 old_mls = ip.options.multi_line_specials
187 for ln in [ ' !ls $f multi_line_specials %s',
188 ' !!ls $f multi_line_specials %s', # !! escapes work on mls
189 # Trailing ? doesn't trigger help:
190 ' !ls $f multi_line_specials %s ?',
191 ' !!ls $f multi_line_specials %s ?',
192 ]:
193 ip.options.multi_line_specials = 1
194 on_ln = ln % 'on'
195 ignore = ip.IP.prefilter(on_ln, continue_prompt=True)
196 check_handler(handle_shell_escape, on_ln)
197
198 ip.options.multi_line_specials = 0
199 off_ln = ln % 'off'
200 ignore = ip.IP.prefilter(off_ln, continue_prompt=True)
201 check_handler(handle_normal, off_ln)
202
203 ip.options.multi_line_specials = old_mls
204
205
206 # Automagic
207 # =========
208
209 # Pick one magic fun and one non_magic fun, make sure both exist
210 assert hasattr(ip.IP, "magic_cpaste")
211 assert not hasattr(ip.IP, "magic_does_not_exist")
212 ip.options.autocall = 0 # gotta have this off to get handle_normal
213 ip.options.automagic = 0
214 run_handler_tests([
215 # Without automagic, only shows up with explicit escape
216 ( 'cpaste', handle_normal),
217 ( '%cpaste', handle_magic),
218 ( '%does_not_exist', handle_magic),
219 ])
220 ip.options.automagic = 1
221 run_handler_tests([
222 ( 'cpaste', handle_magic),
223 ( '%cpaste', handle_magic),
224 ( 'does_not_exist', handle_normal),
225 ( '%does_not_exist', handle_magic),
226 ( 'cd /', handle_magic),
227 ( 'cd = 2', handle_normal),
228 ( 'r', handle_magic),
229 ( 'r thing', handle_magic),
230 ( 'r"str"', handle_normal),
231 ])
232
233 # If next elt starts with anything that could be an assignment, func call,
234 # etc, we don't call the magic func, unless explicitly escaped to do so.
235 #magic_killing_tests = []
236 #for c in list('!=()<>,'):
237 # magic_killing_tests.append(('cpaste %s killed_automagic' % c, handle_normal))
238 # magic_killing_tests.append(('%%cpaste %s escaped_magic' % c, handle_magic))
239 #run_handler_tests(magic_killing_tests)
240
241 # magic on indented continuation lines -- on iff multi_line_specials == 1
242 ip.options.multi_line_specials = 0
243 ln = ' cpaste multi_line off kills magic'
244 ignore = ip.IP.prefilter(ln, continue_prompt=True)
245 check_handler(handle_normal, ln)
246
247 ip.options.multi_line_specials = 1
248 ln = ' cpaste multi_line on enables magic'
249 ignore = ip.IP.prefilter(ln, continue_prompt=True)
250 check_handler(handle_magic, ln)
251
252 # user namespace shadows the magic one unless shell escaped
253 ip.user_ns['cpaste'] = 'user_ns'
254 run_handler_tests([
255 ( 'cpaste', handle_normal),
256 ( '%cpaste', handle_magic)])
257 del ip.user_ns['cpaste']
258
259
260
261 # Check for !=() turning off .ofind
262 # =================================
263 class AttributeMutator(object):
264 """A class which will be modified on attribute access, to test ofind"""
265 def __init__(self):
266 self.called = False
267
268 def getFoo(self): self.called = True
269 foo = property(getFoo)
270
271 attr_mutator = AttributeMutator()
272 ip.to_user_ns('attr_mutator')
273
274 ip.options.autocall = 1
275
276 run_one_test('attr_mutator.foo should mutate', handle_normal)
277 check(attr_mutator.called, 'ofind should be called in absence of assign characters')
278
279 for c in list('!=()<>+*/%^&|'):
280 attr_mutator.called = False
281 run_one_test('attr_mutator.foo %s should *not* mutate' % c, handle_normal)
282 run_one_test('attr_mutator.foo%s should *not* mutate' % c, handle_normal)
283
284 check(not attr_mutator.called,
285 'ofind should not be called near character %s' % c)
286
287
288
289 # Alias expansion
290 # ===============
291
292 # With autocall on or off, aliases should be shadowed by user, internal and
293 # __builtin__ namespaces
294 #
295 # XXX Can aliases have '.' in their name? With autocall off, that works,
296 # with autocall on, it doesn't. Hmmm.
297 import __builtin__
298 for ac_state in [0,1]:
299 ip.options.autocall = ac_state
300 ip.IP.alias_table['alias_cmd'] = 'alias_result'
301 ip.IP.alias_table['alias_head.with_dot'] = 'alias_result'
302 run_handler_tests([
303 ("alias_cmd", handle_alias),
304 # XXX See note above
305 #("alias_head.with_dot unshadowed, autocall=%s" % ac_state, handle_alias),
306 ("alias_cmd.something aliases must match whole expr", handle_normal),
307 ("alias_cmd /", handle_alias),
308 ])
309
310 for ns in [ip.user_ns, ip.IP.internal_ns, __builtin__.__dict__ ]:
311 ns['alias_cmd'] = 'a user value'
312 ns['alias_head'] = 'a user value'
313 run_handler_tests([
314 ("alias_cmd", handle_normal),
315 ("alias_head.with_dot", handle_normal)])
316 del ns['alias_cmd']
317 del ns['alias_head']
318
319 ip.options.autocall = 1
320
321
322
323
324 # Autocall
325 # ========
326
327 # For all the tests below, 'len' is callable / 'thing' is not
328
329 # Objects which are instances of IPyAutocall are *always* autocalled
330 import IPython.ipapi
331 class Autocallable(IPython.ipapi.IPyAutocall):
332 def __call__(self):
333 return "called"
334
335 autocallable = Autocallable()
336 ip.to_user_ns('autocallable')
337
338
339 # First, with autocalling fully off
340 ip.options.autocall = 0
341 run_handler_tests( [
342 # With no escapes, no autocalling expansions happen, callable or not,
343 # unless the obj extends IPyAutocall
344 ( 'len autocall_0', handle_normal),
345 ( 'thing autocall_0', handle_normal),
346 ( 'autocallable', handle_auto),
347
348 # With explicit escapes, callable and non-callables both get expanded,
349 # regardless of the %autocall setting:
350 ( '/len autocall_0', handle_auto),
351 ( ',len autocall_0 b0', handle_auto),
352 ( ';len autocall_0 b0', handle_auto),
353
354 ( '/thing autocall_0', handle_auto),
355 ( ',thing autocall_0 b0', handle_auto),
356 ( ';thing autocall_0 b0', handle_auto),
357
358 # Explicit autocall should not trigger if there is leading whitespace
359 ( ' /len autocall_0', handle_normal),
360 ( ' ;len autocall_0', handle_normal),
361 ( ' ,len autocall_0', handle_normal),
362 ( ' / len autocall_0', handle_normal),
363
364 # But should work if the whitespace comes after the esc char
365 ( '/ len autocall_0', handle_auto),
366 ( '; len autocall_0', handle_auto),
367 ( ', len autocall_0', handle_auto),
368 ( '/ len autocall_0', handle_auto),
369 ])
370
371
372 # Now, with autocall in default, 'smart' mode
373 ip.options.autocall = 1
374 run_handler_tests( [
375 # Autocalls without escapes -- only expand if it's callable
376 ( 'len a1', handle_auto),
377 ( 'thing a1', handle_normal),
378 ( 'autocallable', handle_auto),
379
380 # As above, all explicit escapes generate auto-calls, callable or not
381 ( '/len a1', handle_auto),
382 ( ',len a1 b1', handle_auto),
383 ( ';len a1 b1', handle_auto),
384 ( '/thing a1', handle_auto),
385 ( ',thing a1 b1', handle_auto),
386 ( ';thing a1 b1', handle_auto),
387
388 # Autocalls only happen on things which look like funcs, even if
389 # explicitly requested. Which, in this case means they look like a
390 # sequence of identifiers and . attribute references. Possibly the
391 # second of these two should trigger handle_auto. But not for now.
392 ( '"abc".join range(4)', handle_normal),
393 ( '/"abc".join range(4)', handle_normal),
394 ])
395
396
397 # No tests for autocall = 2, since the extra magic there happens inside the
398 # handle_auto function, which our test doesn't examine.
399
400 # Note that we leave autocall in default, 1, 'smart' mode
401
402
403 # Autocall / Binary operators
404 # ==========================
405
406 # Even with autocall on, 'len in thing' won't transform.
407 # But ';len in thing' will
408
409 # Note, the tests below don't check for multi-char ops. It could.
410
411 # XXX % is a binary op and should be in the list, too, but fails
412 bin_ops = list(r'<>,&^|*/+-') + 'is not in and or'.split()
413 bin_tests = []
414 for b in bin_ops:
415 bin_tests.append(('len %s binop_autocall' % b, handle_normal))
416 bin_tests.append((';len %s binop_autocall' % b, handle_auto))
417 bin_tests.append((',len %s binop_autocall' % b, handle_auto))
418 bin_tests.append(('/len %s binop_autocall' % b, handle_auto))
419
420 # Who loves auto-generating tests?
421 run_handler_tests(bin_tests)
422
423
424 # Possibly add tests for namespace shadowing (really ofind's business?).
425 #
426 # user > ipython internal > python builtin > alias > magic
427
428
429 # ============
430 # Test Summary
431 # ============
432 num_f = len(failures)
433 if verbose:
434 print
435 print "%s tests run, %s failure%s" % (num_tests,
436 num_f,
437 num_f != 1 and "s" or "")
438 for f in failures:
439 print f
440
@@ -1,287 +0,0 b''
1 """Twisted shell support.
2
3 XXX - This module is missing proper docs.
4 """
5 # Tell nose to skip this module
6 __test__ = {}
7
8 import sys
9
10 from twisted.internet import reactor, threads
11
12 from IPython.core.ipmaker import make_IPython
13 from IPython.core.iplib import InteractiveShell
14 from IPython.utils.ipstruct import Struct
15 import Queue,thread,threading,signal
16 from signal import signal, SIGINT
17 import IPython.utils.io, ask_yes_no
18 from IPython.utils.warn import warn, error
19 from IPython.utils.decorators import flag_calls
20 from IPython.core import shellglobals
21
22 def install_gtk2():
23 """ Install gtk2 reactor, needs to be called bef """
24 from twisted.internet import gtk2reactor
25 gtk2reactor.install()
26
27
28 def hijack_reactor():
29 """Modifies Twisted's reactor with a dummy so user code does
30 not block IPython. This function returns the original
31 'twisted.internet.reactor' that has been hijacked.
32
33 NOTE: Make sure you call this *AFTER* you've installed
34 the reactor of your choice.
35 """
36 from twisted import internet
37 orig_reactor = internet.reactor
38
39 class DummyReactor(object):
40 def run(self):
41 pass
42 def __getattr__(self, name):
43 return getattr(orig_reactor, name)
44 def __setattr__(self, name, value):
45 return setattr(orig_reactor, name, value)
46
47 internet.reactor = DummyReactor()
48 return orig_reactor
49
50 class TwistedInteractiveShell(InteractiveShell):
51 """Simple multi-threaded shell."""
52
53 # Threading strategy taken from:
54 # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/65109, by Brian
55 # McErlean and John Finlay. Modified with corrections by Antoon Pardon,
56 # from the pygtk mailing list, to avoid lockups with system calls.
57
58 # class attribute to indicate whether the class supports threads or not.
59 # Subclasses with thread support should override this as needed.
60 isthreaded = True
61
62 def __init__(self,name,usage=None,rc=Struct(opts=None,args=None),
63 user_ns=None,user_global_ns=None,banner2='',**kw):
64 """Similar to the normal InteractiveShell, but with threading control"""
65
66 InteractiveShell.__init__(self,name,usage,rc,user_ns,
67 user_global_ns,banner2)
68
69
70 # A queue to hold the code to be executed.
71 self.code_queue = Queue.Queue()
72
73 # Stuff to do at closing time
74 self._kill = None
75 on_kill = kw.get('on_kill', [])
76 # Check that all things to kill are callable:
77 for t in on_kill:
78 if not callable(t):
79 raise TypeError,'on_kill must be a list of callables'
80 self.on_kill = on_kill
81 # thread identity of the "worker thread" (that may execute code directly)
82 self.worker_ident = None
83 self.reactor_started = False
84 self.first_run = True
85
86 def runsource(self, source, filename="<input>", symbol="single"):
87 """Compile and run some source in the interpreter.
88
89 Modified version of code.py's runsource(), to handle threading issues.
90 See the original for full docstring details."""
91
92 # If Ctrl-C was typed, we reset the flag and return right away
93 if shellglobals.KBINT:
94 shellglobals.KBINT = False
95 return False
96
97 if self._kill:
98 # can't queue new code if we are being killed
99 return True
100
101 try:
102 code = self.compile(source, filename, symbol)
103 except (OverflowError, SyntaxError, ValueError):
104 # Case 1
105 self.showsyntaxerror(filename)
106 return False
107
108 if code is None:
109 # Case 2
110 return True
111
112 # shortcut - if we are in worker thread, or the worker thread is not running,
113 # execute directly (to allow recursion and prevent deadlock if code is run early
114 # in IPython construction)
115
116 if (not self.reactor_started or (self.worker_ident is None and not self.first_run)
117 or self.worker_ident == thread.get_ident() or shellglobals.run_in_frontend(source)):
118 InteractiveShell.runcode(self,code)
119 return
120
121 # Case 3
122 # Store code in queue, so the execution thread can handle it.
123
124 self.first_run = False
125 completed_ev, received_ev = threading.Event(), threading.Event()
126
127 self.code_queue.put((code,completed_ev, received_ev))
128
129 reactor.callLater(0.0,self.runcode)
130 received_ev.wait(5)
131 if not received_ev.isSet():
132 # the mainloop is dead, start executing code directly
133 print "Warning: Timeout for mainloop thread exceeded"
134 print "switching to nonthreaded mode (until mainloop wakes up again)"
135 self.worker_ident = None
136 else:
137 completed_ev.wait()
138
139 return False
140
141 def runcode(self):
142 """Execute a code object.
143
144 Multithreaded wrapper around IPython's runcode()."""
145
146
147 # we are in worker thread, stash out the id for runsource()
148 self.worker_ident = thread.get_ident()
149
150 if self._kill:
151 print >>Term.cout, 'Closing threads...',
152 Term.cout.flush()
153 for tokill in self.on_kill:
154 tokill()
155 print >>Term.cout, 'Done.'
156 # allow kill() to return
157 self._kill.set()
158 return True
159
160 # Install SIGINT handler. We do it every time to ensure that if user
161 # code modifies it, we restore our own handling.
162 try:
163 pass
164 signal(SIGINT,shellglobals.sigint_handler)
165 except SystemError:
166 # This happens under Windows, which seems to have all sorts
167 # of problems with signal handling. Oh well...
168 pass
169
170 # Flush queue of pending code by calling the run methood of the parent
171 # class with all items which may be in the queue.
172 code_to_run = None
173 while 1:
174 try:
175 code_to_run, completed_ev, received_ev = self.code_queue.get_nowait()
176 except Queue.Empty:
177 break
178 received_ev.set()
179
180
181 # Exceptions need to be raised differently depending on which
182 # thread is active. This convoluted try/except is only there to
183 # protect against asynchronous exceptions, to ensure that a shellglobals.KBINT
184 # at the wrong time doesn't deadlock everything. The global
185 # CODE_TO_RUN is set to true/false as close as possible to the
186 # runcode() call, so that the KBINT handler is correctly informed.
187 try:
188 try:
189 shellglobals.CODE_RUN = True
190 InteractiveShell.runcode(self,code_to_run)
191 except KeyboardInterrupt:
192 print "Keyboard interrupted in mainloop"
193 while not self.code_queue.empty():
194 code = self.code_queue.get_nowait()
195 break
196 finally:
197 shellglobals.CODE_RUN = False
198 # allow runsource() return from wait
199 completed_ev.set()
200
201 # This MUST return true for gtk threading to work
202 return True
203
204 def kill(self):
205 """Kill the thread, returning when it has been shut down."""
206 self._kill = threading.Event()
207 reactor.callLater(0.0,self.runcode)
208 self._kill.wait()
209
210
211
212 class IPShellTwisted:
213 """Run a Twisted reactor while in an IPython session.
214
215 Python commands can be passed to the thread where they will be
216 executed. This is implemented by periodically checking for
217 passed code using a Twisted reactor callback.
218 """
219
220 TIMEOUT = 0.01 # Millisecond interval between reactor runs.
221
222 def __init__(self, argv=None, user_ns=None, debug=1,
223 shell_class=TwistedInteractiveShell):
224
225 from twisted.internet import reactor
226 self.reactor = hijack_reactor()
227
228 mainquit = self.reactor.stop
229
230 # Make sure IPython keeps going after reactor stop.
231 def reactorstop():
232 pass
233 self.reactor.stop = reactorstop
234 reactorrun_orig = self.reactor.run
235 self.quitting = False
236 def reactorrun():
237 while True and not self.quitting:
238 reactorrun_orig()
239 self.reactor.run = reactorrun
240
241 self.IP = make_IPython(argv, user_ns=user_ns, debug=debug,
242 shell_class=shell_class,
243 on_kill=[mainquit])
244
245 # threading.Thread.__init__(self)
246
247 def run(self):
248 self.IP.mainloop()
249 self.quitting = True
250 self.IP.kill()
251
252 def mainloop(self):
253 def mainLoopThreadDeath(r):
254 print "mainLoopThreadDeath: ", str(r)
255 def spawnMainloopThread():
256 d=threads.deferToThread(self.run)
257 d.addBoth(mainLoopThreadDeath)
258 reactor.callWhenRunning(spawnMainloopThread)
259 self.IP.reactor_started = True
260 self.reactor.run()
261 print "mainloop ending...."
262
263 exists = True
264
265
266 if __name__ == '__main__':
267 # Sample usage.
268
269 # Create the shell object. This steals twisted.internet.reactor
270 # for its own purposes, to make sure you've already installed a
271 # reactor of your choice.
272 shell = IPShellTwisted(
273 argv=[],
274 user_ns={'__name__': '__example__',
275 'hello': 'world',
276 },
277 )
278
279 # Run the mainloop. This runs the actual reactor.run() method.
280 # The twisted.internet.reactor object at this point is a dummy
281 # object that passes through to the actual reactor, but prevents
282 # run() from being called on it again.
283 shell.mainloop()
284
285 # You must exit IPython to terminate your program.
286 print 'Goodbye!'
287
1 NO CONTENT: file was removed
@@ -1,1 +0,0 b''
1 Subproject commit 2a98f498092682f11affe9b0b86bd7e642cf7b13
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now