diff --git a/.hgignore b/.hgignore deleted file mode 100644 index ecc75a2..0000000 --- a/.hgignore +++ /dev/null @@ -1,18 +0,0 @@ -syntax: glob - -*~ -*.tmp -*.pyc -*.bak -*.tgz -*.org -*.rej -.svn/ -.bzr/ -.settings/ -.project -*.diff -IPython_crash_report.txt - -syntax: regexp -.*\#.*\#$ diff --git a/IPython/ConfigLoader.py b/IPython/ConfigLoader.py deleted file mode 100644 index d64864d..0000000 --- a/IPython/ConfigLoader.py +++ /dev/null @@ -1,111 +0,0 @@ -# -*- coding: utf-8 -*- -"""Configuration loader -""" - -#***************************************************************************** -# Copyright (C) 2001-2006 Fernando Perez. -# -# Distributed under the terms of the BSD License. The full license is in -# the file COPYING, distributed as part of this software. -#***************************************************************************** - -import exceptions -import os -from pprint import pprint - -from IPython import ultraTB -from IPython.ipstruct import Struct -from IPython.genutils import * - -class ConfigLoaderError(exceptions.Exception): - """Exception for ConfigLoader class.""" - - def __init__(self,args=None): - self.args = args - -class ConfigLoader: - - """Configuration file loader capable of handling recursive inclusions and - with parametrized conflict resolution for multiply found keys.""" - - def __init__(self,conflict=None,field_sep=None,reclimit=15): - - """The reclimit parameter controls the number of recursive - configuration file inclusions. This way we can stop early on (before - python's own recursion limit is hit) if there is a circular - inclusion. - - - conflict: dictionary for conflict resolutions (see Struct.merge()) - - """ - self.conflict = conflict - self.field_sep = field_sep - self.reset(reclimit) - - def reset(self,reclimit=15): - self.reclimit = reclimit - self.recdepth = 0 - self.included = [] - - def load(self,fname,convert=None,recurse_key='',incpath = '.',**kw): - """Load a configuration file, return the resulting Struct. - - Call: load_config(fname,convert=None,conflict=None,recurse_key='') - - - fname: file to load from. - - convert: dictionary of type conversions (see read_dict()) - - recurse_key: keyword in dictionary to trigger recursive file - inclusions. - """ - - if self.recdepth > self.reclimit: - raise ConfigLoaderError, 'maximum recursive inclusion of rcfiles '+\ - 'exceeded: ' + `self.recdepth` + \ - '.\nMaybe you have a circular chain of inclusions?' - self.recdepth += 1 - fname = filefind(fname,incpath) - data = Struct() - # avoid including the same file more than once - if fname in self.included: - return data - Xinfo = ultraTB.AutoFormattedTB(color_scheme='NoColor') - if convert==None and recurse_key : convert = {qwflat:recurse_key} - # for production, change warn to 0: - data.merge(read_dict(fname,convert,fs=self.field_sep,strip=1, - warn=0,no_empty=0,**kw)) - # keep track of successfully loaded files - self.included.append(fname) - if recurse_key in data: - for incfilename in data[recurse_key]: - found=0 - try: - incfile = filefind(incfilename,incpath) - except IOError: - if os.name in ['nt','dos']: - try: - # Try again with '.ini' extension - incfilename += '.ini' - incfile = filefind(incfilename,incpath) - except IOError: - found = 0 - else: - found = 1 - else: - found = 0 - else: - found = 1 - if found: - try: - data.merge(self.load(incfile,convert,recurse_key, - incpath,**kw), - self.conflict) - except: - Xinfo() - warn('Problem loading included file: '+ - `incfilename` + '. Ignoring it...') - else: - warn('File `%s` not found. Included by %s' % (incfilename,fname)) - - return data - -# end ConfigLoader diff --git a/IPython/Extensions/ipy_pretty.py b/IPython/Extensions/ipy_pretty.py deleted file mode 100644 index 924b7fa..0000000 --- a/IPython/Extensions/ipy_pretty.py +++ /dev/null @@ -1,132 +0,0 @@ -""" Use pretty.py for configurable pretty-printing. - -Register pretty-printers for types using ipy_pretty.for_type() or -ipy_pretty.for_type_by_name(). For example, to use the example pretty-printer -for numpy dtype objects, add the following to your ipy_user_conf.py:: - - from IPython.Extensions import ipy_pretty - - ipy_pretty.activate() - - # If you want to have numpy always imported anyways: - import numpy - ipy_pretty.for_type(numpy.dtype, ipy_pretty.dtype_pprinter) - - # If you don't want to have numpy imported until it needs to be: - ipy_pretty.for_type_by_name('numpy', 'dtype', ipy_pretty.dtype_pprinter) -""" - -import IPython.ipapi -from IPython.genutils import Term - -from IPython.external import pretty - -ip = IPython.ipapi.get() - - -#### Implementation ############################################################ - -def pretty_result_display(self, arg): - """ Uber-pretty-printing display hook. - - Called for displaying the result to the user. - """ - - if ip.options.pprint: - verbose = getattr(ip.options, 'pretty_verbose', False) - out = pretty.pretty(arg, verbose=verbose) - if '\n' in out: - # So that multi-line strings line up with the left column of - # the screen, instead of having the output prompt mess up - # their first line. - Term.cout.write('\n') - print >>Term.cout, out - else: - raise TryNext - - -#### API ####################################################################### - -# Expose the for_type and for_type_by_name functions for easier use. -for_type = pretty.for_type -for_type_by_name = pretty.for_type_by_name - - -# FIXME: write deactivate(). We need a way to remove a hook. -def activate(): - """ Activate this extension. - """ - ip.set_hook('result_display', pretty_result_display, priority=99) - - -#### Example pretty-printers ################################################### - -def dtype_pprinter(obj, p, cycle): - """ A pretty-printer for numpy dtype objects. - """ - if cycle: - return p.text('dtype(...)') - if obj.fields is None: - p.text(repr(obj)) - else: - p.begin_group(7, 'dtype([') - for i, field in enumerate(obj.descr): - if i > 0: - p.text(',') - p.breakable() - p.pretty(field) - p.end_group(7, '])') - - -#### Tests ##################################################################### - -def test_pretty(): - """ - In [1]: from IPython.Extensions import ipy_pretty - - In [2]: ipy_pretty.activate() - - In [3]: class A(object): - ...: def __repr__(self): - ...: return 'A()' - ...: - ...: - - In [4]: a = A() - - In [5]: a - Out[5]: A() - - In [6]: def a_pretty_printer(obj, p, cycle): - ...: p.text('') - ...: - ...: - - In [7]: ipy_pretty.for_type(A, a_pretty_printer) - - In [8]: a - Out[8]: - - In [9]: class B(object): - ...: def __repr__(self): - ...: return 'B()' - ...: - ...: - - In [10]: B.__module__, B.__name__ - Out[10]: ('__main__', 'B') - - In [11]: def b_pretty_printer(obj, p, cycle): - ....: p.text('') - ....: - ....: - - In [12]: ipy_pretty.for_type_by_name('__main__', 'B', b_pretty_printer) - - In [13]: b = B() - - In [14]: b - Out[14]: - """ - assert False, "This should only be doctested, not run." - diff --git a/IPython/Shell.py b/IPython/Shell.py index 2dd9dac..7bdd833 100644 --- a/IPython/Shell.py +++ b/IPython/Shell.py @@ -1,1246 +1,42 @@ -# -*- coding: utf-8 -*- -"""IPython Shell classes. +#!/usr/bin/env python +# encoding: utf-8 +""" +A backwards compatibility layer for IPython.Shell. -All the matplotlib support code was co-developed with John Hunter, -matplotlib's author. +Previously, IPython had an IPython.Shell module. IPython.Shell has been moved +to IPython.core.shell and is being refactored. This new module is provided +for backwards compatability. We strongly encourage everyone to start using +the new code in IPython.core.shell. """ -#***************************************************************************** -# Copyright (C) 2001-2006 Fernando Perez +#----------------------------------------------------------------------------- +# Copyright (C) 2008-2009 The IPython Development Team # # Distributed under the terms of the BSD License. The full license is in # the file COPYING, distributed as part of this software. -#***************************************************************************** - -# Code begins -# Stdlib imports -import __builtin__ -import __main__ -import Queue -import inspect -import os -import sys -import thread -import threading -import time - -from signal import signal, SIGINT - -try: - import ctypes - HAS_CTYPES = True -except ImportError: - HAS_CTYPES = False - -# IPython imports -import IPython -from IPython import ultraTB, ipapi -from IPython.Magic import Magic -from IPython.genutils import Term,warn,error,flag_calls, ask_yes_no -from IPython.iplib import InteractiveShell -from IPython.ipmaker import make_IPython -from IPython.ipstruct import Struct -from IPython.testing import decorators as testdec - -# Globals -# global flag to pass around information about Ctrl-C without exceptions -KBINT = False - -# global flag to turn on/off Tk support. -USE_TK = False - -# ID for the main thread, used for cross-thread exceptions -MAIN_THREAD_ID = thread.get_ident() - -# Tag when runcode() is active, for exception handling -CODE_RUN = None - -# Default timeout for waiting for multithreaded shells (in seconds) -GUI_TIMEOUT = 10 - -#----------------------------------------------------------------------------- -# This class is trivial now, but I want to have it in to publish a clean -# interface. Later when the internals are reorganized, code that uses this -# shouldn't have to change. - -class IPShell: - """Create an IPython instance.""" - - def __init__(self,argv=None,user_ns=None,user_global_ns=None, - debug=1,shell_class=InteractiveShell): - self.IP = make_IPython(argv,user_ns=user_ns, - user_global_ns=user_global_ns, - debug=debug,shell_class=shell_class) - - def mainloop(self,sys_exit=0,banner=None): - self.IP.mainloop(banner) - if sys_exit: - sys.exit() - #----------------------------------------------------------------------------- -def kill_embedded(self,parameter_s=''): - """%kill_embedded : deactivate for good the current embedded IPython. - - This function (after asking for confirmation) sets an internal flag so that - an embedded IPython will never activate again. This is useful to - permanently disable a shell that is being called inside a loop: once you've - figured out what you needed from it, you may then kill it and the program - will then continue to run without the interactive shell interfering again. - """ - - kill = ask_yes_no("Are you sure you want to kill this embedded instance " - "(y/n)? [y/N] ",'n') - if kill: - self.shell.embedded_active = False - print "This embedded IPython will not reactivate anymore once you exit." - -class IPShellEmbed: - """Allow embedding an IPython shell into a running program. - - Instances of this class are callable, with the __call__ method being an - alias to the embed() method of an InteractiveShell instance. - - Usage (see also the example-embed.py file for a running example): - - ipshell = IPShellEmbed([argv,banner,exit_msg,rc_override]) - - - argv: list containing valid command-line options for IPython, as they - would appear in sys.argv[1:]. - - For example, the following command-line options: - - $ ipython -prompt_in1 'Input <\\#>' -colors LightBG - - would be passed in the argv list as: - - ['-prompt_in1','Input <\\#>','-colors','LightBG'] - - - banner: string which gets printed every time the interpreter starts. - - - exit_msg: string which gets printed every time the interpreter exits. - - - rc_override: a dict or Struct of configuration options such as those - used by IPython. These options are read from your ~/.ipython/ipythonrc - file when the Shell object is created. Passing an explicit rc_override - dict with any options you want allows you to override those values at - creation time without having to modify the file. This way you can create - embeddable instances configured in any way you want without editing any - global files (thus keeping your interactive IPython configuration - unchanged). - - Then the ipshell instance can be called anywhere inside your code: - - ipshell(header='') -> Opens up an IPython shell. - - - header: string printed by the IPython shell upon startup. This can let - you know where in your code you are when dropping into the shell. Note - that 'banner' gets prepended to all calls, so header is used for - location-specific information. - - For more details, see the __call__ method below. - - When the IPython shell is exited with Ctrl-D, normal program execution - resumes. - - This functionality was inspired by a posting on comp.lang.python by cmkl - on Dec. 06/01 concerning similar uses of pyrepl, and - by the IDL stop/continue commands.""" - - def __init__(self,argv=None,banner='',exit_msg=None,rc_override=None, - user_ns=None): - """Note that argv here is a string, NOT a list.""" - self.set_banner(banner) - self.set_exit_msg(exit_msg) - self.set_dummy_mode(0) - - # sys.displayhook is a global, we need to save the user's original - # Don't rely on __displayhook__, as the user may have changed that. - self.sys_displayhook_ori = sys.displayhook - - # save readline completer status - try: - #print 'Save completer',sys.ipcompleter # dbg - self.sys_ipcompleter_ori = sys.ipcompleter - except: - pass # not nested with IPython - - self.IP = make_IPython(argv,rc_override=rc_override, - embedded=True, - user_ns=user_ns) - - ip = ipapi.IPApi(self.IP) - ip.expose_magic("kill_embedded",kill_embedded) - - # copy our own displayhook also - self.sys_displayhook_embed = sys.displayhook - # and leave the system's display hook clean - sys.displayhook = self.sys_displayhook_ori - # don't use the ipython crash handler so that user exceptions aren't - # trapped - sys.excepthook = ultraTB.FormattedTB(color_scheme = self.IP.rc.colors, - mode = self.IP.rc.xmode, - call_pdb = self.IP.rc.pdb) - self.restore_system_completer() - - def restore_system_completer(self): - """Restores the readline completer which was in place. - - This allows embedded IPython within IPython not to disrupt the - parent's completion. - """ - - try: - self.IP.readline.set_completer(self.sys_ipcompleter_ori) - sys.ipcompleter = self.sys_ipcompleter_ori - except: - pass - - def __call__(self,header='',local_ns=None,global_ns=None,dummy=None): - """Activate the interactive interpreter. - - __call__(self,header='',local_ns=None,global_ns,dummy=None) -> Start - the interpreter shell with the given local and global namespaces, and - optionally print a header string at startup. - - The shell can be globally activated/deactivated using the - set/get_dummy_mode methods. This allows you to turn off a shell used - for debugging globally. - - However, *each* time you call the shell you can override the current - state of dummy_mode with the optional keyword parameter 'dummy'. For - example, if you set dummy mode on with IPShell.set_dummy_mode(1), you - can still have a specific call work by making it as IPShell(dummy=0). - - The optional keyword parameter dummy controls whether the call - actually does anything. """ - - # If the user has turned it off, go away - if not self.IP.embedded_active: - return - - # Normal exits from interactive mode set this flag, so the shell can't - # re-enter (it checks this variable at the start of interactive mode). - self.IP.exit_now = False - - # Allow the dummy parameter to override the global __dummy_mode - if dummy or (dummy != 0 and self.__dummy_mode): - return - - # Set global subsystems (display,completions) to our values - sys.displayhook = self.sys_displayhook_embed - if self.IP.has_readline: - self.IP.set_completer() - - if self.banner and header: - format = '%s\n%s\n' - else: - format = '%s%s\n' - banner = format % (self.banner,header) - - # Call the embedding code with a stack depth of 1 so it can skip over - # our call and get the original caller's namespaces. - self.IP.embed_mainloop(banner,local_ns,global_ns,stack_depth=1) - - if self.exit_msg: - print self.exit_msg - - # Restore global systems (display, completion) - sys.displayhook = self.sys_displayhook_ori - self.restore_system_completer() - - def set_dummy_mode(self,dummy): - """Sets the embeddable shell's dummy mode parameter. - - set_dummy_mode(dummy): dummy = 0 or 1. - - This parameter is persistent and makes calls to the embeddable shell - silently return without performing any action. This allows you to - globally activate or deactivate a shell you're using with a single call. - - If you need to manually""" - if dummy not in [0,1,False,True]: - raise ValueError,'dummy parameter must be boolean' - self.__dummy_mode = dummy +from warnings import warn - def get_dummy_mode(self): - """Return the current value of the dummy mode parameter. - """ - return self.__dummy_mode - - def set_banner(self,banner): - """Sets the global banner. +msg = """ +This module (IPython.Shell) is deprecated. The classes that were in this +module have been replaced by: - This banner gets prepended to every header printed when the shell - instance is called.""" +IPShell->IPython.core.iplib.InteractiveShell +IPShellEmbed->IPython.core.embed.InteractiveShellEmbed - self.banner = banner - - def set_exit_msg(self,exit_msg): - """Sets the global exit_msg. - - This exit message gets printed upon exiting every time the embedded - shell is called. It is None by default. """ - - self.exit_msg = exit_msg - -#----------------------------------------------------------------------------- -if HAS_CTYPES: - # Add async exception support. Trick taken from: - # http://sebulba.wikispaces.com/recipe+thread2 - def _async_raise(tid, exctype): - """raises the exception, performs cleanup if needed""" - if not inspect.isclass(exctype): - raise TypeError("Only types can be raised (not instances)") - # Explicit cast to c_long is necessary for 64-bit support: - # See https://bugs.launchpad.net/ipython/+bug/237073 - res = ctypes.pythonapi.PyThreadState_SetAsyncExc(ctypes.c_long(tid), - ctypes.py_object(exctype)) - if res == 0: - raise ValueError("invalid thread id") - elif res != 1: - # If it returns a number greater than one, you're in trouble, - # and you should call it again with exc=NULL to revert the effect - ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, 0) - raise SystemError("PyThreadState_SetAsyncExc failed") - - def sigint_handler(signum,stack_frame): - """Sigint handler for threaded apps. - - This is a horrible hack to pass information about SIGINT _without_ - using exceptions, since I haven't been able to properly manage - cross-thread exceptions in GTK/WX. In fact, I don't think it can be - done (or at least that's my understanding from a c.l.py thread where - this was discussed).""" - - global KBINT - - if CODE_RUN: - _async_raise(MAIN_THREAD_ID,KeyboardInterrupt) - else: - KBINT = True - print '\nKeyboardInterrupt - Press to continue.', - Term.cout.flush() - -else: - def sigint_handler(signum,stack_frame): - """Sigint handler for threaded apps. - - This is a horrible hack to pass information about SIGINT _without_ - using exceptions, since I haven't been able to properly manage - cross-thread exceptions in GTK/WX. In fact, I don't think it can be - done (or at least that's my understanding from a c.l.py thread where - this was discussed).""" - - global KBINT - - print '\nKeyboardInterrupt - Press to continue.', - Term.cout.flush() - # Set global flag so that runsource can know that Ctrl-C was hit - KBINT = True - - -class MTInteractiveShell(InteractiveShell): - """Simple multi-threaded shell.""" - - # Threading strategy taken from: - # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/65109, by Brian - # McErlean and John Finlay. Modified with corrections by Antoon Pardon, - # from the pygtk mailing list, to avoid lockups with system calls. - - # class attribute to indicate whether the class supports threads or not. - # Subclasses with thread support should override this as needed. - isthreaded = True - - def __init__(self,name,usage=None,rc=Struct(opts=None,args=None), - user_ns=None,user_global_ns=None,banner2='', - gui_timeout=GUI_TIMEOUT,**kw): - """Similar to the normal InteractiveShell, but with threading control""" - - InteractiveShell.__init__(self,name,usage,rc,user_ns, - user_global_ns,banner2) - - # Timeout we wait for GUI thread - self.gui_timeout = gui_timeout - - # A queue to hold the code to be executed. - self.code_queue = Queue.Queue() - - # Stuff to do at closing time - self._kill = None - on_kill = kw.get('on_kill', []) - # Check that all things to kill are callable: - for t in on_kill: - if not callable(t): - raise TypeError,'on_kill must be a list of callables' - self.on_kill = on_kill - # thread identity of the "worker thread" (that may execute code directly) - self.worker_ident = None - - def runsource(self, source, filename="", symbol="single"): - """Compile and run some source in the interpreter. - - Modified version of code.py's runsource(), to handle threading issues. - See the original for full docstring details.""" - - global KBINT - - # If Ctrl-C was typed, we reset the flag and return right away - if KBINT: - KBINT = False - return False - - if self._kill: - # can't queue new code if we are being killed - return True - - try: - code = self.compile(source, filename, symbol) - except (OverflowError, SyntaxError, ValueError): - # Case 1 - self.showsyntaxerror(filename) - return False - - if code is None: - # Case 2 - return True - - # shortcut - if we are in worker thread, or the worker thread is not - # running, execute directly (to allow recursion and prevent deadlock if - # code is run early in IPython construction) - - if (self.worker_ident is None - or self.worker_ident == thread.get_ident() ): - InteractiveShell.runcode(self,code) - return False - - # Case 3 - # Store code in queue, so the execution thread can handle it. - - completed_ev, received_ev = threading.Event(), threading.Event() - - self.code_queue.put((code,completed_ev, received_ev)) - # first make sure the message was received, with timeout - received_ev.wait(self.gui_timeout) - if not received_ev.isSet(): - # the mainloop is dead, start executing code directly - print "Warning: Timeout for mainloop thread exceeded" - print "switching to nonthreaded mode (until mainloop wakes up again)" - self.worker_ident = None - else: - completed_ev.wait() - return False - - def runcode(self): - """Execute a code object. - - Multithreaded wrapper around IPython's runcode().""" - - global CODE_RUN - - # we are in worker thread, stash out the id for runsource() - self.worker_ident = thread.get_ident() - - if self._kill: - print >>Term.cout, 'Closing threads...', - Term.cout.flush() - for tokill in self.on_kill: - tokill() - print >>Term.cout, 'Done.' - # allow kill() to return - self._kill.set() - return True - - # Install sigint handler. We do it every time to ensure that if user - # code modifies it, we restore our own handling. - try: - signal(SIGINT,sigint_handler) - except SystemError: - # This happens under Windows, which seems to have all sorts - # of problems with signal handling. Oh well... - pass - - # Flush queue of pending code by calling the run methood of the parent - # class with all items which may be in the queue. - code_to_run = None - while 1: - try: - code_to_run, completed_ev, received_ev = self.code_queue.get_nowait() - except Queue.Empty: - break - received_ev.set() - - # Exceptions need to be raised differently depending on which - # thread is active. This convoluted try/except is only there to - # protect against asynchronous exceptions, to ensure that a KBINT - # at the wrong time doesn't deadlock everything. The global - # CODE_TO_RUN is set to true/false as close as possible to the - # runcode() call, so that the KBINT handler is correctly informed. - try: - try: - CODE_RUN = True - InteractiveShell.runcode(self,code_to_run) - except KeyboardInterrupt: - print "Keyboard interrupted in mainloop" - while not self.code_queue.empty(): - code, ev1,ev2 = self.code_queue.get_nowait() - ev1.set() - ev2.set() - break - finally: - CODE_RUN = False - # allow runsource() return from wait - completed_ev.set() - - - # This MUST return true for gtk threading to work - return True - - def kill(self): - """Kill the thread, returning when it has been shut down.""" - self._kill = threading.Event() - self._kill.wait() - -class MatplotlibShellBase: - """Mixin class to provide the necessary modifications to regular IPython - shell classes for matplotlib support. - - Given Python's MRO, this should be used as the FIRST class in the - inheritance hierarchy, so that it overrides the relevant methods.""" - - def _matplotlib_config(self,name,user_ns,user_global_ns=None): - """Return items needed to setup the user's shell with matplotlib""" - - # Initialize matplotlib to interactive mode always - import matplotlib - from matplotlib import backends - matplotlib.interactive(True) - - def use(arg): - """IPython wrapper for matplotlib's backend switcher. - - In interactive use, we can not allow switching to a different - interactive backend, since thread conflicts will most likely crash - the python interpreter. This routine does a safety check first, - and refuses to perform a dangerous switch. It still allows - switching to non-interactive backends.""" - - if arg in backends.interactive_bk and arg != self.mpl_backend: - m=('invalid matplotlib backend switch.\n' - 'This script attempted to switch to the interactive ' - 'backend: `%s`\n' - 'Your current choice of interactive backend is: `%s`\n\n' - 'Switching interactive matplotlib backends at runtime\n' - 'would crash the python interpreter, ' - 'and IPython has blocked it.\n\n' - 'You need to either change your choice of matplotlib backend\n' - 'by editing your .matplotlibrc file, or run this script as a \n' - 'standalone file from the command line, not using IPython.\n' % - (arg,self.mpl_backend) ) - raise RuntimeError, m - else: - self.mpl_use(arg) - self.mpl_use._called = True - - self.matplotlib = matplotlib - self.mpl_backend = matplotlib.rcParams['backend'] - - # we also need to block switching of interactive backends by use() - self.mpl_use = matplotlib.use - self.mpl_use._called = False - # overwrite the original matplotlib.use with our wrapper - matplotlib.use = use - - # This must be imported last in the matplotlib series, after - # backend/interactivity choices have been made - import matplotlib.pylab as pylab - self.pylab = pylab - - self.pylab.show._needmain = False - # We need to detect at runtime whether show() is called by the user. - # For this, we wrap it into a decorator which adds a 'called' flag. - self.pylab.draw_if_interactive = flag_calls(self.pylab.draw_if_interactive) - - # Build a user namespace initialized with matplotlib/matlab features. - user_ns, user_global_ns = IPython.ipapi.make_user_namespaces(user_ns, - user_global_ns) - - # Import numpy as np/pyplot as plt are conventions we're trying to - # somewhat standardize on. Making them available to users by default - # will greatly help this. - exec ("import numpy\n" - "import numpy as np\n" - "import matplotlib\n" - "import matplotlib.pylab as pylab\n" - "try:\n" - " import matplotlib.pyplot as plt\n" - "except ImportError:\n" - " pass\n" - ) in user_ns - - # Build matplotlib info banner - b=""" - Welcome to pylab, a matplotlib-based Python environment. - For more information, type 'help(pylab)'. +Please migrate your code to use these classes instead. """ - return user_ns,user_global_ns,b - - def mplot_exec(self,fname,*where,**kw): - """Execute a matplotlib script. - - This is a call to execfile(), but wrapped in safeties to properly - handle interactive rendering and backend switching.""" - - #print '*** Matplotlib runner ***' # dbg - # turn off rendering until end of script - isInteractive = self.matplotlib.rcParams['interactive'] - self.matplotlib.interactive(False) - self.safe_execfile(fname,*where,**kw) - self.matplotlib.interactive(isInteractive) - # make rendering call now, if the user tried to do it - if self.pylab.draw_if_interactive.called: - self.pylab.draw() - self.pylab.draw_if_interactive.called = False - - # if a backend switch was performed, reverse it now - if self.mpl_use._called: - self.matplotlib.rcParams['backend'] = self.mpl_backend - - @testdec.skip_doctest - def magic_run(self,parameter_s=''): - Magic.magic_run(self,parameter_s,runner=self.mplot_exec) - - # Fix the docstring so users see the original as well - magic_run.__doc__ = "%s\n%s" % (Magic.magic_run.__doc__, - "\n *** Modified %run for Matplotlib," - " with proper interactive handling ***") - -# Now we provide 2 versions of a matplotlib-aware IPython base shells, single -# and multithreaded. Note that these are meant for internal use, the IPShell* -# classes below are the ones meant for public consumption. - -class MatplotlibShell(MatplotlibShellBase,InteractiveShell): - """Single-threaded shell with matplotlib support.""" - - def __init__(self,name,usage=None,rc=Struct(opts=None,args=None), - user_ns=None,user_global_ns=None,**kw): - user_ns,user_global_ns,b2 = self._matplotlib_config(name,user_ns,user_global_ns) - InteractiveShell.__init__(self,name,usage,rc,user_ns,user_global_ns, - banner2=b2,**kw) - -class MatplotlibMTShell(MatplotlibShellBase,MTInteractiveShell): - """Multi-threaded shell with matplotlib support.""" - - def __init__(self,name,usage=None,rc=Struct(opts=None,args=None), - user_ns=None,user_global_ns=None, **kw): - user_ns,user_global_ns,b2 = self._matplotlib_config(name,user_ns,user_global_ns) - MTInteractiveShell.__init__(self,name,usage,rc,user_ns,user_global_ns, - banner2=b2,**kw) -#----------------------------------------------------------------------------- -# Utility functions for the different GUI enabled IPShell* classes. - -def get_tk(): - """Tries to import Tkinter and returns a withdrawn Tkinter root - window. If Tkinter is already imported or not available, this - returns None. This function calls `hijack_tk` underneath. - """ - if not USE_TK or sys.modules.has_key('Tkinter'): - return None - else: - try: - import Tkinter - except ImportError: - return None - else: - hijack_tk() - r = Tkinter.Tk() - r.withdraw() - return r - -def hijack_tk(): - """Modifies Tkinter's mainloop with a dummy so when a module calls - mainloop, it does not block. +warn(msg, category=DeprecationWarning, stacklevel=1) - """ - def misc_mainloop(self, n=0): - pass - def tkinter_mainloop(n=0): - pass - - import Tkinter - Tkinter.Misc.mainloop = misc_mainloop - Tkinter.mainloop = tkinter_mainloop +from IPython.core.iplib import InteractiveShell as IPShell +from IPython.core.embed import InteractiveShellEmbed as IPShellEmbed -def update_tk(tk): - """Updates the Tkinter event loop. This is typically called from - the respective WX or GTK mainloops. - """ - if tk: - tk.update() - -def hijack_wx(): - """Modifies wxPython's MainLoop with a dummy so user code does not - block IPython. The hijacked mainloop function is returned. - """ - def dummy_mainloop(*args, **kw): - pass - - try: - import wx - except ImportError: - # For very old versions of WX - import wxPython as wx - - ver = wx.__version__ - orig_mainloop = None - if ver[:3] >= '2.5': - import wx - if hasattr(wx, '_core_'): core = getattr(wx, '_core_') - elif hasattr(wx, '_core'): core = getattr(wx, '_core') - else: raise AttributeError('Could not find wx core module') - orig_mainloop = core.PyApp_MainLoop - core.PyApp_MainLoop = dummy_mainloop - elif ver[:3] == '2.4': - orig_mainloop = wx.wxc.wxPyApp_MainLoop - wx.wxc.wxPyApp_MainLoop = dummy_mainloop +def start(user_ns=None, embedded=False): + """Return an instance of :class:`InteractiveShell`.""" + if embedded: + return InteractiveShellEmbed(user_ns=user_ns) else: - warn("Unable to find either wxPython version 2.4 or >= 2.5.") - return orig_mainloop - -def hijack_gtk(): - """Modifies pyGTK's mainloop with a dummy so user code does not - block IPython. This function returns the original `gtk.mainloop` - function that has been hijacked. - """ - def dummy_mainloop(*args, **kw): - pass - import gtk - if gtk.pygtk_version >= (2,4,0): orig_mainloop = gtk.main - else: orig_mainloop = gtk.mainloop - gtk.mainloop = dummy_mainloop - gtk.main = dummy_mainloop - return orig_mainloop - -def hijack_qt(): - """Modifies PyQt's mainloop with a dummy so user code does not - block IPython. This function returns the original - `qt.qApp.exec_loop` function that has been hijacked. - """ - def dummy_mainloop(*args, **kw): - pass - import qt - orig_mainloop = qt.qApp.exec_loop - qt.qApp.exec_loop = dummy_mainloop - qt.QApplication.exec_loop = dummy_mainloop - return orig_mainloop - -def hijack_qt4(): - """Modifies PyQt4's mainloop with a dummy so user code does not - block IPython. This function returns the original - `QtGui.qApp.exec_` function that has been hijacked. - """ - def dummy_mainloop(*args, **kw): - pass - from PyQt4 import QtGui, QtCore - orig_mainloop = QtGui.qApp.exec_ - QtGui.qApp.exec_ = dummy_mainloop - QtGui.QApplication.exec_ = dummy_mainloop - QtCore.QCoreApplication.exec_ = dummy_mainloop - return orig_mainloop - -#----------------------------------------------------------------------------- -# The IPShell* classes below are the ones meant to be run by external code as -# IPython instances. Note that unless a specific threading strategy is -# desired, the factory function start() below should be used instead (it -# selects the proper threaded class). - -class IPThread(threading.Thread): - def run(self): - self.IP.mainloop(self._banner) - self.IP.kill() - -class IPShellGTK(IPThread): - """Run a gtk mainloop() in a separate thread. - - Python commands can be passed to the thread where they will be executed. - This is implemented by periodically checking for passed code using a - GTK timeout callback.""" - - TIMEOUT = 100 # Millisecond interval between timeouts. - - def __init__(self,argv=None,user_ns=None,user_global_ns=None, - debug=1,shell_class=MTInteractiveShell): - - import gtk - # Check for set_interactive, coming up in new pygtk. - # Disable it so that this code works, but notify - # the user that he has a better option as well. - # XXX TODO better support when set_interactive is released - try: - gtk.set_interactive(False) - print "Your PyGtk has set_interactive(), so you can use the" - print "more stable single-threaded Gtk mode." - print "See https://bugs.launchpad.net/ipython/+bug/270856" - except AttributeError: - pass - - self.gtk = gtk - self.gtk_mainloop = hijack_gtk() - - # Allows us to use both Tk and GTK. - self.tk = get_tk() - - if gtk.pygtk_version >= (2,4,0): mainquit = self.gtk.main_quit - else: mainquit = self.gtk.mainquit - - self.IP = make_IPython(argv,user_ns=user_ns, - user_global_ns=user_global_ns, - debug=debug, - shell_class=shell_class, - on_kill=[mainquit]) - - # HACK: slot for banner in self; it will be passed to the mainloop - # method only and .run() needs it. The actual value will be set by - # .mainloop(). - self._banner = None - - threading.Thread.__init__(self) - - def mainloop(self,sys_exit=0,banner=None): - - self._banner = banner - - if self.gtk.pygtk_version >= (2,4,0): - import gobject - gobject.idle_add(self.on_timer) - else: - self.gtk.idle_add(self.on_timer) - - if sys.platform != 'win32': - try: - if self.gtk.gtk_version[0] >= 2: - self.gtk.gdk.threads_init() - except AttributeError: - pass - except RuntimeError: - error('Your pyGTK likely has not been compiled with ' - 'threading support.\n' - 'The exception printout is below.\n' - 'You can either rebuild pyGTK with threads, or ' - 'try using \n' - 'matplotlib with a different backend (like Tk or WX).\n' - 'Note that matplotlib will most likely not work in its ' - 'current state!') - self.IP.InteractiveTB() - - self.start() - self.gtk.gdk.threads_enter() - self.gtk_mainloop() - self.gtk.gdk.threads_leave() - self.join() - - def on_timer(self): - """Called when GTK is idle. - - Must return True always, otherwise GTK stops calling it""" - - update_tk(self.tk) - self.IP.runcode() - time.sleep(0.01) - return True - - -class IPShellWX(IPThread): - """Run a wx mainloop() in a separate thread. - - Python commands can be passed to the thread where they will be executed. - This is implemented by periodically checking for passed code using a - GTK timeout callback.""" - - TIMEOUT = 100 # Millisecond interval between timeouts. - - def __init__(self,argv=None,user_ns=None,user_global_ns=None, - debug=1,shell_class=MTInteractiveShell): - - self.IP = make_IPython(argv,user_ns=user_ns, - user_global_ns=user_global_ns, - debug=debug, - shell_class=shell_class, - on_kill=[self.wxexit]) - - wantedwxversion=self.IP.rc.wxversion - if wantedwxversion!="0": - try: - import wxversion - except ImportError: - error('The wxversion module is needed for WX version selection') - else: - try: - wxversion.select(wantedwxversion) - except: - self.IP.InteractiveTB() - error('Requested wxPython version %s could not be loaded' % - wantedwxversion) - - import wx - - threading.Thread.__init__(self) - self.wx = wx - self.wx_mainloop = hijack_wx() - - # Allows us to use both Tk and GTK. - self.tk = get_tk() - - # HACK: slot for banner in self; it will be passed to the mainloop - # method only and .run() needs it. The actual value will be set by - # .mainloop(). - self._banner = None - - self.app = None - - def wxexit(self, *args): - if self.app is not None: - self.app.agent.timer.Stop() - self.app.ExitMainLoop() - - def mainloop(self,sys_exit=0,banner=None): - - self._banner = banner - - self.start() - - class TimerAgent(self.wx.MiniFrame): - wx = self.wx - IP = self.IP - tk = self.tk - def __init__(self, parent, interval): - style = self.wx.DEFAULT_FRAME_STYLE | self.wx.TINY_CAPTION_HORIZ - self.wx.MiniFrame.__init__(self, parent, -1, ' ', pos=(200, 200), - size=(100, 100),style=style) - self.Show(False) - self.interval = interval - self.timerId = self.wx.NewId() - - def StartWork(self): - self.timer = self.wx.Timer(self, self.timerId) - self.wx.EVT_TIMER(self, self.timerId, self.OnTimer) - self.timer.Start(self.interval) - - def OnTimer(self, event): - update_tk(self.tk) - self.IP.runcode() - - class App(self.wx.App): - wx = self.wx - TIMEOUT = self.TIMEOUT - def OnInit(self): - 'Create the main window and insert the custom frame' - self.agent = TimerAgent(None, self.TIMEOUT) - self.agent.Show(False) - self.agent.StartWork() - return True - - self.app = App(redirect=False) - self.wx_mainloop(self.app) - self.join() - - -class IPShellQt(IPThread): - """Run a Qt event loop in a separate thread. - - Python commands can be passed to the thread where they will be executed. - This is implemented by periodically checking for passed code using a - Qt timer / slot.""" - - TIMEOUT = 100 # Millisecond interval between timeouts. - - def __init__(self, argv=None, user_ns=None, user_global_ns=None, - debug=0, shell_class=MTInteractiveShell): - - import qt - - self.exec_loop = hijack_qt() - - # Allows us to use both Tk and QT. - self.tk = get_tk() - - self.IP = make_IPython(argv, - user_ns=user_ns, - user_global_ns=user_global_ns, - debug=debug, - shell_class=shell_class, - on_kill=[qt.qApp.exit]) - - # HACK: slot for banner in self; it will be passed to the mainloop - # method only and .run() needs it. The actual value will be set by - # .mainloop(). - self._banner = None - - threading.Thread.__init__(self) - - def mainloop(self, sys_exit=0, banner=None): - - import qt - - self._banner = banner - - if qt.QApplication.startingUp(): - a = qt.QApplication(sys.argv) - - self.timer = qt.QTimer() - qt.QObject.connect(self.timer, - qt.SIGNAL('timeout()'), - self.on_timer) - - self.start() - self.timer.start(self.TIMEOUT, True) - while True: - if self.IP._kill: break - self.exec_loop() - self.join() - - def on_timer(self): - update_tk(self.tk) - result = self.IP.runcode() - self.timer.start(self.TIMEOUT, True) - return result - - -class IPShellQt4(IPThread): - """Run a Qt event loop in a separate thread. - - Python commands can be passed to the thread where they will be executed. - This is implemented by periodically checking for passed code using a - Qt timer / slot.""" - - TIMEOUT = 100 # Millisecond interval between timeouts. - - def __init__(self, argv=None, user_ns=None, user_global_ns=None, - debug=0, shell_class=MTInteractiveShell): - - from PyQt4 import QtCore, QtGui - - try: - # present in PyQt4-4.2.1 or later - QtCore.pyqtRemoveInputHook() - except AttributeError: - pass - - if QtCore.PYQT_VERSION_STR == '4.3': - warn('''PyQt4 version 4.3 detected. -If you experience repeated threading warnings, please update PyQt4. -''') - - self.exec_ = hijack_qt4() - - # Allows us to use both Tk and QT. - self.tk = get_tk() - - self.IP = make_IPython(argv, - user_ns=user_ns, - user_global_ns=user_global_ns, - debug=debug, - shell_class=shell_class, - on_kill=[QtGui.qApp.exit]) - - # HACK: slot for banner in self; it will be passed to the mainloop - # method only and .run() needs it. The actual value will be set by - # .mainloop(). - self._banner = None - - threading.Thread.__init__(self) - - def mainloop(self, sys_exit=0, banner=None): - - from PyQt4 import QtCore, QtGui - - self._banner = banner - - if QtGui.QApplication.startingUp(): - a = QtGui.QApplication(sys.argv) - - self.timer = QtCore.QTimer() - QtCore.QObject.connect(self.timer, - QtCore.SIGNAL('timeout()'), - self.on_timer) - - self.start() - self.timer.start(self.TIMEOUT) - while True: - if self.IP._kill: break - self.exec_() - self.join() - - def on_timer(self): - update_tk(self.tk) - result = self.IP.runcode() - self.timer.start(self.TIMEOUT) - return result - - -# A set of matplotlib public IPython shell classes, for single-threaded (Tk* -# and FLTK*) and multithreaded (GTK*, WX* and Qt*) backends to use. -def _load_pylab(user_ns): - """Allow users to disable pulling all of pylab into the top-level - namespace. - - This little utility must be called AFTER the actual ipython instance is - running, since only then will the options file have been fully parsed.""" - - ip = IPython.ipapi.get() - if ip.options.pylab_import_all: - ip.ex("from matplotlib.pylab import *") - ip.IP.user_config_ns.update(ip.user_ns) - - -class IPShellMatplotlib(IPShell): - """Subclass IPShell with MatplotlibShell as the internal shell. - - Single-threaded class, meant for the Tk* and FLTK* backends. - - Having this on a separate class simplifies the external driver code.""" - - def __init__(self,argv=None,user_ns=None,user_global_ns=None,debug=1): - IPShell.__init__(self,argv,user_ns,user_global_ns,debug, - shell_class=MatplotlibShell) - _load_pylab(self.IP.user_ns) - -class IPShellMatplotlibGTK(IPShellGTK): - """Subclass IPShellGTK with MatplotlibMTShell as the internal shell. - - Multi-threaded class, meant for the GTK* backends.""" - - def __init__(self,argv=None,user_ns=None,user_global_ns=None,debug=1): - IPShellGTK.__init__(self,argv,user_ns,user_global_ns,debug, - shell_class=MatplotlibMTShell) - _load_pylab(self.IP.user_ns) - -class IPShellMatplotlibWX(IPShellWX): - """Subclass IPShellWX with MatplotlibMTShell as the internal shell. - - Multi-threaded class, meant for the WX* backends.""" - - def __init__(self,argv=None,user_ns=None,user_global_ns=None,debug=1): - IPShellWX.__init__(self,argv,user_ns,user_global_ns,debug, - shell_class=MatplotlibMTShell) - _load_pylab(self.IP.user_ns) - -class IPShellMatplotlibQt(IPShellQt): - """Subclass IPShellQt with MatplotlibMTShell as the internal shell. - - Multi-threaded class, meant for the Qt* backends.""" - - def __init__(self,argv=None,user_ns=None,user_global_ns=None,debug=1): - IPShellQt.__init__(self,argv,user_ns,user_global_ns,debug, - shell_class=MatplotlibMTShell) - _load_pylab(self.IP.user_ns) - -class IPShellMatplotlibQt4(IPShellQt4): - """Subclass IPShellQt4 with MatplotlibMTShell as the internal shell. - - Multi-threaded class, meant for the Qt4* backends.""" - - def __init__(self,argv=None,user_ns=None,user_global_ns=None,debug=1): - IPShellQt4.__init__(self,argv,user_ns,user_global_ns,debug, - shell_class=MatplotlibMTShell) - _load_pylab(self.IP.user_ns) - -#----------------------------------------------------------------------------- -# Factory functions to actually start the proper thread-aware shell - -def _select_shell(argv): - """Select a shell from the given argv vector. - - This function implements the threading selection policy, allowing runtime - control of the threading mode, both for general users and for matplotlib. - - Return: - Shell class to be instantiated for runtime operation. - """ - - global USE_TK - - mpl_shell = {'gthread' : IPShellMatplotlibGTK, - 'wthread' : IPShellMatplotlibWX, - 'qthread' : IPShellMatplotlibQt, - 'q4thread' : IPShellMatplotlibQt4, - 'tkthread' : IPShellMatplotlib, # Tk is built-in - } - - th_shell = {'gthread' : IPShellGTK, - 'wthread' : IPShellWX, - 'qthread' : IPShellQt, - 'q4thread' : IPShellQt4, - 'tkthread' : IPShell, # Tk is built-in - } - - backends = {'gthread' : 'GTKAgg', - 'wthread' : 'WXAgg', - 'qthread' : 'QtAgg', - 'q4thread' :'Qt4Agg', - 'tkthread' :'TkAgg', - } - - all_opts = set(['tk','pylab','gthread','qthread','q4thread','wthread', - 'tkthread']) - user_opts = set([s.replace('-','') for s in argv[:3]]) - special_opts = user_opts & all_opts - - if 'tk' in special_opts: - USE_TK = True - special_opts.remove('tk') - - if 'pylab' in special_opts: - - try: - import matplotlib - except ImportError: - error('matplotlib could NOT be imported! Starting normal IPython.') - return IPShell - - special_opts.remove('pylab') - # If there's any option left, it means the user wants to force the - # threading backend, else it's auto-selected from the rc file - if special_opts: - th_mode = special_opts.pop() - matplotlib.rcParams['backend'] = backends[th_mode] - else: - backend = matplotlib.rcParams['backend'] - if backend.startswith('GTK'): - th_mode = 'gthread' - elif backend.startswith('WX'): - th_mode = 'wthread' - elif backend.startswith('Qt4'): - th_mode = 'q4thread' - elif backend.startswith('Qt'): - th_mode = 'qthread' - else: - # Any other backend, use plain Tk - th_mode = 'tkthread' - - return mpl_shell[th_mode] - else: - # No pylab requested, just plain threads - try: - th_mode = special_opts.pop() - except KeyError: - th_mode = 'tkthread' - return th_shell[th_mode] - - -# This is the one which should be called by external code. -def start(user_ns = None): - """Return a running shell instance, dealing with threading options. - - This is a factory function which will instantiate the proper IPython shell - based on the user's threading choice. Such a selector is needed because - different GUI toolkits require different thread handling details.""" - - shell = _select_shell(sys.argv) - return shell(user_ns = user_ns) + return InteractiveShell(user_ns=user_ns) -# Some aliases for backwards compatibility -IPythonShell = IPShell -IPythonShellEmbed = IPShellEmbed -#************************ End of file *************************** diff --git a/IPython/UserConfig/ipy_user_conf.py b/IPython/UserConfig/ipy_user_conf.py deleted file mode 100644 index 6d6fbbd..0000000 --- a/IPython/UserConfig/ipy_user_conf.py +++ /dev/null @@ -1,114 +0,0 @@ -""" User configuration file for IPython - -This is a more flexible and safe way to configure ipython than *rc files -(ipythonrc, ipythonrc-pysh etc.) - -This file is always imported on ipython startup. You can import the -ipython extensions you need here (see IPython/Extensions directory). - -Feel free to edit this file to customize your ipython experience. - -Note that as such this file does nothing, for backwards compatibility. -Consult e.g. file 'ipy_profile_sh.py' for an example of the things -you can do here. - -See http://ipython.scipy.org/moin/IpythonExtensionApi for detailed -description on what you could do here. -""" - -# Most of your config files and extensions will probably start with this import - -import IPython.ipapi -ip = IPython.ipapi.get() - -# You probably want to uncomment this if you did %upgrade -nolegacy -# import ipy_defaults - -import os - -def main(): - - # uncomment if you want to get ipython -p sh behaviour - # without having to use command line switches - # import ipy_profile_sh - - # Configure your favourite editor? - # Good idea e.g. for %edit os.path.isfile - - #import ipy_editors - - # Choose one of these: - - #ipy_editors.scite() - #ipy_editors.scite('c:/opt/scite/scite.exe') - #ipy_editors.komodo() - #ipy_editors.idle() - # ... or many others, try 'ipy_editors??' after import to see them - - # Or roll your own: - #ipy_editors.install_editor("c:/opt/jed +$line $file") - - - o = ip.options - # An example on how to set options - #o.autocall = 1 - o.system_verbose = 0 - - #import_all("os sys") - #execf('~/_ipython/ns.py') - - - # -- prompt - # A different, more compact set of prompts from the default ones, that - # always show your current location in the filesystem: - - #o.prompt_in1 = r'\C_LightBlue[\C_LightCyan\Y2\C_LightBlue]\C_Normal\n\C_Green|\#>' - #o.prompt_in2 = r'.\D: ' - #o.prompt_out = r'[\#] ' - - # Try one of these color settings if you can't read the text easily - # autoexec is a list of IPython commands to execute on startup - #o.autoexec.append('%colors LightBG') - #o.autoexec.append('%colors NoColor') - #o.autoexec.append('%colors Linux') - - # for sane integer division that converts to float (1/2 == 0.5) - #o.autoexec.append('from __future__ import division') - - # For %tasks and %kill - #import jobctrl - - # For autoreloading of modules (%autoreload, %aimport) - #import ipy_autoreload - - # For winpdb support (%wdb) - #import ipy_winpdb - - # For bzr completer, requires bzrlib (the python installation of bzr) - #ip.load('ipy_bzr') - - # Tab completer that is not quite so picky (i.e. - # "foo". and str(2). will work). Complete - # at your own risk! - #import ipy_greedycompleter - - # If you are on Linux, you may be annoyed by - # "Display all N possibilities? (y or n)" on tab completion, - # as well as the paging through "more". Uncomment the following - # lines to disable that behaviour - #import readline - #readline.parse_and_bind('set completion-query-items 1000') - #readline.parse_and_bind('set page-completions no') - - -# some config helper functions you can use -def import_all(modules): - """ Usage: import_all("os sys") """ - for m in modules.split(): - ip.ex("from %s import *" % m) - -def execf(fname): - """ Execute a file in user namespace """ - ip.ex('execfile("%s")' % os.path.expanduser(fname)) - -main() diff --git a/IPython/UserConfig/ipythonrc b/IPython/UserConfig/ipythonrc deleted file mode 100644 index c90044f..0000000 --- a/IPython/UserConfig/ipythonrc +++ /dev/null @@ -1,631 +0,0 @@ -# -*- Mode: Shell-Script -*- Not really, but shows comments correctly - -#*************************************************************************** -# -# Configuration file for IPython -- ipythonrc format -# -# =========================================================== -# Deprecation note: you should look into modifying ipy_user_conf.py (located -# in ~/.ipython or ~/_ipython, depending on your platform) instead, it's a -# more flexible and robust (and better supported!) configuration -# method. -# =========================================================== -# -# The format of this file is simply one of 'key value' lines. -# Lines containing only whitespace at the beginning and then a # are ignored -# as comments. But comments can NOT be put on lines with data. - -# The meaning and use of each key are explained below. - -#--------------------------------------------------------------------------- -# Section: included files - -# Put one or more *config* files (with the syntax of this file) you want to -# include. For keys with a unique value the outermost file has precedence. For -# keys with multiple values, they all get assembled into a list which then -# gets loaded by IPython. - -# In this file, all lists of things should simply be space-separated. - -# This allows you to build hierarchies of files which recursively load -# lower-level services. If this is your main ~/.ipython/ipythonrc file, you -# should only keep here basic things you always want available. Then you can -# include it in every other special-purpose config file you create. -include - -#--------------------------------------------------------------------------- -# Section: startup setup - -# These are mostly things which parallel a command line option of the same -# name. - -# Keys in this section should only appear once. If any key from this section -# is encountered more than once, the last value remains, all earlier ones get -# discarded. - - -# Automatic calling of callable objects. If set to 1 or 2, callable objects -# are automatically called when invoked at the command line, even if you don't -# type parentheses. IPython adds the parentheses for you. For example: - -#In [1]: str 45 -#------> str(45) -#Out[1]: '45' - -# IPython reprints your line with '---->' indicating that it added -# parentheses. While this option is very convenient for interactive use, it -# may occasionally cause problems with objects which have side-effects if -# called unexpectedly. - -# The valid values for autocall are: - -# autocall 0 -> disabled (you can toggle it at runtime with the %autocall magic) - -# autocall 1 -> active, but do not apply if there are no arguments on the line. - -# In this mode, you get: - -#In [1]: callable -#Out[1]: - -#In [2]: callable 'hello' -#------> callable('hello') -#Out[2]: False - -# 2 -> Active always. Even if no arguments are present, the callable object -# is called: - -#In [4]: callable -#------> callable() - -# Note that even with autocall off, you can still use '/' at the start of a -# line to treat the first argument on the command line as a function and add -# parentheses to it: - -#In [8]: /str 43 -#------> str(43) -#Out[8]: '43' - -autocall 1 - -# Auto-edit syntax errors. When you use the %edit magic in ipython to edit -# source code (see the 'editor' variable below), it is possible that you save -# a file with syntax errors in it. If this variable is true, IPython will ask -# you whether to re-open the editor immediately to correct such an error. - -autoedit_syntax 0 - -# Auto-indent. IPython can recognize lines ending in ':' and indent the next -# line, while also un-indenting automatically after 'raise' or 'return'. - -# This feature uses the readline library, so it will honor your ~/.inputrc -# configuration (or whatever file your INPUTRC variable points to). Adding -# the following lines to your .inputrc file can make indent/unindenting more -# convenient (M-i indents, M-u unindents): - -# $if Python -# "\M-i": " " -# "\M-u": "\d\d\d\d" -# $endif - -# The feature is potentially a bit dangerous, because it can cause problems -# with pasting of indented code (the pasted code gets re-indented on each -# line). But it's a huge time-saver when working interactively. The magic -# function %autoindent allows you to toggle it on/off at runtime. - -autoindent 1 - -# Auto-magic. This gives you access to all the magic functions without having -# to prepend them with an % sign. If you define a variable with the same name -# as a magic function (say who=1), you will need to access the magic function -# with % (%who in this example). However, if later you delete your variable -# (del who), you'll recover the automagic calling form. - -# Considering that many magic functions provide a lot of shell-like -# functionality, automagic gives you something close to a full Python+system -# shell environment (and you can extend it further if you want). - -automagic 1 - -# Size of the output cache. After this many entries are stored, the cache will -# get flushed. Depending on the size of your intermediate calculations, you -# may have memory problems if you make it too big, since keeping things in the -# cache prevents Python from reclaiming the memory for old results. Experiment -# with a value that works well for you. - -# If you choose cache_size 0 IPython will revert to python's regular >>> -# unnumbered prompt. You will still have _, __ and ___ for your last three -# results, but that will be it. No dynamic _1, _2, etc. will be created. If -# you are running on a slow machine or with very limited memory, this may -# help. - -cache_size 1000 - -# Classic mode: Setting 'classic 1' you lose many of IPython niceties, -# but that's your choice! Classic 1 -> same as IPython -classic. -# Note that this is _not_ the normal python interpreter, it's simply -# IPython emulating most of the classic interpreter's behavior. -classic 0 - -# colors - Coloring option for prompts and traceback printouts. - -# Currently available schemes: NoColor, Linux, LightBG. - -# This option allows coloring the prompts and traceback printouts. This -# requires a terminal which can properly handle color escape sequences. If you -# are having problems with this, use the NoColor scheme (uses no color escapes -# at all). - -# The Linux option works well in linux console type environments: dark -# background with light fonts. - -# LightBG is similar to Linux but swaps dark/light colors to be more readable -# in light background terminals. - -# keep uncommented only the one you want: -colors Linux -#colors LightBG -#colors NoColor - -######################## -# Note to Windows users -# -# Color and readline support is avaialble to Windows users via Gary Bishop's -# readline library. You can find Gary's tools at -# http://sourceforge.net/projects/uncpythontools. -# Note that his readline module requires in turn the ctypes library, available -# at http://starship.python.net/crew/theller/ctypes. -######################## - -# color_info: IPython can display information about objects via a set of -# functions, and optionally can use colors for this, syntax highlighting -# source code and various other elements. This information is passed through a -# pager (it defaults to 'less' if $PAGER is not set). - -# If your pager has problems, try to setting it to properly handle escapes -# (see the less manpage for detail), or disable this option. The magic -# function %color_info allows you to toggle this interactively for testing. - -color_info 1 - -# confirm_exit: set to 1 if you want IPython to confirm when you try to exit -# with an EOF (Control-d in Unix, Control-Z/Enter in Windows). Note that using -# the magic functions %Exit or %Quit you can force a direct exit, bypassing -# any confirmation. - -confirm_exit 1 - -# Use deep_reload() as a substitute for reload() by default. deep_reload() is -# still available as dreload() and appears as a builtin. - -deep_reload 0 - -# Which editor to use with the %edit command. If you leave this at 0, IPython -# will honor your EDITOR environment variable. Since this editor is invoked on -# the fly by ipython and is meant for editing small code snippets, you may -# want to use a small, lightweight editor here. - -# For Emacs users, setting up your Emacs server properly as described in the -# manual is a good idea. An alternative is to use jed, a very light editor -# with much of the feel of Emacs (though not as powerful for heavy-duty work). - -editor 0 - -# log 1 -> same as ipython -log. This automatically logs to ./ipython.log -log 0 - -# Same as ipython -Logfile YourLogfileName. -# Don't use with log 1 (use one or the other) -logfile '' - -# banner 0 -> same as ipython -nobanner -banner 1 - -# messages 0 -> same as ipython -nomessages -messages 1 - -# Automatically call the pdb debugger after every uncaught exception. If you -# are used to debugging using pdb, this puts you automatically inside of it -# after any call (either in IPython or in code called by it) which triggers an -# exception which goes uncaught. -pdb 0 - -# Enable the pprint module for printing. pprint tends to give a more readable -# display (than print) for complex nested data structures. -pprint 1 - -# Prompt strings - -# Most bash-like escapes can be used to customize IPython's prompts, as well as -# a few additional ones which are IPython-specific. All valid prompt escapes -# are described in detail in the Customization section of the IPython HTML/PDF -# manual. - -# Use \# to represent the current prompt number, and quote them to protect -# spaces. -prompt_in1 'In [\#]: ' - -# \D is replaced by as many dots as there are digits in the -# current value of \#. -prompt_in2 ' .\D.: ' - -prompt_out 'Out[\#]: ' - -# Select whether to left-pad the output prompts to match the length of the -# input ones. This allows you for example to use a simple '>' as an output -# prompt, and yet have the output line up with the input. If set to false, -# the output prompts will be unpadded (flush left). -prompts_pad_left 1 - -# Pylab support: when ipython is started with the -pylab switch, by default it -# executes 'from matplotlib.pylab import *'. Set this variable to false if you -# want to disable this behavior. - -# For details on pylab, see the matplotlib website: -# http://matplotlib.sf.net -pylab_import_all 1 - - -# quick 1 -> same as ipython -quick -quick 0 - -# Use the readline library (1) or not (0). Most users will want this on, but -# if you experience strange problems with line management (mainly when using -# IPython inside Emacs buffers) you may try disabling it. Not having it on -# prevents you from getting command history with the arrow keys, searching and -# name completion using TAB. - -readline 1 - -# Screen Length: number of lines of your screen. This is used to control -# printing of very long strings. Strings longer than this number of lines will -# be paged with the less command instead of directly printed. - -# The default value for this is 0, which means IPython will auto-detect your -# screen size every time it needs to print. If for some reason this isn't -# working well (it needs curses support), specify it yourself. Otherwise don't -# change the default. - -screen_length 0 - -# Prompt separators for input and output. -# Use \n for newline explicitly, without quotes. -# Use 0 (like at the cmd line) to turn off a given separator. - -# The structure of prompt printing is: -# (SeparateIn)Input.... -# (SeparateOut)Output... -# (SeparateOut2), # that is, no newline is printed after Out2 -# By choosing these you can organize your output any way you want. - -separate_in \n -separate_out 0 -separate_out2 0 - -# 'nosep 1' is a shorthand for '-SeparateIn 0 -SeparateOut 0 -SeparateOut2 0'. -# Simply removes all input/output separators, overriding the choices above. -nosep 0 - -# Wildcard searches - IPython has a system for searching names using -# shell-like wildcards; type %psearch? for details. This variables sets -# whether by default such searches should be case sensitive or not. You can -# always override the default at the system command line or the IPython -# prompt. - -wildcards_case_sensitive 1 - -# Object information: at what level of detail to display the string form of an -# object. If set to 0, ipython will compute the string form of any object X, -# by calling str(X), when X? is typed. If set to 1, str(X) will only be -# computed when X?? is given, and if set to 2 or higher, it will never be -# computed (there is no X??? level of detail). This is mostly of use to -# people who frequently manipulate objects whose string representation is -# extremely expensive to compute. - -object_info_string_level 0 - -# xmode - Exception reporting mode. - -# Valid modes: Plain, Context and Verbose. - -# Plain: similar to python's normal traceback printing. - -# Context: prints 5 lines of context source code around each line in the -# traceback. - -# Verbose: similar to Context, but additionally prints the variables currently -# visible where the exception happened (shortening their strings if too -# long). This can potentially be very slow, if you happen to have a huge data -# structure whose string representation is complex to compute. Your computer -# may appear to freeze for a while with cpu usage at 100%. If this occurs, you -# can cancel the traceback with Ctrl-C (maybe hitting it more than once). - -#xmode Plain -xmode Context -#xmode Verbose - -# multi_line_specials: if true, allow magics, aliases and shell escapes (via -# !cmd) to be used in multi-line input (like for loops). For example, if you -# have this active, the following is valid in IPython: -# -#In [17]: for i in range(3): -# ....: mkdir $i -# ....: !touch $i/hello -# ....: ls -l $i - -multi_line_specials 1 - - -# System calls: When IPython makes system calls (e.g. via special syntax like -# !cmd or !!cmd, or magics like %sc or %sx), it can print the command it is -# executing to standard output, prefixed by a header string. - -system_header "IPython system call: " - -system_verbose 1 - -# wxversion: request a specific wxPython version (used for -wthread) - -# Set this to the value of wxPython you want to use, but note that this -# feature requires you to have the wxversion Python module to work. If you -# don't have the wxversion module (try 'import wxversion' at the prompt to -# check) or simply want to leave the system to pick up the default, leave this -# variable at 0. - -wxversion 0 - -#--------------------------------------------------------------------------- -# Section: Readline configuration (readline is not available for MS-Windows) - -# This is done via the following options: - -# (i) readline_parse_and_bind: this option can appear as many times as you -# want, each time defining a string to be executed via a -# readline.parse_and_bind() command. The syntax for valid commands of this -# kind can be found by reading the documentation for the GNU readline library, -# as these commands are of the kind which readline accepts in its -# configuration file. - -# The TAB key can be used to complete names at the command line in one of two -# ways: 'complete' and 'menu-complete'. The difference is that 'complete' only -# completes as much as possible while 'menu-complete' cycles through all -# possible completions. Leave the one you prefer uncommented. - -readline_parse_and_bind tab: complete -#readline_parse_and_bind tab: menu-complete - -# This binds Control-l to printing the list of all possible completions when -# there is more than one (what 'complete' does when hitting TAB twice, or at -# the first TAB if show-all-if-ambiguous is on) -readline_parse_and_bind "\C-l": possible-completions - -# This forces readline to automatically print the above list when tab -# completion is set to 'complete'. You can still get this list manually by -# using the key bound to 'possible-completions' (Control-l by default) or by -# hitting TAB twice. Turning this on makes the printing happen at the first -# TAB. -readline_parse_and_bind set show-all-if-ambiguous on - -# If you have TAB set to complete names, you can rebind any key (Control-o by -# default) to insert a true TAB character. -readline_parse_and_bind "\C-o": tab-insert - -# These commands allow you to indent/unindent easily, with the 4-space -# convention of the Python coding standards. Since IPython's internal -# auto-indent system also uses 4 spaces, you should not change the number of -# spaces in the code below. -readline_parse_and_bind "\M-i": " " -readline_parse_and_bind "\M-o": "\d\d\d\d" -readline_parse_and_bind "\M-I": "\d\d\d\d" - -# Bindings for incremental searches in the history. These searches use the -# string typed so far on the command line and search anything in the previous -# input history containing them. -readline_parse_and_bind "\C-r": reverse-search-history -readline_parse_and_bind "\C-s": forward-search-history - -# Bindings for completing the current line in the history of previous -# commands. This allows you to recall any previous command by typing its first -# few letters and hitting Control-p, bypassing all intermediate commands which -# may be in the history (much faster than hitting up-arrow 50 times!) -readline_parse_and_bind "\C-p": history-search-backward -readline_parse_and_bind "\C-n": history-search-forward - -# I also like to have the same functionality on the plain arrow keys. If you'd -# rather have the arrows use all the history (and not just match what you've -# typed so far), comment out or delete the next two lines. -readline_parse_and_bind "\e[A": history-search-backward -readline_parse_and_bind "\e[B": history-search-forward - -# These are typically on by default under *nix, but not win32. -readline_parse_and_bind "\C-k": kill-line -readline_parse_and_bind "\C-u": unix-line-discard - -# (ii) readline_remove_delims: a string of characters to be removed from the -# default word-delimiters list used by readline, so that completions may be -# performed on strings which contain them. - -readline_remove_delims -/~ - -# (iii) readline_merge_completions: whether to merge the result of all -# possible completions or not. If true, IPython will complete filenames, -# python names and aliases and return all possible completions. If you set it -# to false, each completer is used at a time, and only if it doesn't return -# any completions is the next one used. - -# The default order is: [python_matches, file_matches, alias_matches] - -readline_merge_completions 1 - -# (iv) readline_omit__names: normally hitting after a '.' in a name -# will complete all attributes of an object, including all the special methods -# whose names start with single or double underscores (like __getitem__ or -# __class__). - -# This variable allows you to control this completion behavior: - -# readline_omit__names 1 -> completion will omit showing any names starting -# with two __, but it will still show names starting with one _. - -# readline_omit__names 2 -> completion will omit all names beginning with one -# _ (which obviously means filtering out the double __ ones). - -# Even when this option is set, you can still see those names by explicitly -# typing a _ after the period and hitting : 'name._' will always -# complete attribute names starting with '_'. - -# This option is off by default so that new users see all attributes of any -# objects they are dealing with. - -readline_omit__names 0 - -#--------------------------------------------------------------------------- -# Section: modules to be loaded with 'import ...' - -# List, separated by spaces, the names of the modules you want to import - -# Example: -# import_mod sys os -# will produce internally the statements -# import sys -# import os - -# Each import is executed in its own try/except block, so if one module -# fails to load the others will still be ok. - -import_mod - -#--------------------------------------------------------------------------- -# Section: modules to import some functions from: 'from ... import ...' - -# List, one per line, the modules for which you want only to import some -# functions. Give the module name first and then the name of functions to be -# imported from that module. - -# Example: - -# import_some IPython.genutils timing timings -# will produce internally the statement -# from IPython.genutils import timing, timings - -# timing() and timings() are two IPython utilities for timing the execution of -# your own functions, which you may find useful. Just commment out the above -# line if you want to test them. - -# If you have more than one modules_some line, each gets its own try/except -# block (like modules, see above). - -import_some - -#--------------------------------------------------------------------------- -# Section: modules to import all from : 'from ... import *' - -# List (same syntax as import_mod above) those modules for which you want to -# import all functions. Remember, this is a potentially dangerous thing to do, -# since it is very easy to overwrite names of things you need. Use with -# caution. - -# Example: -# import_all sys os -# will produce internally the statements -# from sys import * -# from os import * - -# As before, each will be called in a separate try/except block. - -import_all - -#--------------------------------------------------------------------------- -# Section: Python code to execute. - -# Put here code to be explicitly executed (keep it simple!) -# Put one line of python code per line. All whitespace is removed (this is a -# feature, not a bug), so don't get fancy building loops here. -# This is just for quick convenient creation of things you want available. - -# Example: -# execute x = 1 -# execute print 'hello world'; y = z = 'a' -# will produce internally -# x = 1 -# print 'hello world'; y = z = 'a' -# and each *line* (not each statement, we don't do python syntax parsing) is -# executed in its own try/except block. - -execute - -# Note for the adventurous: you can use this to define your own names for the -# magic functions, by playing some namespace tricks: - -# execute __IPYTHON__.magic_pf = __IPYTHON__.magic_profile - -# defines %pf as a new name for %profile. - -#--------------------------------------------------------------------------- -# Section: Pyhton files to load and execute. - -# Put here the full names of files you want executed with execfile(file). If -# you want complicated initialization, just write whatever you want in a -# regular python file and load it from here. - -# Filenames defined here (which *must* include the extension) are searched for -# through all of sys.path. Since IPython adds your .ipython directory to -# sys.path, they can also be placed in your .ipython dir and will be -# found. Otherwise (if you want to execute things not in .ipyton nor in -# sys.path) give a full path (you can use ~, it gets expanded) - -# Example: -# execfile file1.py ~/file2.py -# will generate -# execfile('file1.py') -# execfile('_path_to_your_home/file2.py') - -# As before, each file gets its own try/except block. - -execfile - -# If you are feeling adventurous, you can even add functionality to IPython -# through here. IPython works through a global variable called __ip which -# exists at the time when these files are read. If you know what you are doing -# (read the source) you can add functions to __ip in files loaded here. - -# The file example-magic.py contains a simple but correct example. Try it: - -# execfile example-magic.py - -# Look at the examples in IPython/iplib.py for more details on how these magic -# functions need to process their arguments. - -#--------------------------------------------------------------------------- -# Section: aliases for system shell commands - -# Here you can define your own names for system commands. The syntax is -# similar to that of the builtin %alias function: - -# alias alias_name command_string - -# The resulting aliases are auto-generated magic functions (hence usable as -# %alias_name) - -# For example: - -# alias myls ls -la - -# will define 'myls' as an alias for executing the system command 'ls -la'. -# This allows you to customize IPython's environment to have the same aliases -# you are accustomed to from your own shell. - -# You can also define aliases with parameters using %s specifiers (one per -# parameter): - -# alias parts echo first %s second %s - -# will give you in IPython: -# >>> %parts A B -# first A second B - -# Use one 'alias' statement per alias you wish to define. - -# alias - -#************************* end of file ************************ diff --git a/IPython/UserConfig/ipythonrc-math b/IPython/UserConfig/ipythonrc-math deleted file mode 100644 index c32bcd1..0000000 --- a/IPython/UserConfig/ipythonrc-math +++ /dev/null @@ -1,36 +0,0 @@ -# -*- Mode: Shell-Script -*- Not really, but shows comments correctly -#*************************************************************************** -# -# Configuration file for ipython -- ipythonrc format -# -# The format of this file is one of 'key value' lines. -# Lines containing only whitespace at the beginning and then a # are ignored -# as comments. But comments can NOT be put on lines with data. -#*************************************************************************** - -# This is an example of a 'profile' file which includes a base file and adds -# some customizaton for a particular purpose. - -# If this file is found in the user's ~/.ipython directory as ipythonrc-math, -# it can be loaded by calling passing the '-profile math' (or '-p math') -# option to IPython. - -# This example is a light customization to have ipython have basic math functions -# readily available, effectively making the python prompt a very capable scientific -# calculator - -# include base config and only add some extras -include ipythonrc - -# load the complex math functions but keep them in a separate namespace -import_mod cmath - -# from ... import * -# load the real math functions in the global namespace for convenience -import_all math - -# from ... import ... -import_some - -# code to execute -execute print "*** math functions available globally, cmath as a module" diff --git a/IPython/UserConfig/ipythonrc-numeric b/IPython/UserConfig/ipythonrc-numeric deleted file mode 100644 index 1700ca0..0000000 --- a/IPython/UserConfig/ipythonrc-numeric +++ /dev/null @@ -1,57 +0,0 @@ -# -*- Mode: Shell-Script -*- Not really, but shows comments correctly -#*************************************************************************** -# -# Configuration file for ipython -- ipythonrc format -# -# The format of this file is one of 'key value' lines. -# Lines containing only whitespace at the beginning and then a # are ignored -# as comments. But comments can NOT be put on lines with data. -#*************************************************************************** - -# This is an example of a 'profile' file which includes a base file and adds -# some customizaton for a particular purpose. - -# If this file is found in the user's ~/.ipython directory as -# ipythonrc-numeric, it can be loaded by calling passing the '-profile -# numeric' (or '-p numeric') option to IPython. - -# A simple alias numpy -> 'ipython -p numeric' makes life very convenient. - -# This example is meant to load several modules to turn IPython into a very -# capable environment for high-end numerical work, similar to IDL or MatLab -# but with the beauty and flexibility of the Python language. - -# Load the user's basic configuration -include ipythonrc - -# import ... - -# Load Numeric by itself so that 'help Numeric' works -import_mod Numeric - -# from ... import * -# GnuplotRuntime loads Gnuplot and adds enhancements for use in IPython -import_all Numeric IPython.numutils IPython.GnuplotInteractive - -# a simple line at zero, often useful for an x-axis -execute xaxis=gpfunc('0',title='',with='lines lt -1') - -# Below are optional things off by default. Uncomment them if desired. - -# MA (MaskedArray) modifies the Numeric printing mechanism so that huge arrays -# are only summarized and not printed (which may freeze the machine for a -# _long_ time). - -#import_mod MA - - -# gracePlot is a Python interface to the plotting package Grace. -# For more details go to: http://www.idyll.org/~n8gray/code/index.html -# Uncomment lines below if you have grace and its python support code - -#import_mod gracePlot -#execute grace = gracePlot.gracePlot # alias to make gracePlot instances -#execute print '*** grace is an alias for gracePlot.gracePlot' - -# Files to execute -execfile diff --git a/IPython/UserConfig/ipythonrc-physics b/IPython/UserConfig/ipythonrc-physics deleted file mode 100644 index c7c25a3..0000000 --- a/IPython/UserConfig/ipythonrc-physics +++ /dev/null @@ -1,45 +0,0 @@ -# -*- Mode: Shell-Script -*- Not really, but shows comments correctly -#*************************************************************************** -# -# Configuration file for ipython -- ipythonrc format -# -# The format of this file is one of 'key value' lines. -# Lines containing only whitespace at the beginning and then a # are ignored -# as comments. But comments can NOT be put on lines with data. -#*************************************************************************** - -# If this file is found in the user's ~/.ipython directory as -# ipythonrc-physics, it can be loaded by calling passing the '-profile -# physics' (or '-p physics') option to IPython. - -# This profile loads modules useful for doing interactive calculations with -# physical quantities (with units). It relies on modules from Konrad Hinsen's -# ScientificPython (http://dirac.cnrs-orleans.fr/ScientificPython/) - -# First load basic user configuration -include ipythonrc - -# import ... -# Module with alternate input syntax for PhysicalQuantity objects. -import_mod IPython.Extensions.PhysicalQInput - -# from ... import * -# math CANNOT be imported after PhysicalQInteractive. It will override the -# functions defined there. -import_all math IPython.Extensions.PhysicalQInteractive - -# from ... import ... -import_some - -# code -execute q = PhysicalQuantityInteractive -execute g = PhysicalQuantityInteractive('9.8 m/s**2') -ececute rad = pi/180. -execute print '*** q is an alias for PhysicalQuantityInteractive' -execute print '*** g = 9.8 m/s^2 has been defined' -execute print '*** rad = pi/180 has been defined' -execute import ipy_constants as C -execute print '*** C is the physical constants module' - -# Files to execute -execfile diff --git a/IPython/UserConfig/ipythonrc-pysh b/IPython/UserConfig/ipythonrc-pysh deleted file mode 100644 index 2c7d204..0000000 --- a/IPython/UserConfig/ipythonrc-pysh +++ /dev/null @@ -1,94 +0,0 @@ -# -*- Mode: Shell-Script -*- Not really, but shows comments correctly -#*************************************************************************** -# Configuration file for ipython -- ipythonrc format -# -# The format of this file is one of 'key value' lines. -# Lines containing only whitespace at the beginning and then a # are ignored -# as comments. But comments can NOT be put on lines with data. -#*************************************************************************** - -# If this file is found in the user's ~/.ipython directory as ipythonrc-pysh, -# it can be loaded by calling passing the '-profile pysh' (or '-p pysh') -# option to IPython. - -# This profile turns IPython into a lightweight system shell with python -# syntax. - -# We only set a few options here, the rest is done in the companion pysh.py -# file. In the future _all_ of IPython's configuration will be done via -# proper python code. - -############################################################################ -# First load common user configuration -include ipythonrc - -############################################################################ -# Load all the actual syntax extensions for shell-like operation, which live -# in the InterpreterExec standard extension. -import_all IPython.Extensions.InterpreterExec - -############################################################################ -# PROMPTS -# -# Configure prompt for more shell-like usage. - -# Most bash-like escapes can be used to customize IPython's prompts, as well as -# a few additional ones which are IPython-specific. All valid prompt escapes -# are described in detail in the Customization section of the IPython HTML/PDF -# manual. - -prompt_in1 '\C_LightGreen\u@\h\C_LightBlue[\C_LightCyan\Y1\C_LightBlue]\C_Green|\#> ' -prompt_in2 '\C_Green|\C_LightGreen\D\C_Green> ' -prompt_out '<\#> ' - -# Here's a more complex prompt, showing the hostname and more path depth (\Y3) -#prompt_in1 '\C_LightRed\u\C_Blue@\C_Red\h\C_LightBlue[\C_LightCyan\Y3\C_LightBlue]\C_LightGreen\#> ' - -# Select whether to left-pad the output prompts to match the length of the -# input ones. This allows you for example to use a simple '>' as an output -# prompt, and yet have the output line up with the input. If set to false, -# the output prompts will be unpadded (flush left). -prompts_pad_left 1 - - -# Remove all blank lines in between prompts, like a normal shell. -separate_in 0 -separate_out 0 -separate_out2 0 - -# Allow special syntax (!, magics and aliases) in multiline input -multi_line_specials 1 - -############################################################################ -# ALIASES - -# Declare some common aliases. Type alias? at an ipython prompt for details on -# the syntax, use @unalias to delete existing aliases. - -# Don't go too crazy here, the file pysh.py called below runs @rehash, which -# loads ALL of your $PATH as aliases (except for Python keywords and -# builtins). - -# Some examples: - -# A simple alias without arguments -#alias cl clear - -# An alias which expands the full line before the end of the alias. This -# lists only directories: -#alias ldir pwd;ls -oF --color %l | grep /$ - -# An alias with two positional arguments: -#alias parts echo 'First <%s> Second <%s>' - -# In use these two aliases give (note that ldir is already built into IPython -# for Unix): - -#fperez[IPython]16> ldir -#/usr/local/home/fperez/ipython/ipython/IPython -#drwxr-xr-x 2 fperez 4096 Jun 21 01:01 CVS/ -#drwxr-xr-x 3 fperez 4096 Jun 21 01:10 Extensions/ -#drwxr-xr-x 3 fperez 4096 Jun 21 01:27 UserConfig/ - -#fperez[IPython]17> parts Hello world and goodbye -#First Second and goodbye diff --git a/IPython/UserConfig/ipythonrc-tutorial b/IPython/UserConfig/ipythonrc-tutorial deleted file mode 100644 index 6c97569..0000000 --- a/IPython/UserConfig/ipythonrc-tutorial +++ /dev/null @@ -1,37 +0,0 @@ -# -*- Mode: Shell-Script -*- Not really, but shows comments correctly -#*************************************************************************** -# -# Configuration file for ipython -- ipythonrc format -# -# The format of this file is one of 'key value' lines. -# Lines containing only whitespace at the beginning and then a # are ignored -# as comments. But comments can NOT be put on lines with data. -#*************************************************************************** - -# If this file is found in the user's ~/.ipython directory as -# ipythonrc-tutorial, it can be loaded by calling passing the '-profile -# tutorial' (or '-p tutorial') option to IPython. - -# This profile loads a special input line filter to allow typing lines which -# begin with '>>> ' or '... '. These two strings, if present at the start of -# the input line, are stripped. This allows for direct pasting of code from -# examples such as those available in the standard Python tutorial. - -# First load basic user configuration -include ipythonrc - -# import ... -# Module with alternate input syntax for pasting python input -import_mod IPython.Extensions.InterpreterPasteInput - -# from ... import * -import_all - -# from ... import ... -import_some - -# code -execute - -# Files to execute -execfile diff --git a/IPython/__init__.py b/IPython/__init__.py index 2fb3281..a3d3030 100644 --- a/IPython/__init__.py +++ b/IPython/__init__.py @@ -1,72 +1,64 @@ -# -*- coding: utf-8 -*- +#!/usr/bin/env python +# encoding: utf-8 """ -IPython -- An enhanced Interactive Python +IPython. -One of Python's nicest features is its interactive interpreter. This allows -very fast testing of ideas without the overhead of creating test files as is -typical in most programming languages. However, the interpreter supplied with -the standard Python distribution is fairly primitive (and IDLE isn't really -much better). - -IPython tries to: - - i - provide an efficient environment for interactive work in Python - programming. It tries to address what we see as shortcomings of the standard - Python prompt, and adds many features to make interactive work much more - efficient. - - ii - offer a flexible framework so that it can be used as the base - environment for other projects and problems where Python can be the - underlying language. Specifically scientific environments like Mathematica, - IDL and Mathcad inspired its design, but similar ideas can be useful in many - fields. Python is a fabulous language for implementing this kind of system - (due to its dynamic and introspective features), and with suitable libraries - entire systems could be built leveraging Python's power. - - iii - serve as an embeddable, ready to go interpreter for your own programs. - -IPython requires Python 2.4 or newer. +IPython is a set of tools for interactive and exploratory computing in Python. """ -#***************************************************************************** -# Copyright (C) 2008-2009 The IPython Development Team -# Copyright (C) 2001-2007 Fernando Perez. +#----------------------------------------------------------------------------- +# Copyright (C) 2008-2009 The IPython Development Team # # Distributed under the terms of the BSD License. The full license is in # the file COPYING, distributed as part of this software. -#***************************************************************************** +#----------------------------------------------------------------------------- + +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- -# Enforce proper version requirements +import os import sys +from IPython.core import release + +#----------------------------------------------------------------------------- +# Setup everything +#----------------------------------------------------------------------------- + if sys.version[0:3] < '2.4': raise ImportError('Python Version 2.4 or above is required for IPython.') + # Make it easy to import extensions - they are always directly on pythonpath. -# Therefore, non-IPython modules can be added to Extensions directory -import os -sys.path.append(os.path.dirname(__file__) + "/Extensions") +# Therefore, non-IPython modules can be added to extensions directory +sys.path.append(os.path.join(os.path.dirname(__file__), "extensions")) -# Define what gets imported with a 'from IPython import *' -__all__ = ['ipapi','generics','ipstruct','Release','Shell'] +#----------------------------------------------------------------------------- +# Setup the top level names +#----------------------------------------------------------------------------- -# Load __all__ in IPython namespace so that a simple 'import IPython' gives -# access to them via IPython. -glob,loc = globals(),locals() -for name in __all__: - #print 'Importing: ',name # dbg - __import__(name,glob,loc,[]) +# In some cases, these are causing circular imports. +from IPython.core.iplib import InteractiveShell +from IPython.core.embed import embed +from IPython.core.error import TryNext -import Shell +from IPython.lib import ( + enable_wx, disable_wx, + enable_gtk, disable_gtk, + enable_qt4, disable_qt4, + enable_tk, disable_tk, + set_inputhook, clear_inputhook, + current_gui, spin, + appstart_qt4, appstart_wx, + appstart_gtk, appstart_tk +) # Release data -from IPython import Release # do it explicitly so pydoc can see it - pydoc bug -__author__ = '%s <%s>\n%s <%s>\n%s <%s>' % \ - ( Release.authors['Fernando'] + Release.authors['Janko'] + \ - Release.authors['Nathan'] ) -__license__ = Release.license -__version__ = Release.version -__revision__ = Release.revision +__author__ = '' +for author, email in release.authors.values(): + __author__ += author + ' <' + email + '>\n' +__license__ = release.license +__version__ = release.version +__revision__ = release.revision -# Namespace cleanup -del name,glob,loc diff --git a/IPython/config/api.py b/IPython/config/api.py deleted file mode 100644 index 6de3d3e..0000000 --- a/IPython/config/api.py +++ /dev/null @@ -1,102 +0,0 @@ -# encoding: utf-8 - -"""This is the official entry point to IPython's configuration system. """ - -__docformat__ = "restructuredtext en" - -#------------------------------------------------------------------------------- -# Copyright (C) 2008 The IPython Development Team -# -# Distributed under the terms of the BSD License. The full license is in -# the file COPYING, distributed as part of this software. -#------------------------------------------------------------------------------- - -#------------------------------------------------------------------------------- -# Imports -#------------------------------------------------------------------------------- - -import os -from os.path import join as pjoin - -from IPython.genutils import get_home_dir, get_ipython_dir -from IPython.external.configobj import ConfigObj - - -class ConfigObjManager(object): - - def __init__(self, configObj, filename): - self.current = configObj - self.current.indent_type = ' ' - self.filename = filename - # self.write_default_config_file() - - def get_config_obj(self): - return self.current - - def update_config_obj(self, newConfig): - self.current.merge(newConfig) - - def update_config_obj_from_file(self, filename): - newConfig = ConfigObj(filename, file_error=False) - self.current.merge(newConfig) - - def update_config_obj_from_default_file(self, ipythondir=None): - fname = self.resolve_file_path(self.filename, ipythondir) - self.update_config_obj_from_file(fname) - - def write_config_obj_to_file(self, filename): - f = open(filename, 'w') - self.current.write(f) - f.close() - - def write_default_config_file(self): - ipdir = get_ipython_dir() - fname = pjoin(ipdir, self.filename) - if not os.path.isfile(fname): - print "Writing the configuration file to: " + fname - self.write_config_obj_to_file(fname) - - def _import(self, key): - package = '.'.join(key.split('.')[0:-1]) - obj = key.split('.')[-1] - execString = 'from %s import %s' % (package, obj) - exec execString - exec 'temp = %s' % obj - return temp - - def resolve_file_path(self, filename, ipythondir = None): - """Resolve filenames into absolute paths. - - This function looks in the following directories in order: - - 1. In the current working directory or by absolute path with ~ expanded - 2. In ipythondir if that is set - 3. In the IPYTHONDIR environment variable if it exists - 4. In the ~/.ipython directory - - Note: The IPYTHONDIR is also used by the trunk version of IPython so - changing it will also affect it was well. - """ - - # In cwd or by absolute path with ~ expanded - trythis = os.path.expanduser(filename) - if os.path.isfile(trythis): - return trythis - - # In ipythondir if it is set - if ipythondir is not None: - trythis = pjoin(ipythondir, filename) - if os.path.isfile(trythis): - return trythis - - trythis = pjoin(get_ipython_dir(), filename) - if os.path.isfile(trythis): - return trythis - - return None - - - - - - diff --git a/IPython/config/cutils.py b/IPython/config/cutils.py deleted file mode 100644 index d72bcc9..0000000 --- a/IPython/config/cutils.py +++ /dev/null @@ -1,34 +0,0 @@ -# encoding: utf-8 - -"""Configuration-related utilities for all IPython.""" - -__docformat__ = "restructuredtext en" - -#------------------------------------------------------------------------------- -# Copyright (C) 2008 The IPython Development Team -# -# Distributed under the terms of the BSD License. The full license is in -# the file COPYING, distributed as part of this software. -#------------------------------------------------------------------------------- - -#------------------------------------------------------------------------------- -# Imports -#------------------------------------------------------------------------------- - -import os -import sys - -#--------------------------------------------------------------------------- -# Normal code begins -#--------------------------------------------------------------------------- - -def import_item(key): - """ - Import and return bar given the string foo.bar. - """ - package = '.'.join(key.split('.')[0:-1]) - obj = key.split('.')[-1] - execString = 'from %s import %s' % (package, obj) - exec execString - exec 'temp = %s' % obj - return temp diff --git a/IPython/UserConfig/__init__.py b/IPython/config/default/__init__.py similarity index 100% rename from IPython/UserConfig/__init__.py rename to IPython/config/default/__init__.py diff --git a/IPython/config/default/ipcluster_config.py b/IPython/config/default/ipcluster_config.py new file mode 100644 index 0000000..36aa453 --- /dev/null +++ b/IPython/config/default/ipcluster_config.py @@ -0,0 +1,184 @@ +import os + +c = get_config() + +#----------------------------------------------------------------------------- +# Select which launchers to use +#----------------------------------------------------------------------------- + +# This allows you to control what method is used to start the controller +# and engines. The following methods are currently supported: +# - Start as a regular process on localhost. +# - Start using mpiexec. +# - Start using the Windows HPC Server 2008 scheduler +# - Start using PBS +# - Start using SSH (currently broken) + + +# The selected launchers can be configured below. + +# Options are: +# - LocalControllerLauncher +# - MPIExecControllerLauncher +# - PBSControllerLauncher +# - WindowsHPCControllerLauncher +# c.Global.controller_launcher = 'IPython.kernel.launcher.LocalControllerLauncher' + +# Options are: +# - LocalEngineSetLauncher +# - MPIExecEngineSetLauncher +# - PBSEngineSetLauncher +# - WindowsHPCEngineSetLauncher +# c.Global.engine_launcher = 'IPython.kernel.launcher.LocalEngineSetLauncher' + +#----------------------------------------------------------------------------- +# Global configuration +#----------------------------------------------------------------------------- + +# The default number of engines that will be started. This is overridden by +# the -n command line option: "ipcluster start -n 4" +# c.Global.n = 2 + +# Log to a file in cluster_dir/log, otherwise just log to sys.stdout. +# c.Global.log_to_file = False + +# Remove old logs from cluster_dir/log before starting. +# c.Global.clean_logs = True + +# The working directory for the process. The application will use os.chdir +# to change to this directory before starting. +# c.Global.work_dir = os.getcwd() + + +#----------------------------------------------------------------------------- +# Local process launchers +#----------------------------------------------------------------------------- + +# The command line arguments to call the controller with. +# c.LocalControllerLauncher.controller_args = \ +# ['--log-to-file','--log-level', '40'] + +# The working directory for the controller +# c.LocalEngineSetLauncher.work_dir = u'' + +# Command line argument passed to the engines. +# c.LocalEngineSetLauncher.engine_args = ['--log-to-file','--log-level', '40'] + +#----------------------------------------------------------------------------- +# MPIExec launchers +#----------------------------------------------------------------------------- + +# The mpiexec/mpirun command to use in started the controller. +# c.MPIExecControllerLauncher.mpi_cmd = ['mpiexec'] + +# Additional arguments to pass to the actual mpiexec command. +# c.MPIExecControllerLauncher.mpi_args = [] + +# The command line argument to call the controller with. +# c.MPIExecControllerLauncher.controller_args = \ +# ['--log-to-file','--log-level', '40'] + + +# The mpiexec/mpirun command to use in started the controller. +# c.MPIExecEngineSetLauncher.mpi_cmd = ['mpiexec'] + +# Additional arguments to pass to the actual mpiexec command. +# c.MPIExecEngineSetLauncher.mpi_args = [] + +# Command line argument passed to the engines. +# c.MPIExecEngineSetLauncher.engine_args = ['--log-to-file','--log-level', '40'] + +# The default number of engines to start if not given elsewhere. +# c.MPIExecEngineSetLauncher.n = 1 + +#----------------------------------------------------------------------------- +# SSH launchers +#----------------------------------------------------------------------------- + +# Todo + + +#----------------------------------------------------------------------------- +# Unix batch (PBS) schedulers launchers +#----------------------------------------------------------------------------- + +# The command line program to use to submit a PBS job. +# c.PBSControllerLauncher.submit_command = 'qsub' + +# The command line program to use to delete a PBS job. +# c.PBSControllerLauncher.delete_command = 'qdel' + +# A regular expression that takes the output of qsub and find the job id. +# c.PBSControllerLauncher.job_id_regexp = r'\d+' + +# The batch submission script used to start the controller. This is where +# environment variables would be setup, etc. This string is interpolated using +# the Itpl module in IPython.external. Basically, you can use ${n} for the +# number of engine and ${cluster_dir} for the cluster_dir. +# c.PBSControllerLauncher.batch_template = """""" + +# The name of the instantiated batch script that will actually be used to +# submit the job. This will be written to the cluster directory. +# c.PBSControllerLauncher.batch_file_name = u'pbs_batch_script_controller' + + +# The command line program to use to submit a PBS job. +# c.PBSEngineSetLauncher.submit_command = 'qsub' + +# The command line program to use to delete a PBS job. +# c.PBSEngineSetLauncher.delete_command = 'qdel' + +# A regular expression that takes the output of qsub and find the job id. +# c.PBSEngineSetLauncher.job_id_regexp = r'\d+' + +# The batch submission script used to start the engines. This is where +# environment variables would be setup, etc. This string is interpolated using +# the Itpl module in IPython.external. Basically, you can use ${n} for the +# number of engine and ${cluster_dir} for the cluster_dir. +# c.PBSEngineSetLauncher.batch_template = """""" + +# The name of the instantiated batch script that will actually be used to +# submit the job. This will be written to the cluster directory. +# c.PBSEngineSetLauncher.batch_file_name = u'pbs_batch_script_engines' + +#----------------------------------------------------------------------------- +# Windows HPC Server 2008 launcher configuration +#----------------------------------------------------------------------------- + +# c.IPControllerJob.job_name = 'IPController' +# c.IPControllerJob.is_exclusive = False +# c.IPControllerJob.username = r'USERDOMAIN\USERNAME' +# c.IPControllerJob.priority = 'Highest' +# c.IPControllerJob.requested_nodes = '' +# c.IPControllerJob.project = 'MyProject' + +# c.IPControllerTask.task_name = 'IPController' +# c.IPControllerTask.controller_cmd = [u'ipcontroller.exe'] +# c.IPControllerTask.controller_args = ['--log-to-file', '--log-level', '40'] +# c.IPControllerTask.environment_variables = {} + +# c.WindowsHPCControllerLauncher.scheduler = 'HEADNODE' +# c.WindowsHPCControllerLauncher.job_file_name = u'ipcontroller_job.xml' + + +# c.IPEngineSetJob.job_name = 'IPEngineSet' +# c.IPEngineSetJob.is_exclusive = False +# c.IPEngineSetJob.username = r'USERDOMAIN\USERNAME' +# c.IPEngineSetJob.priority = 'Highest' +# c.IPEngineSetJob.requested_nodes = '' +# c.IPEngineSetJob.project = 'MyProject' + +# c.IPEngineTask.task_name = 'IPEngine' +# c.IPEngineTask.engine_cmd = [u'ipengine.exe'] +# c.IPEngineTask.engine_args = ['--log-to-file', '--log-level', '40'] +# c.IPEngineTask.environment_variables = {} + +# c.WindowsHPCEngineSetLauncher.scheduler = 'HEADNODE' +# c.WindowsHPCEngineSetLauncher.job_file_name = u'ipengineset_job.xml' + + + + + + + diff --git a/IPython/config/default/ipcontroller_config.py b/IPython/config/default/ipcontroller_config.py new file mode 100644 index 0000000..c1d0bce --- /dev/null +++ b/IPython/config/default/ipcontroller_config.py @@ -0,0 +1,136 @@ +from IPython.config.loader import Config + +c = get_config() + +#----------------------------------------------------------------------------- +# Global configuration +#----------------------------------------------------------------------------- + +# Basic Global config attributes + +# Start up messages are logged to stdout using the logging module. +# These all happen before the twisted reactor is started and are +# useful for debugging purposes. Can be (10=DEBUG,20=INFO,30=WARN,40=CRITICAL) +# and smaller is more verbose. +# c.Global.log_level = 20 + +# Log to a file in cluster_dir/log, otherwise just log to sys.stdout. +# c.Global.log_to_file = False + +# Remove old logs from cluster_dir/log before starting. +# c.Global.clean_logs = True + +# A list of Python statements that will be run before starting the +# controller. This is provided because occasionally certain things need to +# be imported in the controller for pickling to work. +# c.Global.import_statements = ['import math'] + +# Reuse the controller's FURL files. If False, FURL files are regenerated +# each time the controller is run. If True, they will be reused, *but*, you +# also must set the network ports by hand. If set, this will override the +# values set for the client and engine connections below. +# c.Global.reuse_furls = True + +# Enable SSL encryption on all connections to the controller. If set, this +# will override the values set for the client and engine connections below. +# c.Global.secure = True + +# The working directory for the process. The application will use os.chdir +# to change to this directory before starting. +# c.Global.work_dir = os.getcwd() + +#----------------------------------------------------------------------------- +# Configure the client services +#----------------------------------------------------------------------------- + +# Basic client service config attributes + +# The network interface the controller will listen on for client connections. +# This should be an IP address or hostname of the controller's host. The empty +# string means listen on all interfaces. +# c.FCClientServiceFactory.ip = '' + +# The TCP/IP port the controller will listen on for client connections. If 0 +# a random port will be used. If the controller's host has a firewall running +# it must allow incoming traffic on this port. +# c.FCClientServiceFactory.port = 0 + +# The client learns how to connect to the controller by looking at the +# location field embedded in the FURL. If this field is empty, all network +# interfaces that the controller is listening on will be listed. To have the +# client connect on a particular interface, list it here. +# c.FCClientServiceFactory.location = '' + +# Use SSL encryption for the client connection. +# c.FCClientServiceFactory.secure = True + +# Reuse the client FURL each time the controller is started. If set, you must +# also pick a specific network port above (FCClientServiceFactory.port). +# c.FCClientServiceFactory.reuse_furls = False + +#----------------------------------------------------------------------------- +# Configure the engine services +#----------------------------------------------------------------------------- + +# Basic config attributes for the engine services. + +# The network interface the controller will listen on for engine connections. +# This should be an IP address or hostname of the controller's host. The empty +# string means listen on all interfaces. +# c.FCEngineServiceFactory.ip = '' + +# The TCP/IP port the controller will listen on for engine connections. If 0 +# a random port will be used. If the controller's host has a firewall running +# it must allow incoming traffic on this port. +# c.FCEngineServiceFactory.port = 0 + +# The engine learns how to connect to the controller by looking at the +# location field embedded in the FURL. If this field is empty, all network +# interfaces that the controller is listening on will be listed. To have the +# client connect on a particular interface, list it here. +# c.FCEngineServiceFactory.location = '' + +# Use SSL encryption for the engine connection. +# c.FCEngineServiceFactory.secure = True + +# Reuse the client FURL each time the controller is started. If set, you must +# also pick a specific network port above (FCClientServiceFactory.port). +# c.FCEngineServiceFactory.reuse_furls = False + +#----------------------------------------------------------------------------- +# Developer level configuration attributes +#----------------------------------------------------------------------------- + +# You shouldn't have to modify anything in this section. These attributes +# are more for developers who want to change the behavior of the controller +# at a fundamental level. + +# c.FCClientServiceFactory.cert_file = u'ipcontroller-client.pem' + +# default_client_interfaces = Config() +# default_client_interfaces.Task.interface_chain = [ +# 'IPython.kernel.task.ITaskController', +# 'IPython.kernel.taskfc.IFCTaskController' +# ] +# +# default_client_interfaces.Task.furl_file = u'ipcontroller-tc.furl' +# +# default_client_interfaces.MultiEngine.interface_chain = [ +# 'IPython.kernel.multiengine.IMultiEngine', +# 'IPython.kernel.multienginefc.IFCSynchronousMultiEngine' +# ] +# +# default_client_interfaces.MultiEngine.furl_file = u'ipcontroller-mec.furl' +# +# c.FCEngineServiceFactory.interfaces = default_client_interfaces + +# c.FCEngineServiceFactory.cert_file = u'ipcontroller-engine.pem' + +# default_engine_interfaces = Config() +# default_engine_interfaces.Default.interface_chain = [ +# 'IPython.kernel.enginefc.IFCControllerBase' +# ] +# +# default_engine_interfaces.Default.furl_file = u'ipcontroller-engine.furl' +# +# c.FCEngineServiceFactory.interfaces = default_engine_interfaces diff --git a/IPython/config/default/ipengine_config.py b/IPython/config/default/ipengine_config.py new file mode 100644 index 0000000..42483ed --- /dev/null +++ b/IPython/config/default/ipengine_config.py @@ -0,0 +1,90 @@ +c = get_config() + +#----------------------------------------------------------------------------- +# Global configuration +#----------------------------------------------------------------------------- + +# Start up messages are logged to stdout using the logging module. +# These all happen before the twisted reactor is started and are +# useful for debugging purposes. Can be (10=DEBUG,20=INFO,30=WARN,40=CRITICAL) +# and smaller is more verbose. +# c.Global.log_level = 20 + +# Log to a file in cluster_dir/log, otherwise just log to sys.stdout. +# c.Global.log_to_file = False + +# Remove old logs from cluster_dir/log before starting. +# c.Global.clean_logs = True + +# A list of strings that will be executed in the users namespace on the engine +# before it connects to the controller. +# c.Global.exec_lines = ['import numpy'] + +# The engine will try to connect to the controller multiple times, to allow +# the controller time to startup and write its FURL file. These parameters +# control the number of retries (connect_max_tries) and the initial delay +# (connect_delay) between attemps. The actual delay between attempts gets +# longer each time by a factor of 1.5 (delay[i] = 1.5*delay[i-1]) +# those attemps. +# c.Global.connect_delay = 0.1 +# c.Global.connect_max_tries = 15 + +# By default, the engine will look for the controller's FURL file in its own +# cluster directory. Sometimes, the FURL file will be elsewhere and this +# attribute can be set to the full path of the FURL file. +# c.Global.furl_file = u'' + +# The working directory for the process. The application will use os.chdir +# to change to this directory before starting. +# c.Global.work_dir = os.getcwd() + +#----------------------------------------------------------------------------- +# MPI configuration +#----------------------------------------------------------------------------- + +# Upon starting the engine can be configured to call MPI_Init. This section +# configures that. + +# Select which MPI section to execute to setup MPI. The value of this +# attribute must match the name of another attribute in the MPI config +# section (mpi4py, pytrilinos, etc.). This can also be set by the --mpi +# command line option. +# c.MPI.use = '' + +# Initialize MPI using mpi4py. To use this, set c.MPI.use = 'mpi4py' to use +# --mpi=mpi4py at the command line. +# c.MPI.mpi4py = """from mpi4py import MPI as mpi +# mpi.size = mpi.COMM_WORLD.Get_size() +# mpi.rank = mpi.COMM_WORLD.Get_rank() +# """ + +# Initialize MPI using pytrilinos. To use this, set c.MPI.use = 'pytrilinos' +# to use --mpi=pytrilinos at the command line. +# c.MPI.pytrilinos = """from PyTrilinos import Epetra +# class SimpleStruct: +# pass +# mpi = SimpleStruct() +# mpi.rank = 0 +# mpi.size = 0 +# """ + +#----------------------------------------------------------------------------- +# Developer level configuration attributes +#----------------------------------------------------------------------------- + +# You shouldn't have to modify anything in this section. These attributes +# are more for developers who want to change the behavior of the controller +# at a fundamental level. + +# You should not have to change these attributes. + +# c.Global.shell_class = 'IPython.kernel.core.interpreter.Interpreter' + +# c.Global.furl_file_name = u'ipcontroller-engine.furl' + + + + + + + diff --git a/IPython/config/default/ipython_config.py b/IPython/config/default/ipython_config.py new file mode 100644 index 0000000..a52d8de --- /dev/null +++ b/IPython/config/default/ipython_config.py @@ -0,0 +1,148 @@ +# Get the config being loaded so we can set attributes on it +c = get_config() + +#----------------------------------------------------------------------------- +# Global options +#----------------------------------------------------------------------------- + +# c.Global.display_banner = True + +# c.Global.classic = False + +# c.Global.nosep = True + +# Set this to determine the detail of what is logged at startup. +# The default is 30 and possible values are 0,10,20,30,40,50. +# c.Global.log_level = 20 + +# This should be a list of importable Python modules that have an +# load_in_ipython(ip) method. This method gets called when the extension +# is loaded. You can put your extensions anywhere they can be imported +# but we add the extensions subdir of the ipython directory to sys.path +# during extension loading, so you can put them there as well. +# c.Global.extensions = [ +# 'myextension' +# ] + +# These lines are run in IPython in the user's namespace after extensions +# are loaded. They can contain full IPython syntax with magics etc. +# c.Global.exec_lines = [ +# 'import numpy', +# 'a = 10; b = 20', +# '1/0' +# ] + +# These files are run in IPython in the user's namespace. Files with a .py +# extension need to be pure Python. Files with a .ipy extension can have +# custom IPython syntax (like magics, etc.). +# These files need to be in the cwd, the ipython_dir or be absolute paths. +# c.Global.exec_files = [ +# 'mycode.py', +# 'fancy.ipy' +# ] + +#----------------------------------------------------------------------------- +# InteractiveShell options +#----------------------------------------------------------------------------- + +# c.InteractiveShell.autocall = 1 + +# c.InteractiveShell.autoedit_syntax = False + +# c.InteractiveShell.autoindent = True + +# c.InteractiveShell.automagic = False + +# c.InteractiveShell.banner1 = 'This if for overriding the default IPython banner' + +# c.InteractiveShell.banner2 = "This is for extra banner text" + +# c.InteractiveShell.cache_size = 1000 + +# c.InteractiveShell.colors = 'LightBG' + +# c.InteractiveShell.color_info = True + +# c.InteractiveShell.confirm_exit = True + +# c.InteractiveShell.deep_reload = False + +# c.InteractiveShell.editor = 'nano' + +# c.InteractiveShell.logstart = True + +# c.InteractiveShell.logfile = u'ipython_log.py' + +# c.InteractiveShell.logappend = u'mylog.py' + +# c.InteractiveShell.object_info_string_level = 0 + +# c.InteractiveShell.pager = 'less' + +# c.InteractiveShell.pdb = False + +# c.InteractiveShell.pprint = True + +# c.InteractiveShell.prompt_in1 = 'In [\#]: ' +# c.InteractiveShell.prompt_in2 = ' .\D.: ' +# c.InteractiveShell.prompt_out = 'Out[\#]: ' +# c.InteractiveShell.prompts_pad_left = True + +# c.InteractiveShell.quiet = False + +# Readline +# c.InteractiveShell.readline_use = True + +# c.InteractiveShell.readline_parse_and_bind = [ +# 'tab: complete', +# '"\C-l": possible-completions', +# 'set show-all-if-ambiguous on', +# '"\C-o": tab-insert', +# '"\M-i": " "', +# '"\M-o": "\d\d\d\d"', +# '"\M-I": "\d\d\d\d"', +# '"\C-r": reverse-search-history', +# '"\C-s": forward-search-history', +# '"\C-p": history-search-backward', +# '"\C-n": history-search-forward', +# '"\e[A": history-search-backward', +# '"\e[B": history-search-forward', +# '"\C-k": kill-line', +# '"\C-u": unix-line-discard', +# ] +# c.InteractiveShell.readline_remove_delims = '-/~' +# c.InteractiveShell.readline_merge_completions = True +# c.InteractiveShell.readline_omit_names = 0 + +# c.InteractiveShell.screen_length = 0 + +# c.InteractiveShell.separate_in = '\n' +# c.InteractiveShell.separate_out = '' +# c.InteractiveShell.separate_out2 = '' + +# c.InteractiveShell.system_header = "IPython system call: " + +# c.InteractiveShell.system_verbose = True + +# c.InteractiveShell.term_title = False + +# c.InteractiveShell.wildcards_case_sensitive = True + +# c.InteractiveShell.xmode = 'Context' + +#----------------------------------------------------------------------------- +# PrefilterManager options +#----------------------------------------------------------------------------- + +# c.PrefilterManager.multi_line_specials = True + +#----------------------------------------------------------------------------- +# AliasManager options +#----------------------------------------------------------------------------- + +# Do this to disable all defaults +# c.AliasManager.default_aliases = [] + +# c.AliasManager.user_aliases = [ +# ('foo', 'echo Hi') +# ] \ No newline at end of file diff --git a/IPython/config/default/kernel_config.py b/IPython/config/default/kernel_config.py new file mode 100644 index 0000000..0eec0dd --- /dev/null +++ b/IPython/config/default/kernel_config.py @@ -0,0 +1,62 @@ +from os.path import join +pjoin = join + +from IPython.utils.genutils import get_ipython_dir, get_security_dir +security_dir = get_security_dir() + + +ENGINE_LOGFILE = '' + +ENGINE_FURL_FILE = 'ipcontroller-engine.furl' + +MPI_CONFIG_MPI4PY = """from mpi4py import MPI as mpi +mpi.size = mpi.COMM_WORLD.Get_size() +mpi.rank = mpi.COMM_WORLD.Get_rank() +""" + +MPI_CONFIG_PYTRILINOS = """from PyTrilinos import Epetra +class SimpleStruct: +pass +mpi = SimpleStruct() +mpi.rank = 0 +mpi.size = 0 +""" + +MPI_DEFAULT = '' + +CONTROLLER_LOGFILE = '' +CONTROLLER_IMPORT_STATEMENT = '' +CONTROLLER_REUSE_FURLS = False + +ENGINE_TUB_IP = '' +ENGINE_TUB_PORT = 0 +ENGINE_TUB_LOCATION = '' +ENGINE_TUB_SECURE = True +ENGINE_TUB_CERT_FILE = 'ipcontroller-engine.pem' +ENGINE_FC_INTERFACE = 'IPython.kernel.enginefc.IFCControllerBase' +ENGINE_FURL_FILE = 'ipcontroller-engine.furl' + +CONTROLLER_INTERFACES = dict( + TASK = dict( + CONTROLLER_INTERFACE = 'IPython.kernel.task.ITaskController', + FC_INTERFACE = 'IPython.kernel.taskfc.IFCTaskController', + FURL_FILE = pjoin(security_dir, 'ipcontroller-tc.furl') + ), + MULTIENGINE = dict( + CONTROLLER_INTERFACE = 'IPython.kernel.multiengine.IMultiEngine', + FC_INTERFACE = 'IPython.kernel.multienginefc.IFCSynchronousMultiEngine', + FURL_FILE = pjoin(security_dir, 'ipcontroller-mec.furl') + ) +) + +CLIENT_TUB_IP = '' +CLIENT_TUB_PORT = 0 +CLIENT_TUB_LOCATION = '' +CLIENT_TUB_SECURE = True +CLIENT_TUB_CERT_FILE = 'ipcontroller-client.pem' + +CLIENT_INTERFACES = dict( + TASK = dict(FURL_FILE = 'ipcontroller-tc.furl'), + MULTIENGINE = dict(FURLFILE='ipcontroller-mec.furl') +) + diff --git a/IPython/config/loader.py b/IPython/config/loader.py new file mode 100644 index 0000000..e8bb620 --- /dev/null +++ b/IPython/config/loader.py @@ -0,0 +1,336 @@ +#!/usr/bin/env python +# encoding: utf-8 +"""A simple configuration system. + +Authors: + +* Brian Granger +""" + +#----------------------------------------------------------------------------- +# Copyright (C) 2008-2009 The IPython Development Team +# +# Distributed under the terms of the BSD License. The full license is in +# the file COPYING, distributed as part of this software. +#----------------------------------------------------------------------------- + +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- + +import __builtin__ +import os +import sys + +from IPython.external import argparse +from IPython.utils.genutils import filefind + +#----------------------------------------------------------------------------- +# Exceptions +#----------------------------------------------------------------------------- + + +class ConfigError(Exception): + pass + + +class ConfigLoaderError(ConfigError): + pass + + +#----------------------------------------------------------------------------- +# Config class for holding config information +#----------------------------------------------------------------------------- + + +class Config(dict): + """An attribute based dict that can do smart merges.""" + + def __init__(self, *args, **kwds): + dict.__init__(self, *args, **kwds) + # This sets self.__dict__ = self, but it has to be done this way + # because we are also overriding __setattr__. + dict.__setattr__(self, '__dict__', self) + + def _merge(self, other): + to_update = {} + for k, v in other.items(): + if not self.has_key(k): + to_update[k] = v + else: # I have this key + if isinstance(v, Config): + # Recursively merge common sub Configs + self[k]._merge(v) + else: + # Plain updates for non-Configs + to_update[k] = v + + self.update(to_update) + + def _is_section_key(self, key): + if key[0].upper()==key[0] and not key.startswith('_'): + return True + else: + return False + + def has_key(self, key): + if self._is_section_key(key): + return True + else: + return dict.has_key(self, key) + + def _has_section(self, key): + if self._is_section_key(key): + if dict.has_key(self, key): + return True + return False + + def copy(self): + return type(self)(dict.copy(self)) + + def __copy__(self): + return self.copy() + + def __deepcopy__(self, memo): + import copy + return type(self)(copy.deepcopy(self.items())) + + def __getitem__(self, key): + # Because we use this for an exec namespace, we need to delegate + # the lookup of names in __builtin__ to itself. This means + # that you can't have section or attribute names that are + # builtins. + try: + return getattr(__builtin__, key) + except AttributeError: + pass + if self._is_section_key(key): + try: + return dict.__getitem__(self, key) + except KeyError: + c = Config() + dict.__setitem__(self, key, c) + return c + else: + return dict.__getitem__(self, key) + + def __setitem__(self, key, value): + # Don't allow names in __builtin__ to be modified. + if hasattr(__builtin__, key): + raise ConfigError('Config variable names cannot have the same name ' + 'as a Python builtin: %s' % key) + if self._is_section_key(key): + if not isinstance(value, Config): + raise ValueError('values whose keys begin with an uppercase ' + 'char must be Config instances: %r, %r' % (key, value)) + else: + dict.__setitem__(self, key, value) + + def __getattr__(self, key): + try: + return self.__getitem__(key) + except KeyError, e: + raise AttributeError(e) + + def __setattr__(self, key, value): + try: + self.__setitem__(key, value) + except KeyError, e: + raise AttributeError(e) + + def __delattr__(self, key): + try: + dict.__delitem__(self, key) + except KeyError, e: + raise AttributeError(e) + + +#----------------------------------------------------------------------------- +# Config loading classes +#----------------------------------------------------------------------------- + + +class ConfigLoader(object): + """A object for loading configurations from just about anywhere. + + The resulting configuration is packaged as a :class:`Struct`. + + Notes + ----- + A :class:`ConfigLoader` does one thing: load a config from a source + (file, command line arguments) and returns the data as a :class:`Struct`. + There are lots of things that :class:`ConfigLoader` does not do. It does + not implement complex logic for finding config files. It does not handle + default values or merge multiple configs. These things need to be + handled elsewhere. + """ + + def __init__(self): + """A base class for config loaders. + + Examples + -------- + + >>> cl = ConfigLoader() + >>> config = cl.load_config() + >>> config + {} + """ + self.clear() + + def clear(self): + self.config = Config() + + def load_config(self): + """Load a config from somewhere, return a Struct. + + Usually, this will cause self.config to be set and then returned. + """ + return self.config + + +class FileConfigLoader(ConfigLoader): + """A base class for file based configurations. + + As we add more file based config loaders, the common logic should go + here. + """ + pass + + +class PyFileConfigLoader(FileConfigLoader): + """A config loader for pure python files. + + This calls execfile on a plain python file and looks for attributes + that are all caps. These attribute are added to the config Struct. + """ + + def __init__(self, filename, path=None): + """Build a config loader for a filename and path. + + Parameters + ---------- + filename : str + The file name of the config file. + path : str, list, tuple + The path to search for the config file on, or a sequence of + paths to try in order. + """ + super(PyFileConfigLoader, self).__init__() + self.filename = filename + self.path = path + self.full_filename = '' + self.data = None + + def load_config(self): + """Load the config from a file and return it as a Struct.""" + self._find_file() + self._read_file_as_dict() + self._convert_to_config() + return self.config + + def _find_file(self): + """Try to find the file by searching the paths.""" + self.full_filename = filefind(self.filename, self.path) + + def _read_file_as_dict(self): + """Load the config file into self.config, with recursive loading.""" + # This closure is made available in the namespace that is used + # to exec the config file. This allows users to call + # load_subconfig('myconfig.py') to load config files recursively. + # It needs to be a closure because it has references to self.path + # and self.config. The sub-config is loaded with the same path + # as the parent, but it uses an empty config which is then merged + # with the parents. + def load_subconfig(fname): + loader = PyFileConfigLoader(fname, self.path) + try: + sub_config = loader.load_config() + except IOError: + # Pass silently if the sub config is not there. This happens + # when a user us using a profile, but not the default config. + pass + else: + self.config._merge(sub_config) + + # Again, this needs to be a closure and should be used in config + # files to get the config being loaded. + def get_config(): + return self.config + + namespace = dict(load_subconfig=load_subconfig, get_config=get_config) + execfile(self.full_filename, namespace) + + def _convert_to_config(self): + if self.data is None: + ConfigLoaderError('self.data does not exist') + + +class CommandLineConfigLoader(ConfigLoader): + """A config loader for command line arguments. + + As we add more command line based loaders, the common logic should go + here. + """ + + +class NoConfigDefault(object): pass +NoConfigDefault = NoConfigDefault() + + +class ArgParseConfigLoader(CommandLineConfigLoader): + + # arguments = [(('-f','--file'),dict(type=str,dest='file'))] + arguments = () + + def __init__(self, *args, **kw): + """Create a config loader for use with argparse. + + The args and kwargs arguments here are passed onto the constructor + of :class:`argparse.ArgumentParser`. + """ + super(CommandLineConfigLoader, self).__init__() + self.args = args + self.kw = kw + + def load_config(self, args=None): + """Parse command line arguments and return as a Struct.""" + self._create_parser() + self._parse_args(args) + self._convert_to_config() + return self.config + + def get_extra_args(self): + if hasattr(self, 'extra_args'): + return self.extra_args + else: + return [] + + def _create_parser(self): + self.parser = argparse.ArgumentParser(*self.args, **self.kw) + self._add_arguments() + self._add_other_arguments() + + def _add_other_arguments(self): + pass + + def _add_arguments(self): + for argument in self.arguments: + if not argument[1].has_key('default'): + argument[1]['default'] = NoConfigDefault + self.parser.add_argument(*argument[0],**argument[1]) + + def _parse_args(self, args=None): + """self.parser->self.parsed_data""" + if args is None: + self.parsed_data, self.extra_args = self.parser.parse_known_args() + else: + self.parsed_data, self.extra_args = self.parser.parse_known_args(args) + + def _convert_to_config(self): + """self.parsed_data->self.config""" + for k, v in vars(self.parsed_data).items(): + if v is not NoConfigDefault: + exec_str = 'self.config.' + k + '= v' + exec exec_str in locals(), globals() + diff --git a/IPython/tests/__init__.py b/IPython/config/profile/__init__.py similarity index 100% rename from IPython/tests/__init__.py rename to IPython/config/profile/__init__.py diff --git a/IPython/config/profile/ipython_config_cluster.py b/IPython/config/profile/ipython_config_cluster.py new file mode 100644 index 0000000..fd795a4 --- /dev/null +++ b/IPython/config/profile/ipython_config_cluster.py @@ -0,0 +1,24 @@ +c = get_config() + +# This can be used at any point in a config file to load a sub config +# and merge it into the current one. +load_subconfig('ipython_config.py') + +lines = """ +from IPython.kernel.client import * +""" + +# You have to make sure that attributes that are containers already +# exist before using them. Simple assigning a new list will override +# all previous values. +if hasattr(c.Global, 'exec_lines'): + c.Global.exec_lines.append(lines) +else: + c.Global.exec_lines = [lines] + +# Load the parallelmagic extension to enable %result, %px, %autopx magics. +if hasattr(c.Global, 'extensions'): + c.Global.extensions.append('parallelmagic') +else: + c.Global.extensions = ['parallelmagic'] + diff --git a/IPython/config/profile/ipython_config_math.py b/IPython/config/profile/ipython_config_math.py new file mode 100644 index 0000000..49a0933 --- /dev/null +++ b/IPython/config/profile/ipython_config_math.py @@ -0,0 +1,19 @@ +c = get_config() + +# This can be used at any point in a config file to load a sub config +# and merge it into the current one. +load_subconfig('ipython_config.py') + +lines = """ +import cmath +from math import * +""" + +# You have to make sure that attributes that are containers already +# exist before using them. Simple assigning a new list will override +# all previous values. +if hasattr(c.Global, 'exec_lines'): + c.Global.exec_lines.append(lines) +else: + c.Global.exec_lines = [lines] + diff --git a/IPython/config/profile/ipython_config_numeric.py b/IPython/config/profile/ipython_config_numeric.py new file mode 100644 index 0000000..9f7afa8 --- /dev/null +++ b/IPython/config/profile/ipython_config_numeric.py @@ -0,0 +1,20 @@ +c = get_config() + +# This can be used at any point in a config file to load a sub config +# and merge it into the current one. +load_subconfig('ipython_config.py') + +lines = """ +import numpy +import scipy +import numpy as np +import scipy as sp +""" + +# You have to make sure that attributes that are containers already +# exist before using them. Simple assigning a new list will override +# all previous values. +if hasattr(c.Global, 'exec_lines'): + c.Global.exec_lines.append(lines) +else: + c.Global.exec_lines = [lines] \ No newline at end of file diff --git a/IPython/config/profile/ipython_config_pylab.py b/IPython/config/profile/ipython_config_pylab.py new file mode 100644 index 0000000..9885ac7 --- /dev/null +++ b/IPython/config/profile/ipython_config_pylab.py @@ -0,0 +1,22 @@ +c = get_config() + +# This can be used at any point in a config file to load a sub config +# and merge it into the current one. +load_subconfig('ipython_config.py') + +lines = """ +import matplotlib +%gui -a wx +matplotlib.use('wxagg') +matplotlib.interactive(True) +from matplotlib import pyplot as plt +from matplotlib.pyplot import * +""" + +# You have to make sure that attributes that are containers already +# exist before using them. Simple assigning a new list will override +# all previous values. +if hasattr(c.Global, 'exec_lines'): + c.Global.exec_lines.append(lines) +else: + c.Global.exec_lines = [lines] \ No newline at end of file diff --git a/IPython/config/profile/ipython_config_pysh.py b/IPython/config/profile/ipython_config_pysh.py new file mode 100644 index 0000000..ba20354 --- /dev/null +++ b/IPython/config/profile/ipython_config_pysh.py @@ -0,0 +1,29 @@ +c = get_config() + +# This can be used at any point in a config file to load a sub config +# and merge it into the current one. +load_subconfig('ipython_config.py') + +c.InteractiveShell.prompt_in1 = '\C_LightGreen\u@\h\C_LightBlue[\C_LightCyan\Y1\C_LightBlue]\C_Green|\#> ' +c.InteractiveShell.prompt_in2 = '\C_Green|\C_LightGreen\D\C_Green> ' +c.InteractiveShell.prompt_out = '<\#> ' + +c.InteractiveShell.prompts_pad_left = True + +c.InteractiveShell.separate_in = '' +c.InteractiveShell.separate_out = '' +c.InteractiveShell.separate_out2 = '' + +c.PrefilterManager.multi_line_specials = True + +lines = """ +%rehashx +""" + +# You have to make sure that attributes that are containers already +# exist before using them. Simple assigning a new list will override +# all previous values. +if hasattr(c.Global, 'exec_lines'): + c.Global.exec_lines.append(lines) +else: + c.Global.exec_lines = [lines] \ No newline at end of file diff --git a/IPython/config/profile/ipython_config_sympy.py b/IPython/config/profile/ipython_config_sympy.py new file mode 100644 index 0000000..a32b522 --- /dev/null +++ b/IPython/config/profile/ipython_config_sympy.py @@ -0,0 +1,21 @@ +c = get_config() + +# This can be used at any point in a config file to load a sub config +# and merge it into the current one. +load_subconfig('ipython_config.py') + +lines = """ +from __future__ import division +from sympy import * +x, y, z = symbols('xyz') +k, m, n = symbols('kmn', integer=True) +f, g, h = map(Function, 'fgh') +""" + +# You have to make sure that attributes that are containers already +# exist before using them. Simple assigning a new list will override +# all previous values. +if hasattr(c.Global, 'exec_lines'): + c.Global.exec_lines.append(lines) +else: + c.Global.exec_lines = [lines] diff --git a/IPython/config/tests/test_imports.py b/IPython/config/tests/test_imports.py new file mode 100644 index 0000000..84fb531 --- /dev/null +++ b/IPython/config/tests/test_imports.py @@ -0,0 +1,5 @@ +#!/usr/bin/env python +# encoding: utf-8 + + + diff --git a/IPython/config/tests/test_loader.py b/IPython/config/tests/test_loader.py new file mode 100644 index 0000000..23e1890 --- /dev/null +++ b/IPython/config/tests/test_loader.py @@ -0,0 +1,163 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +Tests for IPython.config.loader + +Authors: + +* Brian Granger +* Fernando Perez (design help) +""" + +#----------------------------------------------------------------------------- +# Copyright (C) 2008-2009 The IPython Development Team +# +# Distributed under the terms of the BSD License. The full license is in +# the file COPYING, distributed as part of this software. +#----------------------------------------------------------------------------- + +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- + +import os +from tempfile import mkstemp +from unittest import TestCase + +from IPython.config.loader import ( + Config, + PyFileConfigLoader, + ArgParseConfigLoader, + ConfigError +) + +#----------------------------------------------------------------------------- +# Actual tests +#----------------------------------------------------------------------------- + + +pyfile = """ +a = 10 +b = 20 +Foo.Bar.value = 10 +Foo.Bam.value = range(10) +D.C.value = 'hi there' +""" + +class TestPyFileCL(TestCase): + + def test_basic(self): + fd, fname = mkstemp() + f = os.fdopen(fd, 'w') + f.write(pyfile) + f.close() + # Unlink the file + cl = PyFileConfigLoader(fname) + config = cl.load_config() + self.assertEquals(config.a, 10) + self.assertEquals(config.b, 20) + self.assertEquals(config.Foo.Bar.value, 10) + self.assertEquals(config.Foo.Bam.value, range(10)) + self.assertEquals(config.D.C.value, 'hi there') + + +class TestArgParseCL(TestCase): + + def test_basic(self): + + class MyLoader(ArgParseConfigLoader): + arguments = ( + (('-f','--foo'), dict(dest='Global.foo', type=str)), + (('-b',), dict(dest='MyClass.bar', type=int)), + (('-n',), dict(dest='n', action='store_true')), + (('Global.bam',), dict(type=str)) + ) + + cl = MyLoader() + config = cl.load_config('-f hi -b 10 -n wow'.split()) + self.assertEquals(config.Global.foo, 'hi') + self.assertEquals(config.MyClass.bar, 10) + self.assertEquals(config.n, True) + self.assertEquals(config.Global.bam, 'wow') + + def test_add_arguments(self): + + class MyLoader(ArgParseConfigLoader): + def _add_arguments(self): + subparsers = self.parser.add_subparsers(dest='subparser_name') + subparser1 = subparsers.add_parser('1') + subparser1.add_argument('-x',dest='Global.x') + subparser2 = subparsers.add_parser('2') + subparser2.add_argument('y') + + cl = MyLoader() + config = cl.load_config('2 frobble'.split()) + self.assertEquals(config.subparser_name, '2') + self.assertEquals(config.y, 'frobble') + config = cl.load_config('1 -x frobble'.split()) + self.assertEquals(config.subparser_name, '1') + self.assertEquals(config.Global.x, 'frobble') + +class TestConfig(TestCase): + + def test_setget(self): + c = Config() + c.a = 10 + self.assertEquals(c.a, 10) + self.assertEquals(c.has_key('b'), False) + + def test_auto_section(self): + c = Config() + self.assertEquals(c.has_key('A'), True) + self.assertEquals(c._has_section('A'), False) + A = c.A + A.foo = 'hi there' + self.assertEquals(c._has_section('A'), True) + self.assertEquals(c.A.foo, 'hi there') + del c.A + self.assertEquals(len(c.A.keys()),0) + + def test_merge_doesnt_exist(self): + c1 = Config() + c2 = Config() + c2.bar = 10 + c2.Foo.bar = 10 + c1._merge(c2) + self.assertEquals(c1.Foo.bar, 10) + self.assertEquals(c1.bar, 10) + c2.Bar.bar = 10 + c1._merge(c2) + self.assertEquals(c1.Bar.bar, 10) + + def test_merge_exists(self): + c1 = Config() + c2 = Config() + c1.Foo.bar = 10 + c1.Foo.bam = 30 + c2.Foo.bar = 20 + c2.Foo.wow = 40 + c1._merge(c2) + self.assertEquals(c1.Foo.bam, 30) + self.assertEquals(c1.Foo.bar, 20) + self.assertEquals(c1.Foo.wow, 40) + c2.Foo.Bam.bam = 10 + c1._merge(c2) + self.assertEquals(c1.Foo.Bam.bam, 10) + + def test_deepcopy(self): + c1 = Config() + c1.Foo.bar = 10 + c1.Foo.bam = 30 + c1.a = 'asdf' + c1.b = range(10) + import copy + c2 = copy.deepcopy(c1) + self.assertEquals(c1, c2) + self.assert_(c1 is not c2) + self.assert_(c1.Foo is not c2.Foo) + + def test_builtin(self): + c1 = Config() + exec 'foo = True' in c1 + self.assertEquals(c1.foo, True) + self.assertRaises(ConfigError, setattr, c1, 'ValueError', 10) diff --git a/IPython/tools/__init__.py b/IPython/core/__init__.py similarity index 100% rename from IPython/tools/__init__.py rename to IPython/core/__init__.py diff --git a/IPython/core/alias.py b/IPython/core/alias.py new file mode 100644 index 0000000..3dbb8cd --- /dev/null +++ b/IPython/core/alias.py @@ -0,0 +1,262 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +IPython's alias component + +Authors: + +* Brian Granger +""" + +#----------------------------------------------------------------------------- +# Copyright (C) 2008-2009 The IPython Development Team +# +# Distributed under the terms of the BSD License. The full license is in +# the file COPYING, distributed as part of this software. +#----------------------------------------------------------------------------- + +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- + +import __builtin__ +import keyword +import os +import re +import sys + +from IPython.core.component import Component +from IPython.core.splitinput import split_user_input + +from IPython.utils.traitlets import CBool, List, Instance +from IPython.utils.genutils import error +from IPython.utils.autoattr import auto_attr + +#----------------------------------------------------------------------------- +# Utilities +#----------------------------------------------------------------------------- + +# This is used as the pattern for calls to split_user_input. +shell_line_split = re.compile(r'^(\s*)(\S*\s*)(.*$)') + +def default_aliases(): + # Make some aliases automatically + # Prepare list of shell aliases to auto-define + if os.name == 'posix': + default_aliases = ('mkdir mkdir', 'rmdir rmdir', + 'mv mv -i','rm rm -i','cp cp -i', + 'cat cat','less less','clear clear', + # a better ls + 'ls ls -F', + # long ls + 'll ls -lF') + # Extra ls aliases with color, which need special treatment on BSD + # variants + ls_extra = ( # color ls + 'lc ls -F -o --color', + # ls normal files only + 'lf ls -F -o --color %l | grep ^-', + # ls symbolic links + 'lk ls -F -o --color %l | grep ^l', + # directories or links to directories, + 'ldir ls -F -o --color %l | grep /$', + # things which are executable + 'lx ls -F -o --color %l | grep ^-..x', + ) + # The BSDs don't ship GNU ls, so they don't understand the + # --color switch out of the box + if 'bsd' in sys.platform: + ls_extra = ( # ls normal files only + 'lf ls -lF | grep ^-', + # ls symbolic links + 'lk ls -lF | grep ^l', + # directories or links to directories, + 'ldir ls -lF | grep /$', + # things which are executable + 'lx ls -lF | grep ^-..x', + ) + default_aliases = default_aliases + ls_extra + elif os.name in ['nt','dos']: + default_aliases = ('ls dir /on', + 'ddir dir /ad /on', 'ldir dir /ad /on', + 'mkdir mkdir','rmdir rmdir','echo echo', + 'ren ren','cls cls','copy copy') + else: + default_aliases = () + return [s.split(None,1) for s in default_aliases] + + +class AliasError(Exception): + pass + + +class InvalidAliasError(AliasError): + pass + + +#----------------------------------------------------------------------------- +# Main AliasManager class +#----------------------------------------------------------------------------- + + +class AliasManager(Component): + + default_aliases = List(default_aliases(), config=True) + user_aliases = List(default_value=[], config=True) + + def __init__(self, parent, config=None): + super(AliasManager, self).__init__(parent, config=config) + self.alias_table = {} + self.exclude_aliases() + self.init_aliases() + + @auto_attr + def shell(self): + return Component.get_instances( + root=self.root, + klass='IPython.core.iplib.InteractiveShell')[0] + + def __contains__(self, name): + if name in self.alias_table: + return True + else: + return False + + @property + def aliases(self): + return [(item[0], item[1][1]) for item in self.alias_table.iteritems()] + + def exclude_aliases(self): + # set of things NOT to alias (keywords, builtins and some magics) + no_alias = set(['cd','popd','pushd','dhist','alias','unalias']) + no_alias.update(set(keyword.kwlist)) + no_alias.update(set(__builtin__.__dict__.keys())) + self.no_alias = no_alias + + def init_aliases(self): + # Load default aliases + for name, cmd in self.default_aliases: + self.soft_define_alias(name, cmd) + + # Load user aliases + for name, cmd in self.user_aliases: + self.soft_define_alias(name, cmd) + + def clear_aliases(self): + self.alias_table.clear() + + def soft_define_alias(self, name, cmd): + """Define an alias, but don't raise on an AliasError.""" + try: + self.define_alias(name, cmd) + except AliasError, e: + error("Invalid alias: %s" % e) + + def define_alias(self, name, cmd): + """Define a new alias after validating it. + + This will raise an :exc:`AliasError` if there are validation + problems. + """ + nargs = self.validate_alias(name, cmd) + self.alias_table[name] = (nargs, cmd) + + def undefine_alias(self, name): + if self.alias_table.has_key(name): + del self.alias_table[name] + + def validate_alias(self, name, cmd): + """Validate an alias and return the its number of arguments.""" + if name in self.no_alias: + raise InvalidAliasError("The name %s can't be aliased " + "because it is a keyword or builtin." % name) + if not (isinstance(cmd, basestring)): + raise InvalidAliasError("An alias command must be a string, " + "got: %r" % name) + nargs = cmd.count('%s') + if nargs>0 and cmd.find('%l')>=0: + raise InvalidAliasError('The %s and %l specifiers are mutually ' + 'exclusive in alias definitions.') + return nargs + + def call_alias(self, alias, rest=''): + """Call an alias given its name and the rest of the line.""" + cmd = self.transform_alias(alias, rest) + try: + self.shell.system(cmd) + except: + self.shell.showtraceback() + + def transform_alias(self, alias,rest=''): + """Transform alias to system command string.""" + nargs, cmd = self.alias_table[alias] + + if ' ' in cmd and os.path.isfile(cmd): + cmd = '"%s"' % cmd + + # Expand the %l special to be the user's input line + if cmd.find('%l') >= 0: + cmd = cmd.replace('%l', rest) + rest = '' + if nargs==0: + # Simple, argument-less aliases + cmd = '%s %s' % (cmd, rest) + else: + # Handle aliases with positional arguments + args = rest.split(None, nargs) + if len(args) < nargs: + raise AliasError('Alias <%s> requires %s arguments, %s given.' % + (alias, nargs, len(args))) + cmd = '%s %s' % (cmd % tuple(args[:nargs]),' '.join(args[nargs:])) + return cmd + + def expand_alias(self, line): + """ Expand an alias in the command line + + Returns the provided command line, possibly with the first word + (command) translated according to alias expansion rules. + + [ipython]|16> _ip.expand_aliases("np myfile.txt") + <16> 'q:/opt/np/notepad++.exe myfile.txt' + """ + + pre,fn,rest = split_user_input(line) + res = pre + self.expand_aliases(fn, rest) + return res + + def expand_aliases(self, fn, rest): + """Expand multiple levels of aliases: + + if: + + alias foo bar /tmp + alias baz foo + + then: + + baz huhhahhei -> bar /tmp huhhahhei + + """ + line = fn + " " + rest + + done = set() + while 1: + pre,fn,rest = split_user_input(line, shell_line_split) + if fn in self.alias_table: + if fn in done: + warn("Cyclic alias definition, repeated '%s'" % fn) + return "" + done.add(fn) + + l2 = self.transform_alias(fn, rest) + if l2 == line: + break + # ls -> ls -F should not recurse forever + if l2.split(None,1)[0] == line.split(None,1)[0]: + line = l2 + break + line=l2 + else: + break + + return line diff --git a/IPython/core/application.py b/IPython/core/application.py new file mode 100644 index 0000000..7fa9e57 --- /dev/null +++ b/IPython/core/application.py @@ -0,0 +1,364 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +An application for IPython. + +All top-level applications should use the classes in this module for +handling configuration and creating componenets. + +The job of an :class:`Application` is to create the master configuration +object and then create the components, passing the config to them. + +Authors: + +* Brian Granger +* Fernando Perez + +Notes +----- +""" + +#----------------------------------------------------------------------------- +# Copyright (C) 2008-2009 The IPython Development Team +# +# Distributed under the terms of the BSD License. The full license is in +# the file COPYING, distributed as part of this software. +#----------------------------------------------------------------------------- + +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- + +import logging +import os +import sys + +from IPython.core import release +from IPython.utils.genutils import get_ipython_dir +from IPython.config.loader import ( + PyFileConfigLoader, + ArgParseConfigLoader, + Config, + NoConfigDefault +) + +#----------------------------------------------------------------------------- +# Classes and functions +#----------------------------------------------------------------------------- + + +class BaseAppArgParseConfigLoader(ArgParseConfigLoader): + """Default command line options for IPython based applications.""" + + def _add_other_arguments(self): + self.parser.add_argument('--ipython-dir', + dest='Global.ipython_dir',type=unicode, + help='Set to override default location of Global.ipython_dir.', + default=NoConfigDefault, + metavar='Global.ipython_dir') + self.parser.add_argument('-p', '--profile', + dest='Global.profile',type=unicode, + help='The string name of the ipython profile to be used.', + default=NoConfigDefault, + metavar='Global.profile') + self.parser.add_argument('--log-level', + dest="Global.log_level",type=int, + help='Set the log level (0,10,20,30,40,50). Default is 30.', + default=NoConfigDefault, + metavar='Global.log_level') + self.parser.add_argument('--config-file', + dest='Global.config_file',type=unicode, + help='Set the config file name to override default.', + default=NoConfigDefault, + metavar='Global.config_file') + + +class ApplicationError(Exception): + pass + + +class Application(object): + """Load a config, construct components and set them running.""" + + name = u'ipython' + description = 'IPython: an enhanced interactive Python shell.' + config_file_name = u'ipython_config.py' + default_log_level = logging.WARN + + def __init__(self): + self._exiting = False + self.init_logger() + # Track the default and actual separately because some messages are + # only printed if we aren't using the default. + self.default_config_file_name = self.config_file_name + + def init_logger(self): + self.log = logging.getLogger(self.__class__.__name__) + # This is used as the default until the command line arguments are read. + self.log.setLevel(self.default_log_level) + self._log_handler = logging.StreamHandler() + self._log_formatter = logging.Formatter("[%(name)s] %(message)s") + self._log_handler.setFormatter(self._log_formatter) + self.log.addHandler(self._log_handler) + + def _set_log_level(self, level): + self.log.setLevel(level) + + def _get_log_level(self): + return self.log.level + + log_level = property(_get_log_level, _set_log_level) + + def start(self): + """Start the application.""" + self.attempt(self.create_default_config) + self.log_default_config() + self.set_default_config_log_level() + self.attempt(self.pre_load_command_line_config) + self.attempt(self.load_command_line_config, action='abort') + self.set_command_line_config_log_level() + self.attempt(self.post_load_command_line_config) + self.log_command_line_config() + self.attempt(self.find_ipython_dir) + self.attempt(self.find_resources) + self.attempt(self.find_config_file_name) + self.attempt(self.find_config_file_paths) + self.attempt(self.pre_load_file_config) + self.attempt(self.load_file_config) + self.set_file_config_log_level() + self.attempt(self.post_load_file_config) + self.log_file_config() + self.attempt(self.merge_configs) + self.log_master_config() + self.attempt(self.pre_construct) + self.attempt(self.construct) + self.attempt(self.post_construct) + self.attempt(self.start_app) + + #------------------------------------------------------------------------- + # Various stages of Application creation + #------------------------------------------------------------------------- + + def create_default_config(self): + """Create defaults that can't be set elsewhere. + + For the most part, we try to set default in the class attributes + of Components. But, defaults the top-level Application (which is + not a HasTraitlets or Component) are not set in this way. Instead + we set them here. The Global section is for variables like this that + don't belong to a particular component. + """ + self.default_config = Config() + self.default_config.Global.ipython_dir = get_ipython_dir() + self.default_config.Global.log_level = self.log_level + + def log_default_config(self): + self.log.debug('Default config loaded:') + self.log.debug(repr(self.default_config)) + + def set_default_config_log_level(self): + try: + self.log_level = self.default_config.Global.log_level + except AttributeError: + # Fallback to the default_log_level class attribute + pass + + def create_command_line_config(self): + """Create and return a command line config loader.""" + return BaseAppArgParseConfigLoader( + description=self.description, + version=release.version + ) + + def pre_load_command_line_config(self): + """Do actions just before loading the command line config.""" + pass + + def load_command_line_config(self): + """Load the command line config.""" + loader = self.create_command_line_config() + self.command_line_config = loader.load_config() + self.extra_args = loader.get_extra_args() + + def set_command_line_config_log_level(self): + try: + self.log_level = self.command_line_config.Global.log_level + except AttributeError: + pass + + def post_load_command_line_config(self): + """Do actions just after loading the command line config.""" + pass + + def log_command_line_config(self): + self.log.debug("Command line config loaded:") + self.log.debug(repr(self.command_line_config)) + + def find_ipython_dir(self): + """Set the IPython directory. + + This sets ``self.ipython_dir``, but the actual value that is passed + to the application is kept in either ``self.default_config`` or + ``self.command_line_config``. This also adds ``self.ipython_dir`` to + ``sys.path`` so config files there can be references by other config + files. + """ + + try: + self.ipython_dir = self.command_line_config.Global.ipython_dir + except AttributeError: + self.ipython_dir = self.default_config.Global.ipython_dir + sys.path.append(os.path.abspath(self.ipython_dir)) + if not os.path.isdir(self.ipython_dir): + os.makedirs(self.ipython_dir, mode=0777) + self.log.debug("IPYTHON_DIR set to: %s" % self.ipython_dir) + + def find_resources(self): + """Find other resources that need to be in place. + + Things like cluster directories need to be in place to find the + config file. These happen right after the IPython directory has + been set. + """ + pass + + def find_config_file_name(self): + """Find the config file name for this application. + + This must set ``self.config_file_name`` to the filename of the + config file to use (just the filename). The search paths for the + config file are set in :meth:`find_config_file_paths` and then passed + to the config file loader where they are resolved to an absolute path. + + If a profile has been set at the command line, this will resolve + it. + """ + + try: + self.config_file_name = self.command_line_config.Global.config_file + except AttributeError: + pass + + try: + self.profile_name = self.command_line_config.Global.profile + name_parts = self.config_file_name.split('.') + name_parts.insert(1, u'_' + self.profile_name + u'.') + self.config_file_name = ''.join(name_parts) + except AttributeError: + pass + + def find_config_file_paths(self): + """Set the search paths for resolving the config file. + + This must set ``self.config_file_paths`` to a sequence of search + paths to pass to the config file loader. + """ + self.config_file_paths = (os.getcwd(), self.ipython_dir) + + def pre_load_file_config(self): + """Do actions before the config file is loaded.""" + pass + + def load_file_config(self): + """Load the config file. + + This tries to load the config file from disk. If successful, the + ``CONFIG_FILE`` config variable is set to the resolved config file + location. If not successful, an empty config is used. + """ + self.log.debug("Attempting to load config file: %s" % self.config_file_name) + loader = PyFileConfigLoader(self.config_file_name, + path=self.config_file_paths) + try: + self.file_config = loader.load_config() + self.file_config.Global.config_file = loader.full_filename + except IOError: + # Only warn if the default config file was NOT being used. + if not self.config_file_name==self.default_config_file_name: + self.log.warn("Config file not found, skipping: %s" % \ + self.config_file_name, exc_info=True) + self.file_config = Config() + except: + self.log.warn("Error loading config file: %s" % \ + self.config_file_name, exc_info=True) + self.file_config = Config() + + def set_file_config_log_level(self): + # We need to keeep self.log_level updated. But we only use the value + # of the file_config if a value was not specified at the command + # line, because the command line overrides everything. + if not hasattr(self.command_line_config.Global, 'log_level'): + try: + self.log_level = self.file_config.Global.log_level + except AttributeError: + pass # Use existing value + + def post_load_file_config(self): + """Do actions after the config file is loaded.""" + pass + + def log_file_config(self): + if hasattr(self.file_config.Global, 'config_file'): + self.log.debug("Config file loaded: %s" % self.file_config.Global.config_file) + self.log.debug(repr(self.file_config)) + + def merge_configs(self): + """Merge the default, command line and file config objects.""" + config = Config() + config._merge(self.default_config) + config._merge(self.file_config) + config._merge(self.command_line_config) + self.master_config = config + + def log_master_config(self): + self.log.debug("Master config created:") + self.log.debug(repr(self.master_config)) + + def pre_construct(self): + """Do actions after the config has been built, but before construct.""" + pass + + def construct(self): + """Construct the main components that make up this app.""" + self.log.debug("Constructing components for application") + + def post_construct(self): + """Do actions after construct, but before starting the app.""" + pass + + def start_app(self): + """Actually start the app.""" + self.log.debug("Starting application") + + #------------------------------------------------------------------------- + # Utility methods + #------------------------------------------------------------------------- + + def abort(self): + """Abort the starting of the application.""" + if self._exiting: + pass + else: + self.log.critical("Aborting application: %s" % self.name, exc_info=True) + self._exiting = True + sys.exit(1) + + def exit(self, exit_status=0): + if self._exiting: + pass + else: + self.log.debug("Exiting application: %s" % self.name) + self._exiting = True + sys.exit(exit_status) + + def attempt(self, func, action='abort'): + try: + func() + except SystemExit: + raise + except: + if action == 'abort': + self.abort() + elif action == 'exit': + self.exit(0) + diff --git a/IPython/core/autocall.py b/IPython/core/autocall.py new file mode 100644 index 0000000..ff2d3a6 --- /dev/null +++ b/IPython/core/autocall.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +Autocall capabilities for IPython.core. + +Authors: + +* Brian Granger +* Fernando Perez + +Notes +----- +""" + +#----------------------------------------------------------------------------- +# Copyright (C) 2008-2009 The IPython Development Team +# +# Distributed under the terms of the BSD License. The full license is in +# the file COPYING, distributed as part of this software. +#----------------------------------------------------------------------------- + +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- + + +#----------------------------------------------------------------------------- +# Code +#----------------------------------------------------------------------------- + +class IPyAutocall(object): + """ Instances of this class are always autocalled + + This happens regardless of 'autocall' variable state. Use this to + develop macro-like mechanisms. + """ + + def set_ip(self,ip): + """ Will be used to set _ip point to current ipython instance b/f call + + Override this method if you don't want this to happen. + + """ + self._ip = ip + diff --git a/IPython/core/builtin_trap.py b/IPython/core/builtin_trap.py new file mode 100644 index 0000000..8ddbcf7 --- /dev/null +++ b/IPython/core/builtin_trap.py @@ -0,0 +1,118 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +A context manager for managing things injected into :mod:`__builtin__`. + +Authors: + +* Brian Granger +""" + +#----------------------------------------------------------------------------- +# Copyright (C) 2008-2009 The IPython Development Team +# +# Distributed under the terms of the BSD License. The full license is in +# the file COPYING, distributed as part of this software. +#----------------------------------------------------------------------------- + +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- + +import __builtin__ + +from IPython.core.component import Component +from IPython.core.quitter import Quitter + +from IPython.utils.autoattr import auto_attr + +#----------------------------------------------------------------------------- +# Classes and functions +#----------------------------------------------------------------------------- + + +class BuiltinUndefined(object): pass +BuiltinUndefined = BuiltinUndefined() + + +class BuiltinTrap(Component): + + def __init__(self, parent): + super(BuiltinTrap, self).__init__(parent, None, None) + self._orig_builtins = {} + # We define this to track if a single BuiltinTrap is nested. + # Only turn off the trap when the outermost call to __exit__ is made. + self._nested_level = 0 + + @auto_attr + def shell(self): + return Component.get_instances( + root=self.root, + klass='IPython.core.iplib.InteractiveShell')[0] + + def __enter__(self): + if self._nested_level == 0: + self.set() + self._nested_level += 1 + # I return self, so callers can use add_builtin in a with clause. + return self + + def __exit__(self, type, value, traceback): + if self._nested_level == 1: + self.unset() + self._nested_level -= 1 + # Returning False will cause exceptions to propagate + return False + + def add_builtin(self, key, value): + """Add a builtin and save the original.""" + orig = __builtin__.__dict__.get(key, BuiltinUndefined) + self._orig_builtins[key] = orig + __builtin__.__dict__[key] = value + + def remove_builtin(self, key): + """Remove an added builtin and re-set the original.""" + try: + orig = self._orig_builtins.pop(key) + except KeyError: + pass + else: + if orig is BuiltinUndefined: + del __builtin__.__dict__[key] + else: + __builtin__.__dict__[key] = orig + + def set(self): + """Store ipython references in the __builtin__ namespace.""" + self.add_builtin('exit', Quitter(self.shell, 'exit')) + self.add_builtin('quit', Quitter(self.shell, 'quit')) + self.add_builtin('get_ipython', self.shell.get_ipython) + + # Recursive reload function + try: + from IPython.lib import deepreload + if self.shell.deep_reload: + self.add_builtin('reload', deepreload.reload) + else: + self.add_builtin('dreload', deepreload.reload) + del deepreload + except ImportError: + pass + + # Keep in the builtins a flag for when IPython is active. We set it + # with setdefault so that multiple nested IPythons don't clobber one + # another. Each will increase its value by one upon being activated, + # which also gives us a way to determine the nesting level. + __builtin__.__dict__.setdefault('__IPYTHON__active',0) + + def unset(self): + """Remove any builtins which might have been added by add_builtins, or + restore overwritten ones to their previous values.""" + for key in self._orig_builtins.keys(): + self.remove_builtin(key) + self._orig_builtins.clear() + self._builtins_added = False + try: + del __builtin__.__dict__['__IPYTHON__active'] + except KeyError: + pass diff --git a/IPython/completer.py b/IPython/core/completer.py similarity index 89% rename from IPython/completer.py rename to IPython/core/completer.py index b8b5334..44d2875 100644 --- a/IPython/completer.py +++ b/IPython/core/completer.py @@ -65,25 +65,28 @@ used, and this module (and the readline module) are silently inactive. import __builtin__ import __main__ import glob +import itertools import keyword import os import re import shlex import sys -import IPython.rlineimpl as readline -import itertools -from IPython.ipstruct import Struct -from IPython import ipapi -from IPython import generics import types +from IPython.core.error import TryNext +from IPython.core.prefilter import ESC_MAGIC + +import IPython.utils.rlineimpl as readline +from IPython.utils.ipstruct import Struct +from IPython.utils import generics + # Python 2.4 offers sets as a builtin try: set() except NameError: from sets import Set as set -from IPython.genutils import debugx, dir2 +from IPython.utils.genutils import debugx, dir2 __all__ = ['Completer','IPCompleter'] @@ -195,7 +198,7 @@ class Completer: try: words = generics.complete_object(obj, words) - except ipapi.TryNext: + except TryNext: pass # Build match list to return n = len(attr) @@ -233,7 +236,7 @@ class IPCompleter(Completer): Completer.__init__(self,namespace,global_namespace) self.magic_prefix = shell.name+'.magic_' - self.magic_escape = shell.ESC_MAGIC + self.magic_escape = ESC_MAGIC self.readline = readline delims = self.readline.get_completer_delims() delims = delims.replace(self.magic_escape,'') @@ -241,7 +244,7 @@ class IPCompleter(Completer): self.get_line_buffer = self.readline.get_line_buffer self.get_endidx = self.readline.get_endidx self.omit__names = omit__names - self.merge_completions = shell.rc.readline_merge_completions + self.merge_completions = shell.readline_merge_completions if alias_table is None: alias_table = {} self.alias_table = alias_table @@ -553,7 +556,7 @@ class IPCompleter(Completer): return withcase # if none, then case insensitive ones are ok too return [r for r in res if r.lower().startswith(text.lower())] - except ipapi.TryNext: + except TryNext: pass return None @@ -632,7 +635,7 @@ class IPCompleter(Completer): except IndexError: return None except: - #from IPython.ultraTB import AutoFormattedTB; # dbg + #from IPython.core.ultratb import AutoFormattedTB; # dbg #tb=AutoFormattedTB('Verbose');tb() #dbg # If completion fails, don't annoy the user. diff --git a/IPython/core/component.py b/IPython/core/component.py new file mode 100644 index 0000000..a928b40 --- /dev/null +++ b/IPython/core/component.py @@ -0,0 +1,346 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +A lightweight component system for IPython. + +Authors: + +* Brian Granger +* Fernando Perez +""" + +#----------------------------------------------------------------------------- +# Copyright (C) 2008-2009 The IPython Development Team +# +# Distributed under the terms of the BSD License. The full license is in +# the file COPYING, distributed as part of this software. +#----------------------------------------------------------------------------- + +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- + +from copy import deepcopy +import datetime +from weakref import WeakValueDictionary + +from IPython.utils.importstring import import_item +from IPython.config.loader import Config +from IPython.utils.traitlets import ( + HasTraitlets, TraitletError, MetaHasTraitlets, Instance, This +) + + +#----------------------------------------------------------------------------- +# Helper classes for Components +#----------------------------------------------------------------------------- + + +class ComponentError(Exception): + pass + +class MetaComponentTracker(type): + """A metaclass that tracks instances of Components and its subclasses.""" + + def __init__(cls, name, bases, d): + super(MetaComponentTracker, cls).__init__(name, bases, d) + cls.__instance_refs = WeakValueDictionary() + cls.__numcreated = 0 + + def __call__(cls, *args, **kw): + """Called when a class is called (instantiated)!!! + + When a Component or subclass is instantiated, this is called and + the instance is saved in a WeakValueDictionary for tracking. + """ + instance = cls.__new__(cls, *args, **kw) + + # Register the instance before __init__ is called so get_instances + # works inside __init__ methods! + indices = cls.register_instance(instance) + + # This is in a try/except because of the __init__ method fails, the + # instance is discarded and shouldn't be tracked. + try: + if isinstance(instance, cls): + cls.__init__(instance, *args, **kw) + except: + # Unregister the instance because __init__ failed! + cls.unregister_instances(indices) + raise + else: + return instance + + def register_instance(cls, instance): + """Register instance with cls and its subclasses.""" + # indices is a list of the keys used to register the instance + # with. This list is needed if the instance needs to be unregistered. + indices = [] + for c in cls.__mro__: + if issubclass(cls, c) and issubclass(c, Component): + c.__numcreated += 1 + indices.append(c.__numcreated) + c.__instance_refs[c.__numcreated] = instance + else: + break + return indices + + def unregister_instances(cls, indices): + """Unregister instance with cls and its subclasses.""" + for c, index in zip(cls.__mro__, indices): + try: + del c.__instance_refs[index] + except KeyError: + pass + + def clear_instances(cls): + """Clear all instances tracked by cls.""" + cls.__instance_refs.clear() + cls.__numcreated = 0 + + def get_instances(cls, name=None, root=None, klass=None): + """Get all instances of cls and its subclasses. + + Parameters + ---------- + name : str + Limit to components with this name. + root : Component or subclass + Limit to components having this root. + klass : class or str + Limits to instances of the class or its subclasses. If a str + is given ut must be in the form 'foo.bar.MyClass'. The str + form of this argument is useful for forward declarations. + """ + if klass is not None: + if isinstance(klass, basestring): + klass = import_item(klass) + # Limit search to instances of klass for performance + if issubclass(klass, Component): + return klass.get_instances(name=name, root=root) + instances = cls.__instance_refs.values() + if name is not None: + instances = [i for i in instances if i.name == name] + if klass is not None: + instances = [i for i in instances if isinstance(i, klass)] + if root is not None: + instances = [i for i in instances if i.root == root] + return instances + + def get_instances_by_condition(cls, call, name=None, root=None, + klass=None): + """Get all instances of cls, i such that call(i)==True. + + This also takes the ``name`` and ``root`` and ``classname`` + arguments of :meth:`get_instance` + """ + return [i for i in cls.get_instances(name, root, klass) if call(i)] + + +def masquerade_as(instance, cls): + """Let instance masquerade as an instance of cls. + + Sometimes, such as in testing code, it is useful to let a class + masquerade as another. Python, being duck typed, allows this by + default. But, instances of components are tracked by their class type. + + After calling this, ``cls.get_instances()`` will return ``instance``. This + does not, however, cause ``isinstance(instance, cls)`` to return ``True``. + + Parameters + ---------- + instance : an instance of a Component or Component subclass + The instance that will pretend to be a cls. + cls : subclass of Component + The Component subclass that instance will pretend to be. + """ + cls.register_instance(instance) + + +class ComponentNameGenerator(object): + """A Singleton to generate unique component names.""" + + def __init__(self, prefix): + self.prefix = prefix + self.i = 0 + + def __call__(self): + count = self.i + self.i += 1 + return "%s%s" % (self.prefix, count) + + +ComponentNameGenerator = ComponentNameGenerator('ipython.component') + + +class MetaComponent(MetaHasTraitlets, MetaComponentTracker): + pass + + +#----------------------------------------------------------------------------- +# Component implementation +#----------------------------------------------------------------------------- + + +class Component(HasTraitlets): + + __metaclass__ = MetaComponent + + # Traitlets are fun! + config = Instance(Config,(),{}) + parent = This() + root = This() + created = None + + def __init__(self, parent, name=None, config=None): + """Create a component given a parent and possibly and name and config. + + Parameters + ---------- + parent : Component subclass + The parent in the component graph. The parent is used + to get the root of the component graph. + name : str + The unique name of the component. If empty, then a unique + one will be autogenerated. + config : Config + If this is empty, self.config = parent.config, otherwise + self.config = config and root.config is ignored. This argument + should only be used to *override* the automatic inheritance of + parent.config. If a caller wants to modify parent.config + (not override), the caller should make a copy and change + attributes and then pass the copy to this argument. + + Notes + ----- + Subclasses of Component must call the :meth:`__init__` method of + :class:`Component` *before* doing anything else and using + :func:`super`:: + + class MyComponent(Component): + def __init__(self, parent, name=None, config=None): + super(MyComponent, self).__init__(parent, name, config) + # Then any other code you need to finish initialization. + + This ensures that the :attr:`parent`, :attr:`name` and :attr:`config` + attributes are handled properly. + """ + super(Component, self).__init__() + self._children = [] + if name is None: + self.name = ComponentNameGenerator() + else: + self.name = name + self.root = self # This is the default, it is set when parent is set + self.parent = parent + if config is not None: + self.config = config + # We used to deepcopy, but for now we are trying to just save + # by reference. This *could* have side effects as all components + # will share config. In fact, I did find such a side effect in + # _config_changed below. If a config attribute value was a mutable type + # all instances of a component were getting the same copy, effectively + # making that a class attribute. + # self.config = deepcopy(config) + else: + if self.parent is not None: + self.config = self.parent.config + # We used to deepcopy, but for now we are trying to just save + # by reference. This *could* have side effects as all components + # will share config. In fact, I did find such a side effect in + # _config_changed below. If a config attribute value was a mutable type + # all instances of a component were getting the same copy, effectively + # making that a class attribute. + # self.config = deepcopy(self.parent.config) + + self.created = datetime.datetime.now() + + #------------------------------------------------------------------------- + # Static traitlet notifiations + #------------------------------------------------------------------------- + + def _parent_changed(self, name, old, new): + if old is not None: + old._remove_child(self) + if new is not None: + new._add_child(self) + + if new is None: + self.root = self + else: + self.root = new.root + + def _root_changed(self, name, old, new): + if self.parent is None: + if not (new is self): + raise ComponentError("Root not self, but parent is None.") + else: + if not self.parent.root is new: + raise ComponentError("Error in setting the root attribute: " + "root != parent.root") + + def _config_changed(self, name, old, new): + """Update all the class traits having ``config=True`` as metadata. + + For any class traitlet with a ``config`` metadata attribute that is + ``True``, we update the traitlet with the value of the corresponding + config entry. + """ + # Get all traitlets with a config metadata entry that is True + traitlets = self.traitlets(config=True) + + # We auto-load config section for this class as well as any parent + # classes that are Component subclasses. This starts with Component + # and works down the mro loading the config for each section. + section_names = [cls.__name__ for cls in \ + reversed(self.__class__.__mro__) if + issubclass(cls, Component) and issubclass(self.__class__, cls)] + + for sname in section_names: + # Don't do a blind getattr as that would cause the config to + # dynamically create the section with name self.__class__.__name__. + if new._has_section(sname): + my_config = new[sname] + for k, v in traitlets.items(): + # Don't allow traitlets with config=True to start with + # uppercase. Otherwise, they are confused with Config + # subsections. But, developers shouldn't have uppercase + # attributes anyways! (PEP 6) + if k[0].upper()==k[0] and not k.startswith('_'): + raise ComponentError('Component traitlets with ' + 'config=True must start with a lowercase so they are ' + 'not confused with Config subsections: %s.%s' % \ + (self.__class__.__name__, k)) + try: + # Here we grab the value from the config + # If k has the naming convention of a config + # section, it will be auto created. + config_value = my_config[k] + except KeyError: + pass + else: + # print "Setting %s.%s from %s.%s=%r" % \ + # (self.__class__.__name__,k,sname,k,config_value) + # We have to do a deepcopy here if we don't deepcopy the entire + # config object. If we don't, a mutable config_value will be + # shared by all instances, effectively making it a class attribute. + setattr(self, k, deepcopy(config_value)) + + @property + def children(self): + """A list of all my child components.""" + return self._children + + def _remove_child(self, child): + """A private method for removing children components.""" + if child in self._children: + index = self._children.index(child) + del self._children[index] + + def _add_child(self, child): + """A private method for adding children components.""" + if child not in self._children: + self._children.append(child) + + def __repr__(self): + return "<%s('%s')>" % (self.__class__.__name__, self.name) diff --git a/IPython/CrashHandler.py b/IPython/core/crashhandler.py similarity index 81% rename from IPython/CrashHandler.py rename to IPython/core/crashhandler.py index b437cac..dd5c553 100644 --- a/IPython/CrashHandler.py +++ b/IPython/core/crashhandler.py @@ -21,15 +21,14 @@ Authors # From the standard library import os import sys -from pprint import pprint,pformat +from pprint import pformat # Our own -from IPython import Release -from IPython import ultraTB -from IPython.ColorANSI import ColorScheme,ColorSchemeTable # too long names -from IPython.Itpl import Itpl,itpl,printpl +from IPython.core import release +from IPython.core import ultratb +from IPython.external.Itpl import itpl -from IPython.genutils import * +from IPython.utils.genutils import * #**************************************************************************** class CrashHandler: @@ -125,7 +124,7 @@ $self.bug_tracker #color_scheme = 'Linux' # dbg try: - rptdir = self.IP.rc.ipythondir + rptdir = self.IP.ipython_dir except: rptdir = os.getcwd() if not os.path.isdir(rptdir): @@ -134,7 +133,7 @@ $self.bug_tracker # write the report filename into the instance dict so it can get # properly expanded out in the user message template self.crash_report_fname = report_name - TBhandler = ultraTB.VerboseTB(color_scheme=color_scheme, + TBhandler = ultratb.VerboseTB(color_scheme=color_scheme, long_header=1) traceback = TBhandler.text(etype,evalue,etb,context=31) @@ -167,12 +166,12 @@ $self.bug_tracker rpt_add = report.append rpt_add('*'*75+'\n\n'+'IPython post-mortem report\n\n') - rpt_add('IPython version: %s \n\n' % Release.version) - rpt_add('BZR revision : %s \n\n' % Release.revision) + rpt_add('IPython version: %s \n\n' % release.version) + rpt_add('BZR revision : %s \n\n' % release.revision) rpt_add('Platform info : os.name -> %s, sys.platform -> %s' % (os.name,sys.platform) ) rpt_add(sec_sep+'Current user configuration structure:\n\n') - rpt_add(pformat(self.IP.rc.dict())) + rpt_add(pformat(self.IP.dict())) rpt_add(sec_sep+'Crash traceback:\n\n' + traceback) try: rpt_add(sec_sep+"History of session input:") @@ -191,12 +190,12 @@ class IPythonCrashHandler(CrashHandler): def __init__(self,IP): # Set here which of the IPython authors should be listed as contact - AUTHOR_CONTACT = 'Ville' + AUTHOR_CONTACT = 'Fernando' # Set argument defaults app_name = 'IPython' bug_tracker = 'https://bugs.launchpad.net/ipython/+filebug' - contact_name,contact_email = Release.authors[AUTHOR_CONTACT][:2] + contact_name,contact_email = release.authors[AUTHOR_CONTACT][:2] crash_report_fname = 'IPython_crash_report.txt' # Call parent constructor CrashHandler.__init__(self,IP,app_name,contact_name,contact_email, @@ -211,12 +210,12 @@ class IPythonCrashHandler(CrashHandler): rpt_add = report.append rpt_add('*'*75+'\n\n'+'IPython post-mortem report\n\n') - rpt_add('IPython version: %s \n\n' % Release.version) - rpt_add('BZR revision : %s \n\n' % Release.revision) + rpt_add('IPython version: %s \n\n' % release.version) + rpt_add('BZR revision : %s \n\n' % release.revision) rpt_add('Platform info : os.name -> %s, sys.platform -> %s' % (os.name,sys.platform) ) rpt_add(sec_sep+'Current user configuration structure:\n\n') - rpt_add(pformat(self.IP.rc.dict())) + # rpt_add(pformat(self.IP.dict())) rpt_add(sec_sep+'Crash traceback:\n\n' + traceback) try: rpt_add(sec_sep+"History of session input:") diff --git a/IPython/Debugger.py b/IPython/core/debugger.py similarity index 81% rename from IPython/Debugger.py rename to IPython/core/debugger.py index 117b7d1..e8163f9 100644 --- a/IPython/Debugger.py +++ b/IPython/core/debugger.py @@ -31,9 +31,11 @@ import linecache import os import sys -from IPython import PyColorize, ColorANSI, ipapi -from IPython.genutils import Term -from IPython.excolors import exception_colors +from IPython.utils import PyColorize +from IPython.core import ipapi +from IPython.utils import coloransi +from IPython.utils.genutils import Term +from IPython.core.excolors import exception_colors # See if we can use pydb. has_pydb = False @@ -68,6 +70,7 @@ def BdbQuit_excepthook(et,ev,tb): def BdbQuit_IPython_excepthook(self,et,ev,tb): print 'Exiting Debugger.' + class Tracer(object): """Class for local debugging, similar to pdb.set_trace. @@ -93,7 +96,7 @@ class Tracer(object): Usage example: - from IPython.Debugger import Tracer; debug_here = Tracer() + from IPython.core.debugger import Tracer; debug_here = Tracer() ... later in your code debug_here() # -> will open up the debugger at that point. @@ -103,26 +106,23 @@ class Tracer(object): from the Python standard library for usage details. """ - global __IPYTHON__ try: - __IPYTHON__ - except NameError: + ip = ipapi.get() + except: # Outside of ipython, we set our own exception hook manually - __IPYTHON__ = ipapi.get(True,False) BdbQuit_excepthook.excepthook_ori = sys.excepthook sys.excepthook = BdbQuit_excepthook def_colors = 'NoColor' try: # Limited tab completion support - import rlcompleter,readline + import readline readline.parse_and_bind('tab: complete') except ImportError: pass else: # In ipython, we use its custom exception handler mechanism - ip = ipapi.get() - def_colors = ip.options.colors - ip.set_custom_exc((bdb.BdbQuit,),BdbQuit_IPython_excepthook) + def_colors = ip.colors + ip.set_custom_exc((bdb.BdbQuit,), BdbQuit_IPython_excepthook) if colors is None: colors = def_colors @@ -136,6 +136,7 @@ class Tracer(object): self.debugger.set_trace(sys._getframe().f_back) + def decorate_fn_with_doc(new_fn, old_fn, additional_text=""): """Make new_fn have old_fn's doc string. This is particularly useful for the do_... commands that hook into the help system. @@ -147,6 +148,7 @@ def decorate_fn_with_doc(new_fn, old_fn, additional_text=""): wrapper.__doc__ = old_fn.__doc__ + additional_text return wrapper + def _file_lines(fname): """Return the contents of a named file as a list of lines. @@ -162,143 +164,98 @@ def _file_lines(fname): outfile.close() return out + class Pdb(OldPdb): """Modified Pdb class, does not load readline.""" - if sys.version[:3] >= '2.5' or has_pydb: - def __init__(self,color_scheme='NoColor',completekey=None, - stdin=None, stdout=None): + def __init__(self,color_scheme='NoColor',completekey=None, + stdin=None, stdout=None): - # Parent constructor: - if has_pydb and completekey is None: - OldPdb.__init__(self,stdin=stdin,stdout=Term.cout) - else: - OldPdb.__init__(self,completekey,stdin,stdout) - - self.prompt = prompt # The default prompt is '(Pdb)' + # Parent constructor: + if has_pydb and completekey is None: + OldPdb.__init__(self,stdin=stdin,stdout=Term.cout) + else: + OldPdb.__init__(self,completekey,stdin,stdout) - # IPython changes... - self.is_pydb = has_pydb - - if self.is_pydb: - - # iplib.py's ipalias seems to want pdb's checkline - # which located in pydb.fn - import pydb.fns - self.checkline = lambda filename, lineno: \ - pydb.fns.checkline(self, filename, lineno) - - self.curframe = None - self.do_restart = self.new_do_restart - - self.old_all_completions = __IPYTHON__.Completer.all_completions - __IPYTHON__.Completer.all_completions=self.all_completions - - self.do_list = decorate_fn_with_doc(self.list_command_pydb, - OldPdb.do_list) - self.do_l = self.do_list - self.do_frame = decorate_fn_with_doc(self.new_do_frame, - OldPdb.do_frame) - - self.aliases = {} - - # Create color table: we copy the default one from the traceback - # module and add a few attributes needed for debugging - self.color_scheme_table = exception_colors() + self.prompt = prompt # The default prompt is '(Pdb)' + + # IPython changes... + self.is_pydb = has_pydb - # shorthands - C = ColorANSI.TermColors - cst = self.color_scheme_table + self.shell = ipapi.get() - cst['NoColor'].colors.breakpoint_enabled = C.NoColor - cst['NoColor'].colors.breakpoint_disabled = C.NoColor + if self.is_pydb: - cst['Linux'].colors.breakpoint_enabled = C.LightRed - cst['Linux'].colors.breakpoint_disabled = C.Red + # iplib.py's ipalias seems to want pdb's checkline + # which located in pydb.fn + import pydb.fns + self.checkline = lambda filename, lineno: \ + pydb.fns.checkline(self, filename, lineno) - cst['LightBG'].colors.breakpoint_enabled = C.LightRed - cst['LightBG'].colors.breakpoint_disabled = C.Red + self.curframe = None + self.do_restart = self.new_do_restart - self.set_colors(color_scheme) + self.old_all_completions = self.shell.Completer.all_completions + self.shell.Completer.all_completions=self.all_completions - # Add a python parser so we can syntax highlight source while - # debugging. - self.parser = PyColorize.Parser() + self.do_list = decorate_fn_with_doc(self.list_command_pydb, + OldPdb.do_list) + self.do_l = self.do_list + self.do_frame = decorate_fn_with_doc(self.new_do_frame, + OldPdb.do_frame) + self.aliases = {} - else: - # Ugly hack: for Python 2.3-2.4, we can't call the parent constructor, - # because it binds readline and breaks tab-completion. This means we - # have to COPY the constructor here. - def __init__(self,color_scheme='NoColor'): - bdb.Bdb.__init__(self) - cmd.Cmd.__init__(self,completekey=None) # don't load readline - self.prompt = 'ipdb> ' # The default prompt is '(Pdb)' - self.aliases = {} - - # These two lines are part of the py2.4 constructor, let's put them - # unconditionally here as they won't cause any problems in 2.3. - self.mainpyfile = '' - self._wait_for_mainpyfile = 0 - - # Read $HOME/.pdbrc and ./.pdbrc - try: - self.rcLines = _file_lines(os.path.join(os.environ['HOME'], - ".pdbrc")) - except KeyError: - self.rcLines = [] - self.rcLines.extend(_file_lines(".pdbrc")) + # Create color table: we copy the default one from the traceback + # module and add a few attributes needed for debugging + self.color_scheme_table = exception_colors() - # Create color table: we copy the default one from the traceback - # module and add a few attributes needed for debugging - self.color_scheme_table = exception_colors() + # shorthands + C = coloransi.TermColors + cst = self.color_scheme_table - # shorthands - C = ColorANSI.TermColors - cst = self.color_scheme_table + cst['NoColor'].colors.breakpoint_enabled = C.NoColor + cst['NoColor'].colors.breakpoint_disabled = C.NoColor - cst['NoColor'].colors.breakpoint_enabled = C.NoColor - cst['NoColor'].colors.breakpoint_disabled = C.NoColor + cst['Linux'].colors.breakpoint_enabled = C.LightRed + cst['Linux'].colors.breakpoint_disabled = C.Red - cst['Linux'].colors.breakpoint_enabled = C.LightRed - cst['Linux'].colors.breakpoint_disabled = C.Red + cst['LightBG'].colors.breakpoint_enabled = C.LightRed + cst['LightBG'].colors.breakpoint_disabled = C.Red - cst['LightBG'].colors.breakpoint_enabled = C.LightRed - cst['LightBG'].colors.breakpoint_disabled = C.Red + self.set_colors(color_scheme) - self.set_colors(color_scheme) + # Add a python parser so we can syntax highlight source while + # debugging. + self.parser = PyColorize.Parser() - # Add a python parser so we can syntax highlight source while - # debugging. - self.parser = PyColorize.Parser() - def set_colors(self, scheme): """Shorthand access to the color table scheme selector method.""" self.color_scheme_table.set_active_scheme(scheme) def interaction(self, frame, traceback): - __IPYTHON__.set_completer_frame(frame) + self.shell.set_completer_frame(frame) OldPdb.interaction(self, frame, traceback) def new_do_up(self, arg): OldPdb.do_up(self, arg) - __IPYTHON__.set_completer_frame(self.curframe) + self.shell.set_completer_frame(self.curframe) do_u = do_up = decorate_fn_with_doc(new_do_up, OldPdb.do_up) def new_do_down(self, arg): OldPdb.do_down(self, arg) - __IPYTHON__.set_completer_frame(self.curframe) + self.shell.set_completer_frame(self.curframe) do_d = do_down = decorate_fn_with_doc(new_do_down, OldPdb.do_down) def new_do_frame(self, arg): OldPdb.do_frame(self, arg) - __IPYTHON__.set_completer_frame(self.curframe) + self.shell.set_completer_frame(self.curframe) def new_do_quit(self, arg): if hasattr(self, 'old_all_completions'): - __IPYTHON__.Completer.all_completions=self.old_all_completions + self.shell.Completer.all_completions=self.old_all_completions return OldPdb.do_quit(self, arg) @@ -312,7 +269,7 @@ class Pdb(OldPdb): return self.do_quit(arg) def postloop(self): - __IPYTHON__.set_completer_frame(None) + self.shell.set_completer_frame(None) def print_stack_trace(self): try: @@ -329,7 +286,7 @@ class Pdb(OldPdb): # vds: >> frame, lineno = frame_lineno filename = frame.f_code.co_filename - __IPYTHON__.hooks.synchronize_with_editor(filename, lineno, 0) + self.shell.hooks.synchronize_with_editor(filename, lineno, 0) # vds: << def format_stack_entry(self, frame_lineno, lprefix=': ', context = 3): @@ -498,7 +455,7 @@ class Pdb(OldPdb): # vds: >> lineno = first filename = self.curframe.f_code.co_filename - __IPYTHON__.hooks.synchronize_with_editor(filename, lineno, 0) + self.shell.hooks.synchronize_with_editor(filename, lineno, 0) # vds: << do_l = do_list @@ -507,16 +464,16 @@ class Pdb(OldPdb): """The debugger interface to magic_pdef""" namespaces = [('Locals', self.curframe.f_locals), ('Globals', self.curframe.f_globals)] - __IPYTHON__.magic_pdef(arg, namespaces=namespaces) + self.shell.magic_pdef(arg, namespaces=namespaces) def do_pdoc(self, arg): """The debugger interface to magic_pdoc""" namespaces = [('Locals', self.curframe.f_locals), ('Globals', self.curframe.f_globals)] - __IPYTHON__.magic_pdoc(arg, namespaces=namespaces) + self.shell.magic_pdoc(arg, namespaces=namespaces) def do_pinfo(self, arg): """The debugger equivalant of ?obj""" namespaces = [('Locals', self.curframe.f_locals), ('Globals', self.curframe.f_globals)] - __IPYTHON__.magic_pinfo("pinfo %s" % arg, namespaces=namespaces) + self.shell.magic_pinfo("pinfo %s" % arg, namespaces=namespaces) diff --git a/IPython/core/display_trap.py b/IPython/core/display_trap.py new file mode 100644 index 0000000..c0f0834 --- /dev/null +++ b/IPython/core/display_trap.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +A context manager for handling sys.displayhook. + +Authors: + +* Robert Kern +* Brian Granger +""" + +#----------------------------------------------------------------------------- +# Copyright (C) 2008-2009 The IPython Development Team +# +# Distributed under the terms of the BSD License. The full license is in +# the file COPYING, distributed as part of this software. +#----------------------------------------------------------------------------- + +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- + +import sys + +from IPython.core.component import Component + +from IPython.utils.autoattr import auto_attr + +#----------------------------------------------------------------------------- +# Classes and functions +#----------------------------------------------------------------------------- + + +class DisplayTrap(Component): + """Object to manage sys.displayhook. + + This came from IPython.core.kernel.display_hook, but is simplified + (no callbacks or formatters) until more of the core is refactored. + """ + + def __init__(self, parent, hook): + super(DisplayTrap, self).__init__(parent, None, None) + self.hook = hook + self.old_hook = None + # We define this to track if a single BuiltinTrap is nested. + # Only turn off the trap when the outermost call to __exit__ is made. + self._nested_level = 0 + + # @auto_attr + # def shell(self): + # return Component.get_instances( + # root=self.root, + # klass='IPython.core.iplib.InteractiveShell')[0] + + def __enter__(self): + if self._nested_level == 0: + self.set() + self._nested_level += 1 + return self + + def __exit__(self, type, value, traceback): + if self._nested_level == 1: + self.unset() + self._nested_level -= 1 + # Returning False will cause exceptions to propagate + return False + + def set(self): + """Set the hook.""" + if sys.displayhook is not self.hook: + self.old_hook = sys.displayhook + sys.displayhook = self.hook + + def unset(self): + """Unset the hook.""" + sys.displayhook = self.old_hook + diff --git a/IPython/core/embed.py b/IPython/core/embed.py new file mode 100644 index 0000000..a43342a --- /dev/null +++ b/IPython/core/embed.py @@ -0,0 +1,272 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +An embedded IPython shell. + +Authors: + +* Brian Granger +* Fernando Perez + +Notes +----- +""" + +#----------------------------------------------------------------------------- +# Copyright (C) 2008-2009 The IPython Development Team +# +# Distributed under the terms of the BSD License. The full license is in +# the file COPYING, distributed as part of this software. +#----------------------------------------------------------------------------- + +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- + +from __future__ import with_statement + +import sys +from contextlib import nested + +from IPython.core import ultratb +from IPython.core.iplib import InteractiveShell +from IPython.core.ipapp import load_default_config + +from IPython.utils.traitlets import Bool, Str, CBool +from IPython.utils.genutils import ask_yes_no + + +#----------------------------------------------------------------------------- +# Classes and functions +#----------------------------------------------------------------------------- + +# This is an additional magic that is exposed in embedded shells. +def kill_embedded(self,parameter_s=''): + """%kill_embedded : deactivate for good the current embedded IPython. + + This function (after asking for confirmation) sets an internal flag so that + an embedded IPython will never activate again. This is useful to + permanently disable a shell that is being called inside a loop: once you've + figured out what you needed from it, you may then kill it and the program + will then continue to run without the interactive shell interfering again. + """ + + kill = ask_yes_no("Are you sure you want to kill this embedded instance " + "(y/n)? [y/N] ",'n') + if kill: + self.embedded_active = False + print "This embedded IPython will not reactivate anymore once you exit." + + +class InteractiveShellEmbed(InteractiveShell): + + dummy_mode = Bool(False) + exit_msg = Str('') + embedded = CBool(True) + embedded_active = CBool(True) + # Like the base class display_banner is not configurable, but here it + # is True by default. + display_banner = CBool(True) + + def __init__(self, parent=None, config=None, ipython_dir=None, usage=None, + user_ns=None, user_global_ns=None, + banner1=None, banner2=None, display_banner=None, + custom_exceptions=((),None), exit_msg=''): + + self.save_sys_ipcompleter() + + super(InteractiveShellEmbed,self).__init__( + parent=parent, config=config, ipython_dir=ipython_dir, usage=usage, + user_ns=user_ns, user_global_ns=user_global_ns, + banner1=banner1, banner2=banner2, display_banner=display_banner, + custom_exceptions=custom_exceptions) + + self.exit_msg = exit_msg + self.define_magic("kill_embedded", kill_embedded) + + # don't use the ipython crash handler so that user exceptions aren't + # trapped + sys.excepthook = ultratb.FormattedTB(color_scheme=self.colors, + mode=self.xmode, + call_pdb=self.pdb) + + self.restore_sys_ipcompleter() + + def init_sys_modules(self): + pass + + def save_sys_ipcompleter(self): + """Save readline completer status.""" + try: + #print 'Save completer',sys.ipcompleter # dbg + self.sys_ipcompleter_orig = sys.ipcompleter + except: + pass # not nested with IPython + + def restore_sys_ipcompleter(self): + """Restores the readline completer which was in place. + + This allows embedded IPython within IPython not to disrupt the + parent's completion. + """ + try: + self.readline.set_completer(self.sys_ipcompleter_orig) + sys.ipcompleter = self.sys_ipcompleter_orig + except: + pass + + def __call__(self, header='', local_ns=None, global_ns=None, dummy=None, + stack_depth=1): + """Activate the interactive interpreter. + + __call__(self,header='',local_ns=None,global_ns,dummy=None) -> Start + the interpreter shell with the given local and global namespaces, and + optionally print a header string at startup. + + The shell can be globally activated/deactivated using the + set/get_dummy_mode methods. This allows you to turn off a shell used + for debugging globally. + + However, *each* time you call the shell you can override the current + state of dummy_mode with the optional keyword parameter 'dummy'. For + example, if you set dummy mode on with IPShell.set_dummy_mode(1), you + can still have a specific call work by making it as IPShell(dummy=0). + + The optional keyword parameter dummy controls whether the call + actually does anything. + """ + + # If the user has turned it off, go away + if not self.embedded_active: + return + + # Normal exits from interactive mode set this flag, so the shell can't + # re-enter (it checks this variable at the start of interactive mode). + self.exit_now = False + + # Allow the dummy parameter to override the global __dummy_mode + if dummy or (dummy != 0 and self.dummy_mode): + return + + if self.has_readline: + self.set_completer() + + # self.banner is auto computed + if header: + self.old_banner2 = self.banner2 + self.banner2 = self.banner2 + '\n' + header + '\n' + + # Call the embedding code with a stack depth of 1 so it can skip over + # our call and get the original caller's namespaces. + self.mainloop(local_ns, global_ns, stack_depth=stack_depth) + + self.banner2 = self.old_banner2 + + if self.exit_msg is not None: + print self.exit_msg + + self.restore_sys_ipcompleter() + + def mainloop(self, local_ns=None, global_ns=None, stack_depth=0, + display_banner=None): + """Embeds IPython into a running python program. + + Input: + + - header: An optional header message can be specified. + + - local_ns, global_ns: working namespaces. If given as None, the + IPython-initialized one is updated with __main__.__dict__, so that + program variables become visible but user-specific configuration + remains possible. + + - stack_depth: specifies how many levels in the stack to go to + looking for namespaces (when local_ns and global_ns are None). This + allows an intermediate caller to make sure that this function gets + the namespace from the intended level in the stack. By default (0) + it will get its locals and globals from the immediate caller. + + Warning: it's possible to use this in a program which is being run by + IPython itself (via %run), but some funny things will happen (a few + globals get overwritten). In the future this will be cleaned up, as + there is no fundamental reason why it can't work perfectly.""" + + # Get locals and globals from caller + if local_ns is None or global_ns is None: + call_frame = sys._getframe(stack_depth).f_back + + if local_ns is None: + local_ns = call_frame.f_locals + if global_ns is None: + global_ns = call_frame.f_globals + + # Update namespaces and fire up interpreter + + # The global one is easy, we can just throw it in + self.user_global_ns = global_ns + + # but the user/local one is tricky: ipython needs it to store internal + # data, but we also need the locals. We'll copy locals in the user + # one, but will track what got copied so we can delete them at exit. + # This is so that a later embedded call doesn't see locals from a + # previous call (which most likely existed in a separate scope). + local_varnames = local_ns.keys() + self.user_ns.update(local_ns) + #self.user_ns['local_ns'] = local_ns # dbg + + # Patch for global embedding to make sure that things don't overwrite + # user globals accidentally. Thanks to Richard + # FIXME. Test this a bit more carefully (the if.. is new) + if local_ns is None and global_ns is None: + self.user_global_ns.update(__main__.__dict__) + + # make sure the tab-completer has the correct frame information, so it + # actually completes using the frame's locals/globals + self.set_completer_frame() + + with nested(self.builtin_trap, self.display_trap): + self.interact(display_banner=display_banner) + + # now, purge out the user namespace from anything we might have added + # from the caller's local namespace + delvar = self.user_ns.pop + for var in local_varnames: + delvar(var,None) + + +_embedded_shell = None + + +def embed(header='', config=None, usage=None, banner1=None, banner2=None, + display_banner=True, exit_msg=''): + """Call this to embed IPython at the current point in your program. + + The first invocation of this will create an :class:`InteractiveShellEmbed` + instance and then call it. Consecutive calls just call the already + created instance. + + Here is a simple example:: + + from IPython import embed + a = 10 + b = 20 + embed('First time') + c = 30 + d = 40 + embed + + Full customization can be done by passing a :class:`Struct` in as the + config argument. + """ + if config is None: + config = load_default_config() + config.InteractiveShellEmbed = config.InteractiveShell + global _embedded_shell + if _embedded_shell is None: + _embedded_shell = InteractiveShellEmbed( + config=config, usage=usage, + banner1=banner1, banner2=banner2, + display_banner=display_banner, exit_msg=exit_msg + ) + _embedded_shell(header=header, stack_depth=2) + diff --git a/IPython/core/error.py b/IPython/core/error.py new file mode 100644 index 0000000..8d20ea8 --- /dev/null +++ b/IPython/core/error.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +Global exception classes for IPython.core. + +Authors: + +* Brian Granger +* Fernando Perez + +Notes +----- +""" + +#----------------------------------------------------------------------------- +# Copyright (C) 2008-2009 The IPython Development Team +# +# Distributed under the terms of the BSD License. The full license is in +# the file COPYING, distributed as part of this software. +#----------------------------------------------------------------------------- + +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- + +#----------------------------------------------------------------------------- +# Exception classes +#----------------------------------------------------------------------------- + +class IPythonCoreError(Exception): + pass + + +class TryNext(IPythonCoreError): + """Try next hook exception. + + Raise this in your hook function to indicate that the next hook handler + should be used to handle the operation. If you pass arguments to the + constructor those arguments will be used by the next hook instead of the + original ones. + """ + + def __init__(self, *args, **kwargs): + self.args = args + self.kwargs = kwargs + +class UsageError(IPythonCoreError): + """Error in magic function arguments, etc. + + Something that probably won't warrant a full traceback, but should + nevertheless interrupt a macro / batch file. + """ \ No newline at end of file diff --git a/IPython/excolors.py b/IPython/core/excolors.py similarity index 96% rename from IPython/excolors.py rename to IPython/core/excolors.py index 0245eb5..f36186b 100644 --- a/IPython/excolors.py +++ b/IPython/core/excolors.py @@ -12,7 +12,7 @@ Color schemes for exception handling code in IPython. #**************************************************************************** # Required modules -from IPython.ColorANSI import ColorSchemeTable, TermColors, ColorScheme +from IPython.utils.coloransi import ColorSchemeTable, TermColors, ColorScheme def exception_colors(): """Return a color table with fields for exception reporting. @@ -34,9 +34,9 @@ def exception_colors(): >>> ec.active_scheme_name 'NoColor' >>> ec.active_colors.keys() - ['em', 'caret', '__allownew', 'name', 'val', 'vName', 'Normal', 'normalEm', - 'filename', 'linenoEm', 'excName', 'lineno', 'valEm', 'filenameEm', - 'nameEm', 'line', 'topline'] + ['em', 'filenameEm', 'excName', 'valEm', 'nameEm', 'line', 'topline', + 'name', 'caret', 'val', 'vName', 'Normal', 'filename', 'linenoEm', + 'lineno', 'normalEm'] """ ex_colors = ColorSchemeTable() diff --git a/IPython/FakeModule.py b/IPython/core/fakemodule.py similarity index 100% rename from IPython/FakeModule.py rename to IPython/core/fakemodule.py diff --git a/IPython/history.py b/IPython/core/history.py similarity index 80% rename from IPython/history.py rename to IPython/core/history.py index 40508c8..a85bf01 100644 --- a/IPython/history.py +++ b/IPython/core/history.py @@ -5,9 +5,8 @@ import fnmatch import os -# IPython imports -from IPython.genutils import Term, ask_yes_no, warn -import IPython.ipapi +from IPython.utils.genutils import Term, ask_yes_no, warn +from IPython.core import ipapi def magic_history(self, parameter_s = ''): """Print input history (_i variables), with most recent last. @@ -48,9 +47,7 @@ def magic_history(self, parameter_s = ''): confirmation first if it already exists. """ - ip = self.api - shell = self.shell - if not shell.outputcache.do_full_cache: + if not self.outputcache.do_full_cache: print 'This feature is only available if numbered prompts are in use.' return opts,args = self.parse_options(parameter_s,'gntsrf:',mode='list') @@ -72,11 +69,11 @@ def magic_history(self, parameter_s = ''): close_at_end = True if 't' in opts: - input_hist = shell.input_hist + input_hist = self.input_hist elif 'r' in opts: - input_hist = shell.input_hist_raw + input_hist = self.input_hist_raw else: - input_hist = shell.input_hist + input_hist = self.input_hist default_length = 40 pattern = None @@ -106,7 +103,7 @@ def magic_history(self, parameter_s = ''): found = False if pattern is not None: - sh = ip.IP.shadowhist.all() + sh = self.shadowhist.all() for idx, s in sh: if fnmatch.fnmatch(s, pattern): print "0%d: %s" %(idx, s) @@ -169,9 +166,8 @@ def rep_f(self, arg): """ opts,args = self.parse_options(arg,'',mode='list') - ip = self.api if not args: - ip.set_next_input(str(ip.user_ns["_"])) + self.set_next_input(str(self.user_ns["_"])) return if len(args) == 1 and not '-' in args[0]: @@ -180,33 +176,33 @@ def rep_f(self, arg): # get from shadow hist num = int(arg[1:]) line = self.shadowhist.get(num) - ip.set_next_input(str(line)) + self.set_next_input(str(line)) return try: num = int(args[0]) - ip.set_next_input(str(ip.IP.input_hist_raw[num]).rstrip()) + self.set_next_input(str(self.input_hist_raw[num]).rstrip()) return except ValueError: pass - for h in reversed(self.shell.input_hist_raw): + for h in reversed(self.input_hist_raw): if 'rep' in h: continue if fnmatch.fnmatch(h,'*' + arg + '*'): - ip.set_next_input(str(h).rstrip()) + self.set_next_input(str(h).rstrip()) return try: lines = self.extract_input_slices(args, True) print "lines",lines - ip.runlines(lines) + self.runlines(lines) except ValueError: print "Not found in recent history:", args _sentinel = object() -class ShadowHist: +class ShadowHist(object): def __init__(self,db): # cmd => idx mapping self.curidx = 0 @@ -229,7 +225,7 @@ class ShadowHist: #print "new",newidx # dbg self.db.hset('shadowhist',ent, newidx) except: - IPython.ipapi.get().IP.showtraceback() + ipapi.get().showtraceback() print "WARNING: disabling shadow history" self.disabled = True @@ -251,8 +247,8 @@ class ShadowHist: def init_ipython(ip): import ipy_completers - ip.expose_magic("rep",rep_f) - ip.expose_magic("hist",magic_hist) - ip.expose_magic("history",magic_history) + ip.define_magic("rep",rep_f) + ip.define_magic("hist",magic_hist) + ip.define_magic("history",magic_history) ipy_completers.quick_completer('%hist' ,'-g -t -r -n') diff --git a/IPython/hooks.py b/IPython/core/hooks.py similarity index 82% rename from IPython/hooks.py rename to IPython/core/hooks.py index a8481aa..60e7687 100644 --- a/IPython/hooks.py +++ b/IPython/core/hooks.py @@ -19,14 +19,14 @@ For example, suppose that you have a module called 'myiphooks' in your PYTHONPATH, which contains the following definition: import os -import IPython.ipapi -ip = IPython.ipapi.get() +from IPython.core import ipapi +ip = ipapi.get() def calljed(self,filename, linenum): "My editor hook calls the jed editor directly." print "Calling my own editor, jed ..." if os.system('jed +%d %s' % (linenum,filename)) != 0: - raise ipapi.TryNext() + raise TryNext() ip.set_hook('editor', calljed) @@ -41,22 +41,21 @@ somewhere in your configuration files or ipython command line. # the file COPYING, distributed as part of this software. #***************************************************************************** -from IPython import ipapi - -import os,bisect +import os, bisect import sys -from genutils import Term,shell +from IPython.utils.genutils import Term, shell from pprint import PrettyPrinter +from IPython.core.error import TryNext + # List here all the default hooks. For now it's just the editor functions # but over time we'll move here all the public API for user-accessible things. -# vds: >> + __all__ = ['editor', 'fix_error_editor', 'synchronize_with_editor', 'result_display', 'input_prefilter', 'shutdown_hook', 'late_startup_hook', 'generate_prompt', 'generate_output_prompt','shell_hook', 'show_in_pager','pre_prompt_hook', 'pre_runcode_hook', 'clipboard_get'] -# vds: << pformat = PrettyPrinter().pformat @@ -69,7 +68,7 @@ def editor(self,filename, linenum=None): # IPython configures a default editor at startup by reading $EDITOR from # the environment, and falling back on vi (unix) or notepad (win32). - editor = self.rc.editor + editor = self.editor # marker for at which line to open the file (for existing objects) if linenum is None or editor=='notepad': @@ -83,7 +82,7 @@ def editor(self,filename, linenum=None): # Call the actual editor if os.system('%s %s %s' % (editor,linemark,filename)) != 0: - raise ipapi.TryNext() + raise TryNext() import tempfile def fix_error_editor(self,filename,linenum,column,msg): @@ -99,20 +98,20 @@ def fix_error_editor(self,filename,linenum,column,msg): t.write('%s:%d:%d:%s\n' % (filename,linenum,column,msg)) t.flush() return t - if os.path.basename(self.rc.editor) != 'vim': + if os.path.basename(self.editor) != 'vim': self.hooks.editor(filename,linenum) return t = vim_quickfix_file() try: if os.system('vim --cmd "set errorformat=%f:%l:%c:%m" -q ' + t.name): - raise ipapi.TryNext() + raise TryNext() finally: t.close() -# vds: >> + def synchronize_with_editor(self, filename, linenum, column): - pass -# vds: << + pass + class CommandChainDispatcher: """ Dispatch calls to a chain of commands until some func can handle it @@ -140,12 +139,12 @@ class CommandChainDispatcher: try: ret = cmd(*args, **kw) return ret - except ipapi.TryNext, exc: + except TryNext, exc: if exc.args or exc.kwargs: args = exc.args kw = exc.kwargs # if no function will accept it, raise TryNext up to the caller - raise ipapi.TryNext + raise TryNext def __str__(self): return str(self.chain) @@ -160,14 +159,15 @@ class CommandChainDispatcher: Handy if the objects are not callable. """ return iter(self.chain) - + + def result_display(self,arg): """ Default display hook. Called for displaying the result to the user. """ - if self.rc.pprint: + if self.pprint: out = pformat(arg) if '\n' in out: # So that multi-line strings line up with the left column of @@ -183,6 +183,7 @@ def result_display(self,arg): # the default display hook doesn't manipulate the value to put in history return None + def input_prefilter(self,line): """ Default input prefilter @@ -197,6 +198,7 @@ def input_prefilter(self,line): #print "attempt to rewrite",line #dbg return line + def shutdown_hook(self): """ default shutdown hook @@ -206,32 +208,36 @@ def shutdown_hook(self): #print "default shutdown hook ok" # dbg return + def late_startup_hook(self): """ Executed after ipython has been constructed and configured """ #print "default startup hook ok" # dbg + def generate_prompt(self, is_continuation): """ calculate and return a string with the prompt to display """ - ip = self.api if is_continuation: - return str(ip.IP.outputcache.prompt2) - return str(ip.IP.outputcache.prompt1) + return str(self.outputcache.prompt2) + return str(self.outputcache.prompt1) + def generate_output_prompt(self): - ip = self.api - return str(ip.IP.outputcache.prompt_out) + return str(self.outputcache.prompt_out) + def shell_hook(self,cmd): """ Run system/shell command a'la os.system() """ - shell(cmd, header=self.rc.system_header, verbose=self.rc.system_verbose) + shell(cmd, header=self.system_header, verbose=self.system_verbose) + def show_in_pager(self,s): """ Run a string through pager """ # raising TryNext here will use the default paging functionality - raise ipapi.TryNext + raise TryNext + def pre_prompt_hook(self): """ Run before displaying the next prompt @@ -242,15 +248,19 @@ def pre_prompt_hook(self): return None + def pre_runcode_hook(self): """ Executed before running the (prefiltered) code in IPython """ return None + def clipboard_get(self): """ Get text from the clipboard. """ - from IPython.clipboard import (osx_clipboard_get, tkinter_clipboard_get, - win32_clipboard_get) + from IPython.lib.clipboard import ( + osx_clipboard_get, tkinter_clipboard_get, + win32_clipboard_get + ) if sys.platform == 'win32': chain = [win32_clipboard_get, tkinter_clipboard_get] elif sys.platform == 'darwin': diff --git a/IPython/core/ipapi.py b/IPython/core/ipapi.py new file mode 100644 index 0000000..8ee88a2 --- /dev/null +++ b/IPython/core/ipapi.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +This module is *completely* deprecated and should no longer be used for +any purpose. Currently, we have a few parts of the core that have +not been componentized and thus, still rely on this module. When everything +has been made into a component, this module will be sent to deathrow. +""" + +#----------------------------------------------------------------------------- +# Copyright (C) 2008-2009 The IPython Development Team +# +# Distributed under the terms of the BSD License. The full license is in +# the file COPYING, distributed as part of this software. +#----------------------------------------------------------------------------- + +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- + +from IPython.core.error import TryNext, UsageError, IPythonCoreError + +#----------------------------------------------------------------------------- +# Classes and functions +#----------------------------------------------------------------------------- + + +def get(): + """Get the most recently created InteractiveShell instance.""" + from IPython.core.iplib import InteractiveShell + insts = InteractiveShell.get_instances() + if len(insts)==0: + return None + most_recent = insts[0] + for inst in insts[1:]: + if inst.created > most_recent.created: + most_recent = inst + return most_recent diff --git a/IPython/core/ipapp.py b/IPython/core/ipapp.py new file mode 100644 index 0000000..70d76ce --- /dev/null +++ b/IPython/core/ipapp.py @@ -0,0 +1,544 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +The :class:`~IPython.core.application.Application` object for the command +line :command:`ipython` program. + +Authors: + +* Brian Granger +* Fernando Perez + +Notes +----- +""" + +#----------------------------------------------------------------------------- +# Copyright (C) 2008-2009 The IPython Development Team +# +# Distributed under the terms of the BSD License. The full license is in +# the file COPYING, distributed as part of this software. +#----------------------------------------------------------------------------- + +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- + +import logging +import os +import sys +import warnings + +from IPython.core.application import Application, BaseAppArgParseConfigLoader +from IPython.core import release +from IPython.core.iplib import InteractiveShell +from IPython.config.loader import ( + NoConfigDefault, + Config, + PyFileConfigLoader +) + +from IPython.lib import inputhook + +from IPython.utils.genutils import filefind, get_ipython_dir + +#----------------------------------------------------------------------------- +# Utilities and helpers +#----------------------------------------------------------------------------- + + +ipython_desc = """ +A Python shell with automatic history (input and output), dynamic object +introspection, easier configuration, command completion, access to the system +shell and more. +""" + +def pylab_warning(): + msg = """ + +IPython's -pylab mode has been disabled until matplotlib supports this version +of IPython. This version of IPython has greatly improved GUI integration that +matplotlib will soon be able to take advantage of. This will eventually +result in greater stability and a richer API for matplotlib under IPython. +However during this transition, you will either need to use an older version +of IPython, or do the following to use matplotlib interactively:: + + import matplotlib + matplotlib.interactive(True) + matplotlib.use('wxagg') # adjust for your backend + %gui -a wx # adjust for your GUI + from matplotlib import pyplot as plt + +See the %gui magic for information on the new interface. +""" + warnings.warn(msg, category=DeprecationWarning, stacklevel=1) + + +#----------------------------------------------------------------------------- +# Main classes and functions +#----------------------------------------------------------------------------- + +cl_args = ( + (('--autocall',), dict( + type=int, dest='InteractiveShell.autocall', default=NoConfigDefault, + help='Set the autocall value (0,1,2).', + metavar='InteractiveShell.autocall') + ), + (('--autoindent',), dict( + action='store_true', dest='InteractiveShell.autoindent', default=NoConfigDefault, + help='Turn on autoindenting.') + ), + (('--no-autoindent',), dict( + action='store_false', dest='InteractiveShell.autoindent', default=NoConfigDefault, + help='Turn off autoindenting.') + ), + (('--automagic',), dict( + action='store_true', dest='InteractiveShell.automagic', default=NoConfigDefault, + help='Turn on the auto calling of magic commands.') + ), + (('--no-automagic',), dict( + action='store_false', dest='InteractiveShell.automagic', default=NoConfigDefault, + help='Turn off the auto calling of magic commands.') + ), + (('--autoedit-syntax',), dict( + action='store_true', dest='InteractiveShell.autoedit_syntax', default=NoConfigDefault, + help='Turn on auto editing of files with syntax errors.') + ), + (('--no-autoedit-syntax',), dict( + action='store_false', dest='InteractiveShell.autoedit_syntax', default=NoConfigDefault, + help='Turn off auto editing of files with syntax errors.') + ), + (('--banner',), dict( + action='store_true', dest='Global.display_banner', default=NoConfigDefault, + help='Display a banner upon starting IPython.') + ), + (('--no-banner',), dict( + action='store_false', dest='Global.display_banner', default=NoConfigDefault, + help="Don't display a banner upon starting IPython.") + ), + (('--cache-size',), dict( + type=int, dest='InteractiveShell.cache_size', default=NoConfigDefault, + help="Set the size of the output cache.", + metavar='InteractiveShell.cache_size') + ), + (('--classic',), dict( + action='store_true', dest='Global.classic', default=NoConfigDefault, + help="Gives IPython a similar feel to the classic Python prompt.") + ), + (('--colors',), dict( + type=str, dest='InteractiveShell.colors', default=NoConfigDefault, + help="Set the color scheme (NoColor, Linux, and LightBG).", + metavar='InteractiveShell.colors') + ), + (('--color-info',), dict( + action='store_true', dest='InteractiveShell.color_info', default=NoConfigDefault, + help="Enable using colors for info related things.") + ), + (('--no-color-info',), dict( + action='store_false', dest='InteractiveShell.color_info', default=NoConfigDefault, + help="Disable using colors for info related things.") + ), + (('--confirm-exit',), dict( + action='store_true', dest='InteractiveShell.confirm_exit', default=NoConfigDefault, + help="Prompt the user when existing.") + ), + (('--no-confirm-exit',), dict( + action='store_false', dest='InteractiveShell.confirm_exit', default=NoConfigDefault, + help="Don't prompt the user when existing.") + ), + (('--deep-reload',), dict( + action='store_true', dest='InteractiveShell.deep_reload', default=NoConfigDefault, + help="Enable deep (recursive) reloading by default.") + ), + (('--no-deep-reload',), dict( + action='store_false', dest='InteractiveShell.deep_reload', default=NoConfigDefault, + help="Disable deep (recursive) reloading by default.") + ), + (('--editor',), dict( + type=str, dest='InteractiveShell.editor', default=NoConfigDefault, + help="Set the editor used by IPython (default to $EDITOR/vi/notepad).", + metavar='InteractiveShell.editor') + ), + (('--log','-l'), dict( + action='store_true', dest='InteractiveShell.logstart', default=NoConfigDefault, + help="Start logging to the default file (./ipython_log.py).") + ), + (('--logfile','-lf'), dict( + type=unicode, dest='InteractiveShell.logfile', default=NoConfigDefault, + help="Start logging to logfile.", + metavar='InteractiveShell.logfile') + ), + (('--log-append','-la'), dict( + type=unicode, dest='InteractiveShell.logappend', default=NoConfigDefault, + help="Start logging to the give file in append mode.", + metavar='InteractiveShell.logfile') + ), + (('--pdb',), dict( + action='store_true', dest='InteractiveShell.pdb', default=NoConfigDefault, + help="Enable auto calling the pdb debugger after every exception.") + ), + (('--no-pdb',), dict( + action='store_false', dest='InteractiveShell.pdb', default=NoConfigDefault, + help="Disable auto calling the pdb debugger after every exception.") + ), + (('--pprint',), dict( + action='store_true', dest='InteractiveShell.pprint', default=NoConfigDefault, + help="Enable auto pretty printing of results.") + ), + (('--no-pprint',), dict( + action='store_false', dest='InteractiveShell.pprint', default=NoConfigDefault, + help="Disable auto auto pretty printing of results.") + ), + (('--prompt-in1','-pi1'), dict( + type=str, dest='InteractiveShell.prompt_in1', default=NoConfigDefault, + help="Set the main input prompt ('In [\#]: ')", + metavar='InteractiveShell.prompt_in1') + ), + (('--prompt-in2','-pi2'), dict( + type=str, dest='InteractiveShell.prompt_in2', default=NoConfigDefault, + help="Set the secondary input prompt (' .\D.: ')", + metavar='InteractiveShell.prompt_in2') + ), + (('--prompt-out','-po'), dict( + type=str, dest='InteractiveShell.prompt_out', default=NoConfigDefault, + help="Set the output prompt ('Out[\#]:')", + metavar='InteractiveShell.prompt_out') + ), + (('--quick',), dict( + action='store_true', dest='Global.quick', default=NoConfigDefault, + help="Enable quick startup with no config files.") + ), + (('--readline',), dict( + action='store_true', dest='InteractiveShell.readline_use', default=NoConfigDefault, + help="Enable readline for command line usage.") + ), + (('--no-readline',), dict( + action='store_false', dest='InteractiveShell.readline_use', default=NoConfigDefault, + help="Disable readline for command line usage.") + ), + (('--screen-length','-sl'), dict( + type=int, dest='InteractiveShell.screen_length', default=NoConfigDefault, + help='Number of lines on screen, used to control printing of long strings.', + metavar='InteractiveShell.screen_length') + ), + (('--separate-in','-si'), dict( + type=str, dest='InteractiveShell.separate_in', default=NoConfigDefault, + help="Separator before input prompts. Default '\n'.", + metavar='InteractiveShell.separate_in') + ), + (('--separate-out','-so'), dict( + type=str, dest='InteractiveShell.separate_out', default=NoConfigDefault, + help="Separator before output prompts. Default 0 (nothing).", + metavar='InteractiveShell.separate_out') + ), + (('--separate-out2','-so2'), dict( + type=str, dest='InteractiveShell.separate_out2', default=NoConfigDefault, + help="Separator after output prompts. Default 0 (nonight).", + metavar='InteractiveShell.separate_out2') + ), + (('-no-sep',), dict( + action='store_true', dest='Global.nosep', default=NoConfigDefault, + help="Eliminate all spacing between prompts.") + ), + (('--term-title',), dict( + action='store_true', dest='InteractiveShell.term_title', default=NoConfigDefault, + help="Enable auto setting the terminal title.") + ), + (('--no-term-title',), dict( + action='store_false', dest='InteractiveShell.term_title', default=NoConfigDefault, + help="Disable auto setting the terminal title.") + ), + (('--xmode',), dict( + type=str, dest='InteractiveShell.xmode', default=NoConfigDefault, + help="Exception mode ('Plain','Context','Verbose')", + metavar='InteractiveShell.xmode') + ), + (('--ext',), dict( + type=str, dest='Global.extra_extension', default=NoConfigDefault, + help="The dotted module name of an IPython extension to load.", + metavar='Global.extra_extension') + ), + (('-c',), dict( + type=str, dest='Global.code_to_run', default=NoConfigDefault, + help="Execute the given command string.", + metavar='Global.code_to_run') + ), + (('-i',), dict( + action='store_true', dest='Global.force_interact', default=NoConfigDefault, + help="If running code from the command line, become interactive afterwards.") + ), + (('--wthread',), dict( + action='store_true', dest='Global.wthread', default=NoConfigDefault, + help="Enable wxPython event loop integration.") + ), + (('--q4thread','--qthread'), dict( + action='store_true', dest='Global.q4thread', default=NoConfigDefault, + help="Enable Qt4 event loop integration. Qt3 is no longer supported.") + ), + (('--gthread',), dict( + action='store_true', dest='Global.gthread', default=NoConfigDefault, + help="Enable GTK event loop integration.") + ), + # # These are only here to get the proper deprecation warnings + (('--pylab',), dict( + action='store_true', dest='Global.pylab', default=NoConfigDefault, + help="Disabled. Pylab has been disabled until matplotlib " + "supports this version of IPython.") + ) +) + + +class IPythonAppCLConfigLoader(BaseAppArgParseConfigLoader): + + arguments = cl_args + + +default_config_file_name = u'ipython_config.py' + + +class IPythonApp(Application): + name = u'ipython' + description = 'IPython: an enhanced interactive Python shell.' + config_file_name = default_config_file_name + + def create_default_config(self): + super(IPythonApp, self).create_default_config() + self.default_config.Global.display_banner = True + + # If the -c flag is given or a file is given to run at the cmd line + # like "ipython foo.py", normally we exit without starting the main + # loop. The force_interact config variable allows a user to override + # this and interact. It is also set by the -i cmd line flag, just + # like Python. + self.default_config.Global.force_interact = False + + # By default always interact by starting the IPython mainloop. + self.default_config.Global.interact = True + + # No GUI integration by default + self.default_config.Global.wthread = False + self.default_config.Global.q4thread = False + self.default_config.Global.gthread = False + + def create_command_line_config(self): + """Create and return a command line config loader.""" + return IPythonAppCLConfigLoader( + description=self.description, + version=release.version + ) + + def post_load_command_line_config(self): + """Do actions after loading cl config.""" + clc = self.command_line_config + + # Display the deprecation warnings about threaded shells + if hasattr(clc.Global, 'pylab'): + pylab_warning() + del clc.Global['pylab'] + + def load_file_config(self): + if hasattr(self.command_line_config.Global, 'quick'): + if self.command_line_config.Global.quick: + self.file_config = Config() + return + super(IPythonApp, self).load_file_config() + + def post_load_file_config(self): + if hasattr(self.command_line_config.Global, 'extra_extension'): + if not hasattr(self.file_config.Global, 'extensions'): + self.file_config.Global.extensions = [] + self.file_config.Global.extensions.append( + self.command_line_config.Global.extra_extension) + del self.command_line_config.Global.extra_extension + + def pre_construct(self): + config = self.master_config + + if hasattr(config.Global, 'classic'): + if config.Global.classic: + config.InteractiveShell.cache_size = 0 + config.InteractiveShell.pprint = 0 + config.InteractiveShell.prompt_in1 = '>>> ' + config.InteractiveShell.prompt_in2 = '... ' + config.InteractiveShell.prompt_out = '' + config.InteractiveShell.separate_in = \ + config.InteractiveShell.separate_out = \ + config.InteractiveShell.separate_out2 = '' + config.InteractiveShell.colors = 'NoColor' + config.InteractiveShell.xmode = 'Plain' + + if hasattr(config.Global, 'nosep'): + if config.Global.nosep: + config.InteractiveShell.separate_in = \ + config.InteractiveShell.separate_out = \ + config.InteractiveShell.separate_out2 = '' + + # if there is code of files to run from the cmd line, don't interact + # unless the -i flag (Global.force_interact) is true. + code_to_run = config.Global.get('code_to_run','') + file_to_run = False + if len(self.extra_args)>=1: + if self.extra_args[0]: + file_to_run = True + if file_to_run or code_to_run: + if not config.Global.force_interact: + config.Global.interact = False + + def construct(self): + # I am a little hesitant to put these into InteractiveShell itself. + # But that might be the place for them + sys.path.insert(0, '') + + # Create an InteractiveShell instance + self.shell = InteractiveShell( + parent=None, + config=self.master_config + ) + + def post_construct(self): + """Do actions after construct, but before starting the app.""" + config = self.master_config + + # shell.display_banner should always be False for the terminal + # based app, because we call shell.show_banner() by hand below + # so the banner shows *before* all extension loading stuff. + self.shell.display_banner = False + + if config.Global.display_banner and \ + config.Global.interact: + self.shell.show_banner() + + # Make sure there is a space below the banner. + if self.log_level <= logging.INFO: print + + # Now a variety of things that happen after the banner is printed. + self._enable_gui() + self._load_extensions() + self._run_exec_lines() + self._run_exec_files() + self._run_cmd_line_code() + + def _enable_gui(self): + """Enable GUI event loop integration.""" + config = self.master_config + try: + # Enable GUI integration + if config.Global.wthread: + self.log.info("Enabling wx GUI event loop integration") + inputhook.enable_wx(app=True) + elif config.Global.q4thread: + self.log.info("Enabling Qt4 GUI event loop integration") + inputhook.enable_qt4(app=True) + elif config.Global.gthread: + self.log.info("Enabling GTK GUI event loop integration") + inputhook.enable_gtk(app=True) + except: + self.log.warn("Error in enabling GUI event loop integration:") + self.shell.showtraceback() + + def _load_extensions(self): + """Load all IPython extensions in Global.extensions. + + This uses the :meth:`InteractiveShell.load_extensions` to load all + the extensions listed in ``self.master_config.Global.extensions``. + """ + try: + if hasattr(self.master_config.Global, 'extensions'): + self.log.debug("Loading IPython extensions...") + extensions = self.master_config.Global.extensions + for ext in extensions: + try: + self.log.info("Loading IPython extension: %s" % ext) + self.shell.load_extension(ext) + except: + self.log.warn("Error in loading extension: %s" % ext) + self.shell.showtraceback() + except: + self.log.warn("Unknown error in loading extensions:") + self.shell.showtraceback() + + def _run_exec_lines(self): + """Run lines of code in Global.exec_lines in the user's namespace.""" + try: + if hasattr(self.master_config.Global, 'exec_lines'): + self.log.debug("Running code from Global.exec_lines...") + exec_lines = self.master_config.Global.exec_lines + for line in exec_lines: + try: + self.log.info("Running code in user namespace: %s" % line) + self.shell.runlines(line) + except: + self.log.warn("Error in executing line in user namespace: %s" % line) + self.shell.showtraceback() + except: + self.log.warn("Unknown error in handling Global.exec_lines:") + self.shell.showtraceback() + + def _exec_file(self, fname): + full_filename = filefind(fname, [u'.', self.ipython_dir]) + if os.path.isfile(full_filename): + if full_filename.endswith(u'.py'): + self.log.info("Running file in user namespace: %s" % full_filename) + self.shell.safe_execfile(full_filename, self.shell.user_ns) + elif full_filename.endswith('.ipy'): + self.log.info("Running file in user namespace: %s" % full_filename) + self.shell.safe_execfile_ipy(full_filename) + else: + self.log.warn("File does not have a .py or .ipy extension: <%s>" % full_filename) + + def _run_exec_files(self): + try: + if hasattr(self.master_config.Global, 'exec_files'): + self.log.debug("Running files in Global.exec_files...") + exec_files = self.master_config.Global.exec_files + for fname in exec_files: + self._exec_file(fname) + except: + self.log.warn("Unknown error in handling Global.exec_files:") + self.shell.showtraceback() + + def _run_cmd_line_code(self): + if hasattr(self.master_config.Global, 'code_to_run'): + line = self.master_config.Global.code_to_run + try: + self.log.info("Running code given at command line (-c): %s" % line) + self.shell.runlines(line) + except: + self.log.warn("Error in executing line in user namespace: %s" % line) + self.shell.showtraceback() + return + # Like Python itself, ignore the second if the first of these is present + try: + fname = self.extra_args[0] + except: + pass + else: + try: + self._exec_file(fname) + except: + self.log.warn("Error in executing file in user namespace: %s" % fname) + self.shell.showtraceback() + + def start_app(self): + if self.master_config.Global.interact: + self.log.debug("Starting IPython's mainloop...") + self.shell.mainloop() + + +def load_default_config(ipython_dir=None): + """Load the default config file from the default ipython_dir. + + This is useful for embedded shells. + """ + if ipython_dir is None: + ipython_dir = get_ipython_dir() + cl = PyFileConfigLoader(default_config_file_name, ipython_dir) + config = cl.load_config() + return config + + +def launch_new_instance(): + """Create and run a full blown IPython instance""" + app = IPythonApp() + app.start() + diff --git a/IPython/core/iplib.py b/IPython/core/iplib.py new file mode 100644 index 0000000..de12b2d --- /dev/null +++ b/IPython/core/iplib.py @@ -0,0 +1,2488 @@ +# -*- coding: utf-8 -*- +""" +Main IPython Component +""" + +#----------------------------------------------------------------------------- +# Copyright (C) 2001 Janko Hauser +# Copyright (C) 2001-2007 Fernando Perez. +# Copyright (C) 2008-2009 The IPython Development Team +# +# Distributed under the terms of the BSD License. The full license is in +# the file COPYING, distributed as part of this software. +#----------------------------------------------------------------------------- + +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- + +from __future__ import with_statement + +import __builtin__ +import StringIO +import bdb +import codeop +import exceptions +import new +import os +import re +import string +import sys +import tempfile +from contextlib import nested + +from IPython.core import ultratb +from IPython.core import debugger, oinspect +from IPython.core import shadowns +from IPython.core import history as ipcorehist +from IPython.core import prefilter +from IPython.core.alias import AliasManager +from IPython.core.builtin_trap import BuiltinTrap +from IPython.core.display_trap import DisplayTrap +from IPython.core.fakemodule import FakeModule, init_fakemod_dict +from IPython.core.logger import Logger +from IPython.core.magic import Magic +from IPython.core.prompts import CachedOutput +from IPython.core.prefilter import PrefilterManager +from IPython.core.component import Component +from IPython.core.usage import interactive_usage, default_banner +from IPython.core.error import TryNext, UsageError + +from IPython.utils import pickleshare +from IPython.external.Itpl import ItplNS +from IPython.lib.backgroundjobs import BackgroundJobManager +from IPython.utils.ipstruct import Struct +from IPython.utils import PyColorize +from IPython.utils.genutils import * +from IPython.utils.genutils import get_ipython_dir +from IPython.utils.platutils import toggle_set_term_title, set_term_title +from IPython.utils.strdispatch import StrDispatch +from IPython.utils.syspathcontext import prepended_to_syspath + +# from IPython.utils import growl +# growl.start("IPython") + +from IPython.utils.traitlets import ( + Int, Str, CBool, CaselessStrEnum, Enum, List, Unicode +) + +#----------------------------------------------------------------------------- +# Globals +#----------------------------------------------------------------------------- + + +# store the builtin raw_input globally, and use this always, in case user code +# overwrites it (like wx.py.PyShell does) +raw_input_original = raw_input + +# compiled regexps for autoindent management +dedent_re = re.compile(r'^\s+raise|^\s+return|^\s+pass') + + +#----------------------------------------------------------------------------- +# Utilities +#----------------------------------------------------------------------------- + + +ini_spaces_re = re.compile(r'^(\s+)') + + +def num_ini_spaces(strng): + """Return the number of initial spaces in a string""" + + ini_spaces = ini_spaces_re.match(strng) + if ini_spaces: + return ini_spaces.end() + else: + return 0 + + +def softspace(file, newvalue): + """Copied from code.py, to remove the dependency""" + + oldvalue = 0 + try: + oldvalue = file.softspace + except AttributeError: + pass + try: + file.softspace = newvalue + except (AttributeError, TypeError): + # "attribute-less object" or "read-only attributes" + pass + return oldvalue + + +class SpaceInInput(exceptions.Exception): pass + +class Bunch: pass + +class InputList(list): + """Class to store user input. + + It's basically a list, but slices return a string instead of a list, thus + allowing things like (assuming 'In' is an instance): + + exec In[4:7] + + or + + exec In[5:9] + In[14] + In[21:25]""" + + def __getslice__(self,i,j): + return ''.join(list.__getslice__(self,i,j)) + + +class SyntaxTB(ultratb.ListTB): + """Extension which holds some state: the last exception value""" + + def __init__(self,color_scheme = 'NoColor'): + ultratb.ListTB.__init__(self,color_scheme) + self.last_syntax_error = None + + def __call__(self, etype, value, elist): + self.last_syntax_error = value + ultratb.ListTB.__call__(self,etype,value,elist) + + def clear_err_state(self): + """Return the current error state and clear it""" + e = self.last_syntax_error + self.last_syntax_error = None + return e + + +def get_default_editor(): + try: + ed = os.environ['EDITOR'] + except KeyError: + if os.name == 'posix': + ed = 'vi' # the only one guaranteed to be there! + else: + ed = 'notepad' # same in Windows! + return ed + + +def get_default_colors(): + if sys.platform=='darwin': + return "LightBG" + elif os.name=='nt': + return 'Linux' + else: + return 'Linux' + + +class SeparateStr(Str): + """A Str subclass to validate separate_in, separate_out, etc. + + This is a Str based traitlet that converts '0'->'' and '\\n'->'\n'. + """ + + def validate(self, obj, value): + if value == '0': value = '' + value = value.replace('\\n','\n') + return super(SeparateStr, self).validate(obj, value) + + +#----------------------------------------------------------------------------- +# Main IPython class +#----------------------------------------------------------------------------- + + +class InteractiveShell(Component, Magic): + """An enhanced, interactive shell for Python.""" + + autocall = Enum((0,1,2), default_value=1, config=True) + autoedit_syntax = CBool(False, config=True) + autoindent = CBool(True, config=True) + automagic = CBool(True, config=True) + banner = Str('') + banner1 = Str(default_banner, config=True) + banner2 = Str('', config=True) + cache_size = Int(1000, config=True) + color_info = CBool(True, config=True) + colors = CaselessStrEnum(('NoColor','LightBG','Linux'), + default_value=get_default_colors(), config=True) + confirm_exit = CBool(True, config=True) + debug = CBool(False, config=True) + deep_reload = CBool(False, config=True) + # This display_banner only controls whether or not self.show_banner() + # is called when mainloop/interact are called. The default is False + # because for the terminal based application, the banner behavior + # is controlled by Global.display_banner, which IPythonApp looks at + # to determine if *it* should call show_banner() by hand or not. + display_banner = CBool(False) # This isn't configurable! + embedded = CBool(False) + embedded_active = CBool(False) + editor = Str(get_default_editor(), config=True) + filename = Str("") + ipython_dir= Unicode('', config=True) # Set to get_ipython_dir() in __init__ + logstart = CBool(False, config=True) + logfile = Str('', config=True) + logappend = Str('', config=True) + object_info_string_level = Enum((0,1,2), default_value=0, + config=True) + pager = Str('less', config=True) + pdb = CBool(False, config=True) + pprint = CBool(True, config=True) + profile = Str('', config=True) + prompt_in1 = Str('In [\\#]: ', config=True) + prompt_in2 = Str(' .\\D.: ', config=True) + prompt_out = Str('Out[\\#]: ', config=True) + prompts_pad_left = CBool(True, config=True) + quiet = CBool(False, config=True) + + readline_use = CBool(True, config=True) + readline_merge_completions = CBool(True, config=True) + readline_omit__names = Enum((0,1,2), default_value=0, config=True) + readline_remove_delims = Str('-/~', config=True) + readline_parse_and_bind = List([ + 'tab: complete', + '"\C-l": possible-completions', + 'set show-all-if-ambiguous on', + '"\C-o": tab-insert', + '"\M-i": " "', + '"\M-o": "\d\d\d\d"', + '"\M-I": "\d\d\d\d"', + '"\C-r": reverse-search-history', + '"\C-s": forward-search-history', + '"\C-p": history-search-backward', + '"\C-n": history-search-forward', + '"\e[A": history-search-backward', + '"\e[B": history-search-forward', + '"\C-k": kill-line', + '"\C-u": unix-line-discard', + ], allow_none=False, config=True) + + screen_length = Int(0, config=True) + + # Use custom TraitletTypes that convert '0'->'' and '\\n'->'\n' + separate_in = SeparateStr('\n', config=True) + separate_out = SeparateStr('', config=True) + separate_out2 = SeparateStr('', config=True) + + system_header = Str('IPython system call: ', config=True) + system_verbose = CBool(False, config=True) + term_title = CBool(False, config=True) + wildcards_case_sensitive = CBool(True, config=True) + xmode = CaselessStrEnum(('Context','Plain', 'Verbose'), + default_value='Context', config=True) + + autoexec = List(allow_none=False) + + # class attribute to indicate whether the class supports threads or not. + # Subclasses with thread support should override this as needed. + isthreaded = False + + def __init__(self, parent=None, config=None, ipython_dir=None, usage=None, + user_ns=None, user_global_ns=None, + banner1=None, banner2=None, display_banner=None, + custom_exceptions=((),None)): + + # This is where traitlets with a config_key argument are updated + # from the values on config. + super(InteractiveShell, self).__init__(parent, config=config) + + # These are relatively independent and stateless + self.init_ipython_dir(ipython_dir) + self.init_instance_attrs() + self.init_term_title() + self.init_usage(usage) + self.init_banner(banner1, banner2, display_banner) + + # Create namespaces (user_ns, user_global_ns, etc.) + self.init_create_namespaces(user_ns, user_global_ns) + # This has to be done after init_create_namespaces because it uses + # something in self.user_ns, but before init_sys_modules, which + # is the first thing to modify sys. + self.save_sys_module_state() + self.init_sys_modules() + + self.init_history() + self.init_encoding() + self.init_prefilter() + + Magic.__init__(self, self) + + self.init_syntax_highlighting() + self.init_hooks() + self.init_pushd_popd_magic() + self.init_traceback_handlers(custom_exceptions) + self.init_user_ns() + self.init_logger() + self.init_alias() + self.init_builtins() + + # pre_config_initialization + self.init_shadow_hist() + + # The next section should contain averything that was in ipmaker. + self.init_logstart() + + # The following was in post_config_initialization + self.init_inspector() + self.init_readline() + self.init_prompts() + self.init_displayhook() + self.init_reload_doctest() + self.init_magics() + self.init_pdb() + self.hooks.late_startup_hook() + + def get_ipython(self): + return self + + #------------------------------------------------------------------------- + # Traitlet changed handlers + #------------------------------------------------------------------------- + + def _banner1_changed(self): + self.compute_banner() + + def _banner2_changed(self): + self.compute_banner() + + def _ipython_dir_changed(self, name, new): + if not os.path.isdir(new): + os.makedirs(new, mode = 0777) + if not os.path.isdir(self.ipython_extension_dir): + os.makedirs(self.ipython_extension_dir, mode = 0777) + + @property + def ipython_extension_dir(self): + return os.path.join(self.ipython_dir, 'extensions') + + @property + def usable_screen_length(self): + if self.screen_length == 0: + return 0 + else: + num_lines_bot = self.separate_in.count('\n')+1 + return self.screen_length - num_lines_bot + + def _term_title_changed(self, name, new_value): + self.init_term_title() + + def set_autoindent(self,value=None): + """Set the autoindent flag, checking for readline support. + + If called with no arguments, it acts as a toggle.""" + + if not self.has_readline: + if os.name == 'posix': + warn("The auto-indent feature requires the readline library") + self.autoindent = 0 + return + if value is None: + self.autoindent = not self.autoindent + else: + self.autoindent = value + + #------------------------------------------------------------------------- + # init_* methods called by __init__ + #------------------------------------------------------------------------- + + def init_ipython_dir(self, ipython_dir): + if ipython_dir is not None: + self.ipython_dir = ipython_dir + self.config.Global.ipython_dir = self.ipython_dir + return + + if hasattr(self.config.Global, 'ipython_dir'): + self.ipython_dir = self.config.Global.ipython_dir + else: + self.ipython_dir = get_ipython_dir() + + # All children can just read this + self.config.Global.ipython_dir = self.ipython_dir + + def init_instance_attrs(self): + self.jobs = BackgroundJobManager() + self.more = False + + # command compiler + self.compile = codeop.CommandCompiler() + + # User input buffer + self.buffer = [] + + # Make an empty namespace, which extension writers can rely on both + # existing and NEVER being used by ipython itself. This gives them a + # convenient location for storing additional information and state + # their extensions may require, without fear of collisions with other + # ipython names that may develop later. + self.meta = Struct() + + # Object variable to store code object waiting execution. This is + # used mainly by the multithreaded shells, but it can come in handy in + # other situations. No need to use a Queue here, since it's a single + # item which gets cleared once run. + self.code_to_run = None + + # Flag to mark unconditional exit + self.exit_now = False + + # Temporary files used for various purposes. Deleted at exit. + self.tempfiles = [] + + # Keep track of readline usage (later set by init_readline) + self.has_readline = False + + # keep track of where we started running (mainly for crash post-mortem) + # This is not being used anywhere currently. + self.starting_dir = os.getcwd() + + # Indentation management + self.indent_current_nsp = 0 + + def init_term_title(self): + # Enable or disable the terminal title. + if self.term_title: + toggle_set_term_title(True) + set_term_title('IPython: ' + abbrev_cwd()) + else: + toggle_set_term_title(False) + + def init_usage(self, usage=None): + if usage is None: + self.usage = interactive_usage + else: + self.usage = usage + + def init_encoding(self): + # Get system encoding at startup time. Certain terminals (like Emacs + # under Win32 have it set to None, and we need to have a known valid + # encoding to use in the raw_input() method + try: + self.stdin_encoding = sys.stdin.encoding or 'ascii' + except AttributeError: + self.stdin_encoding = 'ascii' + + def init_syntax_highlighting(self): + # Python source parser/formatter for syntax highlighting + pyformat = PyColorize.Parser().format + self.pycolorize = lambda src: pyformat(src,'str',self.colors) + + def init_pushd_popd_magic(self): + # for pushd/popd management + try: + self.home_dir = get_home_dir() + except HomeDirError, msg: + fatal(msg) + + self.dir_stack = [] + + def init_logger(self): + self.logger = Logger(self, logfname='ipython_log.py', logmode='rotate') + # local shortcut, this is used a LOT + self.log = self.logger.log + + def init_logstart(self): + if self.logappend: + self.magic_logstart(self.logappend + ' append') + elif self.logfile: + self.magic_logstart(self.logfile) + elif self.logstart: + self.magic_logstart() + + def init_builtins(self): + self.builtin_trap = BuiltinTrap(self) + + def init_inspector(self): + # Object inspector + self.inspector = oinspect.Inspector(oinspect.InspectColors, + PyColorize.ANSICodeColors, + 'NoColor', + self.object_info_string_level) + + def init_prompts(self): + # Initialize cache, set in/out prompts and printing system + self.outputcache = CachedOutput(self, + self.cache_size, + self.pprint, + input_sep = self.separate_in, + output_sep = self.separate_out, + output_sep2 = self.separate_out2, + ps1 = self.prompt_in1, + ps2 = self.prompt_in2, + ps_out = self.prompt_out, + pad_left = self.prompts_pad_left) + + # user may have over-ridden the default print hook: + try: + self.outputcache.__class__.display = self.hooks.display + except AttributeError: + pass + + def init_displayhook(self): + self.display_trap = DisplayTrap(self, self.outputcache) + + def init_reload_doctest(self): + # Do a proper resetting of doctest, including the necessary displayhook + # monkeypatching + try: + doctest_reload() + except ImportError: + warn("doctest module does not exist.") + + #------------------------------------------------------------------------- + # Things related to the banner + #------------------------------------------------------------------------- + + def init_banner(self, banner1, banner2, display_banner): + if banner1 is not None: + self.banner1 = banner1 + if banner2 is not None: + self.banner2 = banner2 + if display_banner is not None: + self.display_banner = display_banner + self.compute_banner() + + def show_banner(self, banner=None): + if banner is None: + banner = self.banner + self.write(banner) + + def compute_banner(self): + self.banner = self.banner1 + '\n' + if self.profile: + self.banner += '\nIPython profile: %s\n' % self.profile + if self.banner2: + self.banner += '\n' + self.banner2 + '\n' + + #------------------------------------------------------------------------- + # Things related to injections into the sys module + #------------------------------------------------------------------------- + + def save_sys_module_state(self): + """Save the state of hooks in the sys module. + + This has to be called after self.user_ns is created. + """ + self._orig_sys_module_state = {} + self._orig_sys_module_state['stdin'] = sys.stdin + self._orig_sys_module_state['stdout'] = sys.stdout + self._orig_sys_module_state['stderr'] = sys.stderr + self._orig_sys_module_state['excepthook'] = sys.excepthook + try: + self._orig_sys_modules_main_name = self.user_ns['__name__'] + except KeyError: + pass + + def restore_sys_module_state(self): + """Restore the state of the sys module.""" + try: + for k, v in self._orig_sys_module_state.items(): + setattr(sys, k, v) + except AttributeError: + pass + try: + delattr(sys, 'ipcompleter') + except AttributeError: + pass + # Reset what what done in self.init_sys_modules + try: + sys.modules[self.user_ns['__name__']] = self._orig_sys_modules_main_name + except (AttributeError, KeyError): + pass + + #------------------------------------------------------------------------- + # Things related to hooks + #------------------------------------------------------------------------- + + def init_hooks(self): + # hooks holds pointers used for user-side customizations + self.hooks = Struct() + + self.strdispatchers = {} + + # Set all default hooks, defined in the IPython.hooks module. + import IPython.core.hooks + hooks = IPython.core.hooks + for hook_name in hooks.__all__: + # default hooks have priority 100, i.e. low; user hooks should have + # 0-100 priority + self.set_hook(hook_name,getattr(hooks,hook_name), 100) + + def set_hook(self,name,hook, priority = 50, str_key = None, re_key = None): + """set_hook(name,hook) -> sets an internal IPython hook. + + IPython exposes some of its internal API as user-modifiable hooks. By + adding your function to one of these hooks, you can modify IPython's + behavior to call at runtime your own routines.""" + + # At some point in the future, this should validate the hook before it + # accepts it. Probably at least check that the hook takes the number + # of args it's supposed to. + + f = new.instancemethod(hook,self,self.__class__) + + # check if the hook is for strdispatcher first + if str_key is not None: + sdp = self.strdispatchers.get(name, StrDispatch()) + sdp.add_s(str_key, f, priority ) + self.strdispatchers[name] = sdp + return + if re_key is not None: + sdp = self.strdispatchers.get(name, StrDispatch()) + sdp.add_re(re.compile(re_key), f, priority ) + self.strdispatchers[name] = sdp + return + + dp = getattr(self.hooks, name, None) + if name not in IPython.core.hooks.__all__: + print "Warning! Hook '%s' is not one of %s" % (name, IPython.core.hooks.__all__ ) + if not dp: + dp = IPython.core.hooks.CommandChainDispatcher() + + try: + dp.add(f,priority) + except AttributeError: + # it was not commandchain, plain old func - replace + dp = f + + setattr(self.hooks,name, dp) + + #------------------------------------------------------------------------- + # Things related to the "main" module + #------------------------------------------------------------------------- + + def new_main_mod(self,ns=None): + """Return a new 'main' module object for user code execution. + """ + main_mod = self._user_main_module + init_fakemod_dict(main_mod,ns) + return main_mod + + def cache_main_mod(self,ns,fname): + """Cache a main module's namespace. + + When scripts are executed via %run, we must keep a reference to the + namespace of their __main__ module (a FakeModule instance) around so + that Python doesn't clear it, rendering objects defined therein + useless. + + This method keeps said reference in a private dict, keyed by the + absolute path of the module object (which corresponds to the script + path). This way, for multiple executions of the same script we only + keep one copy of the namespace (the last one), thus preventing memory + leaks from old references while allowing the objects from the last + execution to be accessible. + + Note: we can not allow the actual FakeModule instances to be deleted, + because of how Python tears down modules (it hard-sets all their + references to None without regard for reference counts). This method + must therefore make a *copy* of the given namespace, to allow the + original module's __dict__ to be cleared and reused. + + + Parameters + ---------- + ns : a namespace (a dict, typically) + + fname : str + Filename associated with the namespace. + + Examples + -------- + + In [10]: import IPython + + In [11]: _ip.cache_main_mod(IPython.__dict__,IPython.__file__) + + In [12]: IPython.__file__ in _ip._main_ns_cache + Out[12]: True + """ + self._main_ns_cache[os.path.abspath(fname)] = ns.copy() + + def clear_main_mod_cache(self): + """Clear the cache of main modules. + + Mainly for use by utilities like %reset. + + Examples + -------- + + In [15]: import IPython + + In [16]: _ip.cache_main_mod(IPython.__dict__,IPython.__file__) + + In [17]: len(_ip._main_ns_cache) > 0 + Out[17]: True + + In [18]: _ip.clear_main_mod_cache() + + In [19]: len(_ip._main_ns_cache) == 0 + Out[19]: True + """ + self._main_ns_cache.clear() + + #------------------------------------------------------------------------- + # Things related to debugging + #------------------------------------------------------------------------- + + def init_pdb(self): + # Set calling of pdb on exceptions + # self.call_pdb is a property + self.call_pdb = self.pdb + + def _get_call_pdb(self): + return self._call_pdb + + def _set_call_pdb(self,val): + + if val not in (0,1,False,True): + raise ValueError,'new call_pdb value must be boolean' + + # store value in instance + self._call_pdb = val + + # notify the actual exception handlers + self.InteractiveTB.call_pdb = val + if self.isthreaded: + try: + self.sys_excepthook.call_pdb = val + except: + warn('Failed to activate pdb for threaded exception handler') + + call_pdb = property(_get_call_pdb,_set_call_pdb,None, + 'Control auto-activation of pdb at exceptions') + + def debugger(self,force=False): + """Call the pydb/pdb debugger. + + Keywords: + + - force(False): by default, this routine checks the instance call_pdb + flag and does not actually invoke the debugger if the flag is false. + The 'force' option forces the debugger to activate even if the flag + is false. + """ + + if not (force or self.call_pdb): + return + + if not hasattr(sys,'last_traceback'): + error('No traceback has been produced, nothing to debug.') + return + + # use pydb if available + if debugger.has_pydb: + from pydb import pm + else: + # fallback to our internal debugger + pm = lambda : self.InteractiveTB.debugger(force=True) + self.history_saving_wrapper(pm)() + + #------------------------------------------------------------------------- + # Things related to IPython's various namespaces + #------------------------------------------------------------------------- + + def init_create_namespaces(self, user_ns=None, user_global_ns=None): + # Create the namespace where the user will operate. user_ns is + # normally the only one used, and it is passed to the exec calls as + # the locals argument. But we do carry a user_global_ns namespace + # given as the exec 'globals' argument, This is useful in embedding + # situations where the ipython shell opens in a context where the + # distinction between locals and globals is meaningful. For + # non-embedded contexts, it is just the same object as the user_ns dict. + + # FIXME. For some strange reason, __builtins__ is showing up at user + # level as a dict instead of a module. This is a manual fix, but I + # should really track down where the problem is coming from. Alex + # Schmolck reported this problem first. + + # A useful post by Alex Martelli on this topic: + # Re: inconsistent value from __builtins__ + # Von: Alex Martelli + # Datum: Freitag 01 Oktober 2004 04:45:34 nachmittags/abends + # Gruppen: comp.lang.python + + # Michael Hohn wrote: + # > >>> print type(builtin_check.get_global_binding('__builtins__')) + # > + # > >>> print type(__builtins__) + # > + # > Is this difference in return value intentional? + + # Well, it's documented that '__builtins__' can be either a dictionary + # or a module, and it's been that way for a long time. Whether it's + # intentional (or sensible), I don't know. In any case, the idea is + # that if you need to access the built-in namespace directly, you + # should start with "import __builtin__" (note, no 's') which will + # definitely give you a module. Yeah, it's somewhat confusing:-(. + + # These routines return properly built dicts as needed by the rest of + # the code, and can also be used by extension writers to generate + # properly initialized namespaces. + user_ns, user_global_ns = self.make_user_namespaces(user_ns, + user_global_ns) + + # Assign namespaces + # This is the namespace where all normal user variables live + self.user_ns = user_ns + self.user_global_ns = user_global_ns + + # An auxiliary namespace that checks what parts of the user_ns were + # loaded at startup, so we can list later only variables defined in + # actual interactive use. Since it is always a subset of user_ns, it + # doesn't need to be seaparately tracked in the ns_table + self.user_config_ns = {} + + # A namespace to keep track of internal data structures to prevent + # them from cluttering user-visible stuff. Will be updated later + self.internal_ns = {} + + # Now that FakeModule produces a real module, we've run into a nasty + # problem: after script execution (via %run), the module where the user + # code ran is deleted. Now that this object is a true module (needed + # so docetst and other tools work correctly), the Python module + # teardown mechanism runs over it, and sets to None every variable + # present in that module. Top-level references to objects from the + # script survive, because the user_ns is updated with them. However, + # calling functions defined in the script that use other things from + # the script will fail, because the function's closure had references + # to the original objects, which are now all None. So we must protect + # these modules from deletion by keeping a cache. + # + # To avoid keeping stale modules around (we only need the one from the + # last run), we use a dict keyed with the full path to the script, so + # only the last version of the module is held in the cache. Note, + # however, that we must cache the module *namespace contents* (their + # __dict__). Because if we try to cache the actual modules, old ones + # (uncached) could be destroyed while still holding references (such as + # those held by GUI objects that tend to be long-lived)> + # + # The %reset command will flush this cache. See the cache_main_mod() + # and clear_main_mod_cache() methods for details on use. + + # This is the cache used for 'main' namespaces + self._main_ns_cache = {} + # And this is the single instance of FakeModule whose __dict__ we keep + # copying and clearing for reuse on each %run + self._user_main_module = FakeModule() + + # A table holding all the namespaces IPython deals with, so that + # introspection facilities can search easily. + self.ns_table = {'user':user_ns, + 'user_global':user_global_ns, + 'internal':self.internal_ns, + 'builtin':__builtin__.__dict__ + } + + # Similarly, track all namespaces where references can be held and that + # we can safely clear (so it can NOT include builtin). This one can be + # a simple list. + self.ns_refs_table = [ user_ns, user_global_ns, self.user_config_ns, + self.internal_ns, self._main_ns_cache ] + + def init_sys_modules(self): + # We need to insert into sys.modules something that looks like a + # module but which accesses the IPython namespace, for shelve and + # pickle to work interactively. Normally they rely on getting + # everything out of __main__, but for embedding purposes each IPython + # instance has its own private namespace, so we can't go shoving + # everything into __main__. + + # note, however, that we should only do this for non-embedded + # ipythons, which really mimic the __main__.__dict__ with their own + # namespace. Embedded instances, on the other hand, should not do + # this because they need to manage the user local/global namespaces + # only, but they live within a 'normal' __main__ (meaning, they + # shouldn't overtake the execution environment of the script they're + # embedded in). + + # This is overridden in the InteractiveShellEmbed subclass to a no-op. + + try: + main_name = self.user_ns['__name__'] + except KeyError: + raise KeyError('user_ns dictionary MUST have a "__name__" key') + else: + sys.modules[main_name] = FakeModule(self.user_ns) + + def make_user_namespaces(self, user_ns=None, user_global_ns=None): + """Return a valid local and global user interactive namespaces. + + This builds a dict with the minimal information needed to operate as a + valid IPython user namespace, which you can pass to the various + embedding classes in ipython. The default implementation returns the + same dict for both the locals and the globals to allow functions to + refer to variables in the namespace. Customized implementations can + return different dicts. The locals dictionary can actually be anything + following the basic mapping protocol of a dict, but the globals dict + must be a true dict, not even a subclass. It is recommended that any + custom object for the locals namespace synchronize with the globals + dict somehow. + + Raises TypeError if the provided globals namespace is not a true dict. + + :Parameters: + user_ns : dict-like, optional + The current user namespace. The items in this namespace should + be included in the output. If None, an appropriate blank + namespace should be created. + user_global_ns : dict, optional + The current user global namespace. The items in this namespace + should be included in the output. If None, an appropriate + blank namespace should be created. + + :Returns: + A tuple pair of dictionary-like object to be used as the local namespace + of the interpreter and a dict to be used as the global namespace. + """ + + if user_ns is None: + # Set __name__ to __main__ to better match the behavior of the + # normal interpreter. + user_ns = {'__name__' :'__main__', + '__builtins__' : __builtin__, + } + else: + user_ns.setdefault('__name__','__main__') + user_ns.setdefault('__builtins__',__builtin__) + + if user_global_ns is None: + user_global_ns = user_ns + if type(user_global_ns) is not dict: + raise TypeError("user_global_ns must be a true dict; got %r" + % type(user_global_ns)) + + return user_ns, user_global_ns + + def init_user_ns(self): + """Initialize all user-visible namespaces to their minimum defaults. + + Certain history lists are also initialized here, as they effectively + act as user namespaces. + + Notes + ----- + All data structures here are only filled in, they are NOT reset by this + method. If they were not empty before, data will simply be added to + therm. + """ + # Store myself as the public api!!! + self.user_ns['get_ipython'] = self.get_ipython + + # make global variables for user access to the histories + self.user_ns['_ih'] = self.input_hist + self.user_ns['_oh'] = self.output_hist + self.user_ns['_dh'] = self.dir_hist + + # user aliases to input and output histories + self.user_ns['In'] = self.input_hist + self.user_ns['Out'] = self.output_hist + + self.user_ns['_sh'] = shadowns + + # Put 'help' in the user namespace + try: + from site import _Helper + self.user_ns['help'] = _Helper() + except ImportError: + warn('help() not available - check site.py') + + def reset(self): + """Clear all internal namespaces. + + Note that this is much more aggressive than %reset, since it clears + fully all namespaces, as well as all input/output lists. + """ + for ns in self.ns_refs_table: + ns.clear() + + self.alias_manager.clear_aliases() + + # Clear input and output histories + self.input_hist[:] = [] + self.input_hist_raw[:] = [] + self.output_hist.clear() + + # Restore the user namespaces to minimal usability + self.init_user_ns() + + # Restore the default and user aliases + self.alias_manager.init_aliases() + + def push(self, variables, interactive=True): + """Inject a group of variables into the IPython user namespace. + + Parameters + ---------- + variables : dict, str or list/tuple of str + The variables to inject into the user's namespace. If a dict, + a simple update is done. If a str, the string is assumed to + have variable names separated by spaces. A list/tuple of str + can also be used to give the variable names. If just the variable + names are give (list/tuple/str) then the variable values looked + up in the callers frame. + interactive : bool + If True (default), the variables will be listed with the ``who`` + magic. + """ + vdict = None + + # We need a dict of name/value pairs to do namespace updates. + if isinstance(variables, dict): + vdict = variables + elif isinstance(variables, (basestring, list, tuple)): + if isinstance(variables, basestring): + vlist = variables.split() + else: + vlist = variables + vdict = {} + cf = sys._getframe(1) + for name in vlist: + try: + vdict[name] = eval(name, cf.f_globals, cf.f_locals) + except: + print ('Could not get variable %s from %s' % + (name,cf.f_code.co_name)) + else: + raise ValueError('variables must be a dict/str/list/tuple') + + # Propagate variables to user namespace + self.user_ns.update(vdict) + + # And configure interactive visibility + config_ns = self.user_config_ns + if interactive: + for name, val in vdict.iteritems(): + config_ns.pop(name, None) + else: + for name,val in vdict.iteritems(): + config_ns[name] = val + + #------------------------------------------------------------------------- + # Things related to history management + #------------------------------------------------------------------------- + + def init_history(self): + # List of input with multi-line handling. + self.input_hist = InputList() + # This one will hold the 'raw' input history, without any + # pre-processing. This will allow users to retrieve the input just as + # it was exactly typed in by the user, with %hist -r. + self.input_hist_raw = InputList() + + # list of visited directories + try: + self.dir_hist = [os.getcwd()] + except OSError: + self.dir_hist = [] + + # dict of output history + self.output_hist = {} + + # Now the history file + if self.profile: + histfname = 'history-%s' % self.profile + else: + histfname = 'history' + self.histfile = os.path.join(self.ipython_dir, histfname) + + # Fill the history zero entry, user counter starts at 1 + self.input_hist.append('\n') + self.input_hist_raw.append('\n') + + def init_shadow_hist(self): + try: + self.db = pickleshare.PickleShareDB(self.ipython_dir + "/db") + except exceptions.UnicodeDecodeError: + print "Your ipython_dir can't be decoded to unicode!" + print "Please set HOME environment variable to something that" + print r"only has ASCII characters, e.g. c:\home" + print "Now it is", self.ipython_dir + sys.exit() + self.shadowhist = ipcorehist.ShadowHist(self.db) + + def savehist(self): + """Save input history to a file (via readline library).""" + + if not self.has_readline: + return + + try: + self.readline.write_history_file(self.histfile) + except: + print 'Unable to save IPython command history to file: ' + \ + `self.histfile` + + def reloadhist(self): + """Reload the input history from disk file.""" + + if self.has_readline: + try: + self.readline.clear_history() + self.readline.read_history_file(self.shell.histfile) + except AttributeError: + pass + + def history_saving_wrapper(self, func): + """ Wrap func for readline history saving + + Convert func into callable that saves & restores + history around the call """ + + if not self.has_readline: + return func + + def wrapper(): + self.savehist() + try: + func() + finally: + readline.read_history_file(self.histfile) + return wrapper + + #------------------------------------------------------------------------- + # Things related to exception handling and tracebacks (not debugging) + #------------------------------------------------------------------------- + + def init_traceback_handlers(self, custom_exceptions): + # Syntax error handler. + self.SyntaxTB = SyntaxTB(color_scheme='NoColor') + + # The interactive one is initialized with an offset, meaning we always + # want to remove the topmost item in the traceback, which is our own + # internal code. Valid modes: ['Plain','Context','Verbose'] + self.InteractiveTB = ultratb.AutoFormattedTB(mode = 'Plain', + color_scheme='NoColor', + tb_offset = 1) + + # IPython itself shouldn't crash. This will produce a detailed + # post-mortem if it does. But we only install the crash handler for + # non-threaded shells, the threaded ones use a normal verbose reporter + # and lose the crash handler. This is because exceptions in the main + # thread (such as in GUI code) propagate directly to sys.excepthook, + # and there's no point in printing crash dumps for every user exception. + if self.isthreaded: + ipCrashHandler = ultratb.FormattedTB() + else: + from IPython.core import crashhandler + ipCrashHandler = crashhandler.IPythonCrashHandler(self) + self.set_crash_handler(ipCrashHandler) + + # and add any custom exception handlers the user may have specified + self.set_custom_exc(*custom_exceptions) + + def set_crash_handler(self, crashHandler): + """Set the IPython crash handler. + + This must be a callable with a signature suitable for use as + sys.excepthook.""" + + # Install the given crash handler as the Python exception hook + sys.excepthook = crashHandler + + # The instance will store a pointer to this, so that runtime code + # (such as magics) can access it. This is because during the + # read-eval loop, it gets temporarily overwritten (to deal with GUI + # frameworks). + self.sys_excepthook = sys.excepthook + + def set_custom_exc(self,exc_tuple,handler): + """set_custom_exc(exc_tuple,handler) + + Set a custom exception handler, which will be called if any of the + exceptions in exc_tuple occur in the mainloop (specifically, in the + runcode() method. + + Inputs: + + - exc_tuple: a *tuple* of valid exceptions to call the defined + handler for. It is very important that you use a tuple, and NOT A + LIST here, because of the way Python's except statement works. If + you only want to trap a single exception, use a singleton tuple: + + exc_tuple == (MyCustomException,) + + - handler: this must be defined as a function with the following + basic interface: def my_handler(self,etype,value,tb). + + This will be made into an instance method (via new.instancemethod) + of IPython itself, and it will be called if any of the exceptions + listed in the exc_tuple are caught. If the handler is None, an + internal basic one is used, which just prints basic info. + + WARNING: by putting in your own exception handler into IPython's main + execution loop, you run a very good chance of nasty crashes. This + facility should only be used if you really know what you are doing.""" + + assert type(exc_tuple)==type(()) , \ + "The custom exceptions must be given AS A TUPLE." + + def dummy_handler(self,etype,value,tb): + print '*** Simple custom exception handler ***' + print 'Exception type :',etype + print 'Exception value:',value + print 'Traceback :',tb + print 'Source code :','\n'.join(self.buffer) + + if handler is None: handler = dummy_handler + + self.CustomTB = new.instancemethod(handler,self,self.__class__) + self.custom_exceptions = exc_tuple + + def excepthook(self, etype, value, tb): + """One more defense for GUI apps that call sys.excepthook. + + GUI frameworks like wxPython trap exceptions and call + sys.excepthook themselves. I guess this is a feature that + enables them to keep running after exceptions that would + otherwise kill their mainloop. This is a bother for IPython + which excepts to catch all of the program exceptions with a try: + except: statement. + + Normally, IPython sets sys.excepthook to a CrashHandler instance, so if + any app directly invokes sys.excepthook, it will look to the user like + IPython crashed. In order to work around this, we can disable the + CrashHandler and replace it with this excepthook instead, which prints a + regular traceback using our InteractiveTB. In this fashion, apps which + call sys.excepthook will generate a regular-looking exception from + IPython, and the CrashHandler will only be triggered by real IPython + crashes. + + This hook should be used sparingly, only in places which are not likely + to be true IPython errors. + """ + self.showtraceback((etype,value,tb),tb_offset=0) + + def showtraceback(self,exc_tuple = None,filename=None,tb_offset=None): + """Display the exception that just occurred. + + If nothing is known about the exception, this is the method which + should be used throughout the code for presenting user tracebacks, + rather than directly invoking the InteractiveTB object. + + A specific showsyntaxerror() also exists, but this method can take + care of calling it if needed, so unless you are explicitly catching a + SyntaxError exception, don't try to analyze the stack manually and + simply call this method.""" + + + # Though this won't be called by syntax errors in the input line, + # there may be SyntaxError cases whith imported code. + + try: + if exc_tuple is None: + etype, value, tb = sys.exc_info() + else: + etype, value, tb = exc_tuple + + if etype is SyntaxError: + self.showsyntaxerror(filename) + elif etype is UsageError: + print "UsageError:", value + else: + # WARNING: these variables are somewhat deprecated and not + # necessarily safe to use in a threaded environment, but tools + # like pdb depend on their existence, so let's set them. If we + # find problems in the field, we'll need to revisit their use. + sys.last_type = etype + sys.last_value = value + sys.last_traceback = tb + + if etype in self.custom_exceptions: + self.CustomTB(etype,value,tb) + else: + self.InteractiveTB(etype,value,tb,tb_offset=tb_offset) + if self.InteractiveTB.call_pdb and self.has_readline: + # pdb mucks up readline, fix it back + self.set_completer() + except KeyboardInterrupt: + self.write("\nKeyboardInterrupt\n") + + def showsyntaxerror(self, filename=None): + """Display the syntax error that just occurred. + + This doesn't display a stack trace because there isn't one. + + If a filename is given, it is stuffed in the exception instead + of what was there before (because Python's parser always uses + "" when reading from a string). + """ + etype, value, last_traceback = sys.exc_info() + + # See note about these variables in showtraceback() below + sys.last_type = etype + sys.last_value = value + sys.last_traceback = last_traceback + + if filename and etype is SyntaxError: + # Work hard to stuff the correct filename in the exception + try: + msg, (dummy_filename, lineno, offset, line) = value + except: + # Not the format we expect; leave it alone + pass + else: + # Stuff in the right filename + try: + # Assume SyntaxError is a class exception + value = SyntaxError(msg, (filename, lineno, offset, line)) + except: + # If that failed, assume SyntaxError is a string + value = msg, (filename, lineno, offset, line) + self.SyntaxTB(etype,value,[]) + + def edit_syntax_error(self): + """The bottom half of the syntax error handler called in the main loop. + + Loop until syntax error is fixed or user cancels. + """ + + while self.SyntaxTB.last_syntax_error: + # copy and clear last_syntax_error + err = self.SyntaxTB.clear_err_state() + if not self._should_recompile(err): + return + try: + # may set last_syntax_error again if a SyntaxError is raised + self.safe_execfile(err.filename,self.user_ns) + except: + self.showtraceback() + else: + try: + f = file(err.filename) + try: + # This should be inside a display_trap block and I + # think it is. + sys.displayhook(f.read()) + finally: + f.close() + except: + self.showtraceback() + + def _should_recompile(self,e): + """Utility routine for edit_syntax_error""" + + if e.filename in ('','','', + '','', + None): + + return False + try: + if (self.autoedit_syntax and + not self.ask_yes_no('Return to editor to correct syntax error? ' + '[Y/n] ','y')): + return False + except EOFError: + return False + + def int0(x): + try: + return int(x) + except TypeError: + return 0 + # always pass integer line and offset values to editor hook + try: + self.hooks.fix_error_editor(e.filename, + int0(e.lineno),int0(e.offset),e.msg) + except TryNext: + warn('Could not open editor') + return False + return True + + #------------------------------------------------------------------------- + # Things related to tab completion + #------------------------------------------------------------------------- + + def complete(self, text): + """Return a sorted list of all possible completions on text. + + Inputs: + + - text: a string of text to be completed on. + + This is a wrapper around the completion mechanism, similar to what + readline does at the command line when the TAB key is hit. By + exposing it as a method, it can be used by other non-readline + environments (such as GUIs) for text completion. + + Simple usage example: + + In [7]: x = 'hello' + + In [8]: x + Out[8]: 'hello' + + In [9]: print x + hello + + In [10]: _ip.complete('x.l') + Out[10]: ['x.ljust', 'x.lower', 'x.lstrip'] + """ + + # Inject names into __builtin__ so we can complete on the added names. + with self.builtin_trap: + complete = self.Completer.complete + state = 0 + # use a dict so we get unique keys, since ipyhton's multiple + # completers can return duplicates. When we make 2.4 a requirement, + # start using sets instead, which are faster. + comps = {} + while True: + newcomp = complete(text,state,line_buffer=text) + if newcomp is None: + break + comps[newcomp] = 1 + state += 1 + outcomps = comps.keys() + outcomps.sort() + #print "T:",text,"OC:",outcomps # dbg + #print "vars:",self.user_ns.keys() + return outcomps + + def set_custom_completer(self,completer,pos=0): + """Adds a new custom completer function. + + The position argument (defaults to 0) is the index in the completers + list where you want the completer to be inserted.""" + + newcomp = new.instancemethod(completer,self.Completer, + self.Completer.__class__) + self.Completer.matchers.insert(pos,newcomp) + + def set_completer(self): + """Reset readline's completer to be our own.""" + self.readline.set_completer(self.Completer.complete) + + def set_completer_frame(self, frame=None): + """Set the frame of the completer.""" + if frame: + self.Completer.namespace = frame.f_locals + self.Completer.global_namespace = frame.f_globals + else: + self.Completer.namespace = self.user_ns + self.Completer.global_namespace = self.user_global_ns + + #------------------------------------------------------------------------- + # Things related to readline + #------------------------------------------------------------------------- + + def init_readline(self): + """Command history completion/saving/reloading.""" + + self.rl_next_input = None + self.rl_do_indent = False + + if not self.readline_use: + return + + import IPython.utils.rlineimpl as readline + + if not readline.have_readline: + self.has_readline = 0 + self.readline = None + # no point in bugging windows users with this every time: + warn('Readline services not available on this platform.') + else: + sys.modules['readline'] = readline + import atexit + from IPython.core.completer import IPCompleter + self.Completer = IPCompleter(self, + self.user_ns, + self.user_global_ns, + self.readline_omit__names, + self.alias_manager.alias_table) + sdisp = self.strdispatchers.get('complete_command', StrDispatch()) + self.strdispatchers['complete_command'] = sdisp + self.Completer.custom_completers = sdisp + # Platform-specific configuration + if os.name == 'nt': + self.readline_startup_hook = readline.set_pre_input_hook + else: + self.readline_startup_hook = readline.set_startup_hook + + # Load user's initrc file (readline config) + # Or if libedit is used, load editrc. + inputrc_name = os.environ.get('INPUTRC') + if inputrc_name is None: + home_dir = get_home_dir() + if home_dir is not None: + inputrc_name = '.inputrc' + if readline.uses_libedit: + inputrc_name = '.editrc' + inputrc_name = os.path.join(home_dir, inputrc_name) + if os.path.isfile(inputrc_name): + try: + readline.read_init_file(inputrc_name) + except: + warn('Problems reading readline initialization file <%s>' + % inputrc_name) + + self.has_readline = 1 + self.readline = readline + # save this in sys so embedded copies can restore it properly + sys.ipcompleter = self.Completer.complete + self.set_completer() + + # Configure readline according to user's prefs + # This is only done if GNU readline is being used. If libedit + # is being used (as on Leopard) the readline config is + # not run as the syntax for libedit is different. + if not readline.uses_libedit: + for rlcommand in self.readline_parse_and_bind: + #print "loading rl:",rlcommand # dbg + readline.parse_and_bind(rlcommand) + + # Remove some chars from the delimiters list. If we encounter + # unicode chars, discard them. + delims = readline.get_completer_delims().encode("ascii", "ignore") + delims = delims.translate(string._idmap, + self.readline_remove_delims) + readline.set_completer_delims(delims) + # otherwise we end up with a monster history after a while: + readline.set_history_length(1000) + try: + #print '*** Reading readline history' # dbg + readline.read_history_file(self.histfile) + except IOError: + pass # It doesn't exist yet. + + atexit.register(self.atexit_operations) + del atexit + + # Configure auto-indent for all platforms + self.set_autoindent(self.autoindent) + + def set_next_input(self, s): + """ Sets the 'default' input string for the next command line. + + Requires readline. + + Example: + + [D:\ipython]|1> _ip.set_next_input("Hello Word") + [D:\ipython]|2> Hello Word_ # cursor is here + """ + + self.rl_next_input = s + + def pre_readline(self): + """readline hook to be used at the start of each line. + + Currently it handles auto-indent only.""" + + #debugx('self.indent_current_nsp','pre_readline:') + + if self.rl_do_indent: + self.readline.insert_text(self._indent_current_str()) + if self.rl_next_input is not None: + self.readline.insert_text(self.rl_next_input) + self.rl_next_input = None + + def _indent_current_str(self): + """return the current level of indentation as a string""" + return self.indent_current_nsp * ' ' + + #------------------------------------------------------------------------- + # Things related to magics + #------------------------------------------------------------------------- + + def init_magics(self): + # Set user colors (don't do it in the constructor above so that it + # doesn't crash if colors option is invalid) + self.magic_colors(self.colors) + + def magic(self,arg_s): + """Call a magic function by name. + + Input: a string containing the name of the magic function to call and any + additional arguments to be passed to the magic. + + magic('name -opt foo bar') is equivalent to typing at the ipython + prompt: + + In[1]: %name -opt foo bar + + To call a magic without arguments, simply use magic('name'). + + This provides a proper Python function to call IPython's magics in any + valid Python code you can type at the interpreter, including loops and + compound statements. + """ + + args = arg_s.split(' ',1) + magic_name = args[0] + magic_name = magic_name.lstrip(prefilter.ESC_MAGIC) + + try: + magic_args = args[1] + except IndexError: + magic_args = '' + fn = getattr(self,'magic_'+magic_name,None) + if fn is None: + error("Magic function `%s` not found." % magic_name) + else: + magic_args = self.var_expand(magic_args,1) + with nested(self.builtin_trap,): + result = fn(magic_args) + return result + + def define_magic(self, magicname, func): + """Expose own function as magic function for ipython + + def foo_impl(self,parameter_s=''): + 'My very own magic!. (Use docstrings, IPython reads them).' + print 'Magic function. Passed parameter is between < >:' + print '<%s>' % parameter_s + print 'The self object is:',self + + self.define_magic('foo',foo_impl) + """ + + import new + im = new.instancemethod(func,self, self.__class__) + old = getattr(self, "magic_" + magicname, None) + setattr(self, "magic_" + magicname, im) + return old + + #------------------------------------------------------------------------- + # Things related to macros + #------------------------------------------------------------------------- + + def define_macro(self, name, themacro): + """Define a new macro + + Parameters + ---------- + name : str + The name of the macro. + themacro : str or Macro + The action to do upon invoking the macro. If a string, a new + Macro object is created by passing the string to it. + """ + + from IPython.core import macro + + if isinstance(themacro, basestring): + themacro = macro.Macro(themacro) + if not isinstance(themacro, macro.Macro): + raise ValueError('A macro must be a string or a Macro instance.') + self.user_ns[name] = themacro + + #------------------------------------------------------------------------- + # Things related to the running of system commands + #------------------------------------------------------------------------- + + def system(self, cmd): + """Make a system call, using IPython.""" + return self.hooks.shell_hook(self.var_expand(cmd, depth=2)) + + #------------------------------------------------------------------------- + # Things related to aliases + #------------------------------------------------------------------------- + + def init_alias(self): + self.alias_manager = AliasManager(self, config=self.config) + self.ns_table['alias'] = self.alias_manager.alias_table, + + #------------------------------------------------------------------------- + # Things related to the running of code + #------------------------------------------------------------------------- + + def ex(self, cmd): + """Execute a normal python statement in user namespace.""" + with nested(self.builtin_trap,): + exec cmd in self.user_global_ns, self.user_ns + + def ev(self, expr): + """Evaluate python expression expr in user namespace. + + Returns the result of evaluation + """ + with nested(self.builtin_trap,): + return eval(expr, self.user_global_ns, self.user_ns) + + def mainloop(self, display_banner=None): + """Start the mainloop. + + If an optional banner argument is given, it will override the + internally created default banner. + """ + + with nested(self.builtin_trap, self.display_trap): + + # if you run stuff with -c , raw hist is not updated + # ensure that it's in sync + if len(self.input_hist) != len (self.input_hist_raw): + self.input_hist_raw = InputList(self.input_hist) + + while 1: + try: + self.interact(display_banner=display_banner) + #self.interact_with_readline() + # XXX for testing of a readline-decoupled repl loop, call + # interact_with_readline above + break + except KeyboardInterrupt: + # this should not be necessary, but KeyboardInterrupt + # handling seems rather unpredictable... + self.write("\nKeyboardInterrupt in interact()\n") + + def interact_prompt(self): + """ Print the prompt (in read-eval-print loop) + + Provided for those who want to implement their own read-eval-print loop (e.g. GUIs), not + used in standard IPython flow. + """ + if self.more: + try: + prompt = self.hooks.generate_prompt(True) + except: + self.showtraceback() + if self.autoindent: + self.rl_do_indent = True + + else: + try: + prompt = self.hooks.generate_prompt(False) + except: + self.showtraceback() + self.write(prompt) + + def interact_handle_input(self,line): + """ Handle the input line (in read-eval-print loop) + + Provided for those who want to implement their own read-eval-print loop (e.g. GUIs), not + used in standard IPython flow. + """ + if line.lstrip() == line: + self.shadowhist.add(line.strip()) + lineout = self.prefilter_manager.prefilter_lines(line,self.more) + + if line.strip(): + if self.more: + self.input_hist_raw[-1] += '%s\n' % line + else: + self.input_hist_raw.append('%s\n' % line) + + + self.more = self.push_line(lineout) + if (self.SyntaxTB.last_syntax_error and + self.autoedit_syntax): + self.edit_syntax_error() + + def interact_with_readline(self): + """ Demo of using interact_handle_input, interact_prompt + + This is the main read-eval-print loop. If you need to implement your own (e.g. for GUI), + it should work like this. + """ + self.readline_startup_hook(self.pre_readline) + while not self.exit_now: + self.interact_prompt() + if self.more: + self.rl_do_indent = True + else: + self.rl_do_indent = False + line = raw_input_original().decode(self.stdin_encoding) + self.interact_handle_input(line) + + def interact(self, display_banner=None): + """Closely emulate the interactive Python console.""" + + # batch run -> do not interact + if self.exit_now: + return + + if display_banner is None: + display_banner = self.display_banner + if display_banner: + self.show_banner() + + more = 0 + + # Mark activity in the builtins + __builtin__.__dict__['__IPYTHON__active'] += 1 + + if self.has_readline: + self.readline_startup_hook(self.pre_readline) + # exit_now is set by a call to %Exit or %Quit, through the + # ask_exit callback. + + while not self.exit_now: + self.hooks.pre_prompt_hook() + if more: + try: + prompt = self.hooks.generate_prompt(True) + except: + self.showtraceback() + if self.autoindent: + self.rl_do_indent = True + + else: + try: + prompt = self.hooks.generate_prompt(False) + except: + self.showtraceback() + try: + line = self.raw_input(prompt, more) + if self.exit_now: + # quick exit on sys.std[in|out] close + break + if self.autoindent: + self.rl_do_indent = False + + except KeyboardInterrupt: + #double-guard against keyboardinterrupts during kbdint handling + try: + self.write('\nKeyboardInterrupt\n') + self.resetbuffer() + # keep cache in sync with the prompt counter: + self.outputcache.prompt_count -= 1 + + if self.autoindent: + self.indent_current_nsp = 0 + more = 0 + except KeyboardInterrupt: + pass + except EOFError: + if self.autoindent: + self.rl_do_indent = False + self.readline_startup_hook(None) + self.write('\n') + self.exit() + except bdb.BdbQuit: + warn('The Python debugger has exited with a BdbQuit exception.\n' + 'Because of how pdb handles the stack, it is impossible\n' + 'for IPython to properly format this particular exception.\n' + 'IPython will resume normal operation.') + except: + # exceptions here are VERY RARE, but they can be triggered + # asynchronously by signal handlers, for example. + self.showtraceback() + else: + more = self.push_line(line) + if (self.SyntaxTB.last_syntax_error and + self.autoedit_syntax): + self.edit_syntax_error() + + # We are off again... + __builtin__.__dict__['__IPYTHON__active'] -= 1 + + def safe_execfile(self, fname, *where, **kw): + """A safe version of the builtin execfile(). + + This version will never throw an exception, but instead print + helpful error messages to the screen. This only works on pure + Python files with the .py extension. + + Parameters + ---------- + fname : string + The name of the file to be executed. + where : tuple + One or two namespaces, passed to execfile() as (globals,locals). + If only one is given, it is passed as both. + exit_ignore : bool (False) + If True, then don't print errors for non-zero exit statuses. + """ + kw.setdefault('exit_ignore', False) + + fname = os.path.abspath(os.path.expanduser(fname)) + + # Make sure we have a .py file + if not fname.endswith('.py'): + warn('File must end with .py to be run using execfile: <%s>' % fname) + + # Make sure we can open the file + try: + with open(fname) as thefile: + pass + except: + warn('Could not open file <%s> for safe execution.' % fname) + return + + # Find things also in current directory. This is needed to mimic the + # behavior of running a script from the system command line, where + # Python inserts the script's directory into sys.path + dname = os.path.dirname(fname) + + with prepended_to_syspath(dname): + try: + if sys.platform == 'win32' and sys.version_info < (2,5,1): + # Work around a bug in Python for Windows. The bug was + # fixed in in Python 2.5 r54159 and 54158, but that's still + # SVN Python as of March/07. For details, see: + # http://projects.scipy.org/ipython/ipython/ticket/123 + try: + globs,locs = where[0:2] + except: + try: + globs = locs = where[0] + except: + globs = locs = globals() + exec file(fname) in globs,locs + else: + execfile(fname,*where) + except SyntaxError: + self.showsyntaxerror() + warn('Failure executing file: <%s>' % fname) + except SystemExit, status: + # Code that correctly sets the exit status flag to success (0) + # shouldn't be bothered with a traceback. Note that a plain + # sys.exit() does NOT set the message to 0 (it's empty) so that + # will still get a traceback. Note that the structure of the + # SystemExit exception changed between Python 2.4 and 2.5, so + # the checks must be done in a version-dependent way. + show = False + if status.args[0]==0 and not kw['exit_ignore']: + show = True + if show: + self.showtraceback() + warn('Failure executing file: <%s>' % fname) + except: + self.showtraceback() + warn('Failure executing file: <%s>' % fname) + + def safe_execfile_ipy(self, fname): + """Like safe_execfile, but for .ipy files with IPython syntax. + + Parameters + ---------- + fname : str + The name of the file to execute. The filename must have a + .ipy extension. + """ + fname = os.path.abspath(os.path.expanduser(fname)) + + # Make sure we have a .py file + if not fname.endswith('.ipy'): + warn('File must end with .py to be run using execfile: <%s>' % fname) + + # Make sure we can open the file + try: + with open(fname) as thefile: + pass + except: + warn('Could not open file <%s> for safe execution.' % fname) + return + + # Find things also in current directory. This is needed to mimic the + # behavior of running a script from the system command line, where + # Python inserts the script's directory into sys.path + dname = os.path.dirname(fname) + + with prepended_to_syspath(dname): + try: + with open(fname) as thefile: + script = thefile.read() + # self.runlines currently captures all exceptions + # raise in user code. It would be nice if there were + # versions of runlines, execfile that did raise, so + # we could catch the errors. + self.runlines(script, clean=True) + except: + self.showtraceback() + warn('Unknown failure executing file: <%s>' % fname) + + def _is_secondary_block_start(self, s): + if not s.endswith(':'): + return False + if (s.startswith('elif') or + s.startswith('else') or + s.startswith('except') or + s.startswith('finally')): + return True + + def cleanup_ipy_script(self, script): + """Make a script safe for self.runlines() + + Currently, IPython is lines based, with blocks being detected by + empty lines. This is a problem for block based scripts that may + not have empty lines after blocks. This script adds those empty + lines to make scripts safe for running in the current line based + IPython. + """ + res = [] + lines = script.splitlines() + level = 0 + + for l in lines: + lstripped = l.lstrip() + stripped = l.strip() + if not stripped: + continue + newlevel = len(l) - len(lstripped) + if level > 0 and newlevel == 0 and \ + not self._is_secondary_block_start(stripped): + # add empty line + res.append('') + res.append(l) + level = newlevel + + return '\n'.join(res) + '\n' + + def runlines(self, lines, clean=False): + """Run a string of one or more lines of source. + + This method is capable of running a string containing multiple source + lines, as if they had been entered at the IPython prompt. Since it + exposes IPython's processing machinery, the given strings can contain + magic calls (%magic), special shell access (!cmd), etc. + """ + + if isinstance(lines, (list, tuple)): + lines = '\n'.join(lines) + + if clean: + lines = self.cleanup_ipy_script(lines) + + # We must start with a clean buffer, in case this is run from an + # interactive IPython session (via a magic, for example). + self.resetbuffer() + lines = lines.splitlines() + more = 0 + + with nested(self.builtin_trap, self.display_trap): + for line in lines: + # skip blank lines so we don't mess up the prompt counter, but do + # NOT skip even a blank line if we are in a code block (more is + # true) + + if line or more: + # push to raw history, so hist line numbers stay in sync + self.input_hist_raw.append("# " + line + "\n") + prefiltered = self.prefilter_manager.prefilter_lines(line,more) + more = self.push_line(prefiltered) + # IPython's runsource returns None if there was an error + # compiling the code. This allows us to stop processing right + # away, so the user gets the error message at the right place. + if more is None: + break + else: + self.input_hist_raw.append("\n") + # final newline in case the input didn't have it, so that the code + # actually does get executed + if more: + self.push_line('\n') + + def runsource(self, source, filename='', symbol='single'): + """Compile and run some source in the interpreter. + + Arguments are as for compile_command(). + + One several things can happen: + + 1) The input is incorrect; compile_command() raised an + exception (SyntaxError or OverflowError). A syntax traceback + will be printed by calling the showsyntaxerror() method. + + 2) The input is incomplete, and more input is required; + compile_command() returned None. Nothing happens. + + 3) The input is complete; compile_command() returned a code + object. The code is executed by calling self.runcode() (which + also handles run-time exceptions, except for SystemExit). + + The return value is: + + - True in case 2 + + - False in the other cases, unless an exception is raised, where + None is returned instead. This can be used by external callers to + know whether to continue feeding input or not. + + The return value can be used to decide whether to use sys.ps1 or + sys.ps2 to prompt the next line.""" + + # if the source code has leading blanks, add 'if 1:\n' to it + # this allows execution of indented pasted code. It is tempting + # to add '\n' at the end of source to run commands like ' a=1' + # directly, but this fails for more complicated scenarios + source=source.encode(self.stdin_encoding) + if source[:1] in [' ', '\t']: + source = 'if 1:\n%s' % source + + try: + code = self.compile(source,filename,symbol) + except (OverflowError, SyntaxError, ValueError, TypeError, MemoryError): + # Case 1 + self.showsyntaxerror(filename) + return None + + if code is None: + # Case 2 + return True + + # Case 3 + # We store the code object so that threaded shells and + # custom exception handlers can access all this info if needed. + # The source corresponding to this can be obtained from the + # buffer attribute as '\n'.join(self.buffer). + self.code_to_run = code + # now actually execute the code object + if self.runcode(code) == 0: + return False + else: + return None + + def runcode(self,code_obj): + """Execute a code object. + + When an exception occurs, self.showtraceback() is called to display a + traceback. + + Return value: a flag indicating whether the code to be run completed + successfully: + + - 0: successful execution. + - 1: an error occurred. + """ + + # Set our own excepthook in case the user code tries to call it + # directly, so that the IPython crash handler doesn't get triggered + old_excepthook,sys.excepthook = sys.excepthook, self.excepthook + + # we save the original sys.excepthook in the instance, in case config + # code (such as magics) needs access to it. + self.sys_excepthook = old_excepthook + outflag = 1 # happens in more places, so it's easier as default + try: + try: + self.hooks.pre_runcode_hook() + exec code_obj in self.user_global_ns, self.user_ns + finally: + # Reset our crash handler in place + sys.excepthook = old_excepthook + except SystemExit: + self.resetbuffer() + self.showtraceback() + warn("Type %exit or %quit to exit IPython " + "(%Exit or %Quit do so unconditionally).",level=1) + except self.custom_exceptions: + etype,value,tb = sys.exc_info() + self.CustomTB(etype,value,tb) + except: + self.showtraceback() + else: + outflag = 0 + if softspace(sys.stdout, 0): + print + # Flush out code object which has been run (and source) + self.code_to_run = None + return outflag + + def push_line(self, line): + """Push a line to the interpreter. + + The line should not have a trailing newline; it may have + internal newlines. The line is appended to a buffer and the + interpreter's runsource() method is called with the + concatenated contents of the buffer as source. If this + indicates that the command was executed or invalid, the buffer + is reset; otherwise, the command is incomplete, and the buffer + is left as it was after the line was appended. The return + value is 1 if more input is required, 0 if the line was dealt + with in some way (this is the same as runsource()). + """ + + # autoindent management should be done here, and not in the + # interactive loop, since that one is only seen by keyboard input. We + # need this done correctly even for code run via runlines (which uses + # push). + + #print 'push line: <%s>' % line # dbg + for subline in line.splitlines(): + self._autoindent_update(subline) + self.buffer.append(line) + more = self.runsource('\n'.join(self.buffer), self.filename) + if not more: + self.resetbuffer() + return more + + def _autoindent_update(self,line): + """Keep track of the indent level.""" + + #debugx('line') + #debugx('self.indent_current_nsp') + if self.autoindent: + if line: + inisp = num_ini_spaces(line) + if inisp < self.indent_current_nsp: + self.indent_current_nsp = inisp + + if line[-1] == ':': + self.indent_current_nsp += 4 + elif dedent_re.match(line): + self.indent_current_nsp -= 4 + else: + self.indent_current_nsp = 0 + + def resetbuffer(self): + """Reset the input buffer.""" + self.buffer[:] = [] + + def raw_input(self,prompt='',continue_prompt=False): + """Write a prompt and read a line. + + The returned line does not include the trailing newline. + When the user enters the EOF key sequence, EOFError is raised. + + Optional inputs: + + - prompt(''): a string to be printed to prompt the user. + + - continue_prompt(False): whether this line is the first one or a + continuation in a sequence of inputs. + """ + # growl.notify("raw_input: ", "prompt = %r\ncontinue_prompt = %s" % (prompt, continue_prompt)) + + # Code run by the user may have modified the readline completer state. + # We must ensure that our completer is back in place. + + if self.has_readline: + self.set_completer() + + try: + line = raw_input_original(prompt).decode(self.stdin_encoding) + except ValueError: + warn("\n********\nYou or a %run:ed script called sys.stdin.close()" + " or sys.stdout.close()!\nExiting IPython!") + self.ask_exit() + return "" + + # Try to be reasonably smart about not re-indenting pasted input more + # than necessary. We do this by trimming out the auto-indent initial + # spaces, if the user's actual input started itself with whitespace. + #debugx('self.buffer[-1]') + + if self.autoindent: + if num_ini_spaces(line) > self.indent_current_nsp: + line = line[self.indent_current_nsp:] + self.indent_current_nsp = 0 + + # store the unfiltered input before the user has any chance to modify + # it. + if line.strip(): + if continue_prompt: + self.input_hist_raw[-1] += '%s\n' % line + if self.has_readline and self.readline_use: + try: + histlen = self.readline.get_current_history_length() + if histlen > 1: + newhist = self.input_hist_raw[-1].rstrip() + self.readline.remove_history_item(histlen-1) + self.readline.replace_history_item(histlen-2, + newhist.encode(self.stdin_encoding)) + except AttributeError: + pass # re{move,place}_history_item are new in 2.4. + else: + self.input_hist_raw.append('%s\n' % line) + # only entries starting at first column go to shadow history + if line.lstrip() == line: + self.shadowhist.add(line.strip()) + elif not continue_prompt: + self.input_hist_raw.append('\n') + try: + lineout = self.prefilter_manager.prefilter_lines(line,continue_prompt) + except: + # blanket except, in case a user-defined prefilter crashes, so it + # can't take all of ipython with it. + self.showtraceback() + return '' + else: + return lineout + + #------------------------------------------------------------------------- + # Working with components + #------------------------------------------------------------------------- + + def get_component(self, name=None, klass=None): + """Fetch a component by name and klass in my tree.""" + c = Component.get_instances(root=self, name=name, klass=klass) + if len(c) == 0: + return None + if len(c) == 1: + return c[0] + else: + return c + + #------------------------------------------------------------------------- + # IPython extensions + #------------------------------------------------------------------------- + + def load_extension(self, module_str): + """Load an IPython extension by its module name. + + An IPython extension is an importable Python module that has + a function with the signature:: + + def load_ipython_extension(ipython): + # Do things with ipython + + This function is called after your extension is imported and the + currently active :class:`InteractiveShell` instance is passed as + the only argument. You can do anything you want with IPython at + that point, including defining new magic and aliases, adding new + components, etc. + + The :func:`load_ipython_extension` will be called again is you + load or reload the extension again. It is up to the extension + author to add code to manage that. + + You can put your extension modules anywhere you want, as long as + they can be imported by Python's standard import mechanism. However, + to make it easy to write extensions, you can also put your extensions + in ``os.path.join(self.ipython_dir, 'extensions')``. This directory + is added to ``sys.path`` automatically. + """ + from IPython.utils.syspathcontext import prepended_to_syspath + + if module_str not in sys.modules: + with prepended_to_syspath(self.ipython_extension_dir): + __import__(module_str) + mod = sys.modules[module_str] + self._call_load_ipython_extension(mod) + + def unload_extension(self, module_str): + """Unload an IPython extension by its module name. + + This function looks up the extension's name in ``sys.modules`` and + simply calls ``mod.unload_ipython_extension(self)``. + """ + if module_str in sys.modules: + mod = sys.modules[module_str] + self._call_unload_ipython_extension(mod) + + def reload_extension(self, module_str): + """Reload an IPython extension by calling reload. + + If the module has not been loaded before, + :meth:`InteractiveShell.load_extension` is called. Otherwise + :func:`reload` is called and then the :func:`load_ipython_extension` + function of the module, if it exists is called. + """ + from IPython.utils.syspathcontext import prepended_to_syspath + + with prepended_to_syspath(self.ipython_extension_dir): + if module_str in sys.modules: + mod = sys.modules[module_str] + reload(mod) + self._call_load_ipython_extension(mod) + else: + self.load_extension(module_str) + + def _call_load_ipython_extension(self, mod): + if hasattr(mod, 'load_ipython_extension'): + mod.load_ipython_extension(self) + + def _call_unload_ipython_extension(self, mod): + if hasattr(mod, 'unload_ipython_extension'): + mod.unload_ipython_extension(self) + + #------------------------------------------------------------------------- + # Things related to the prefilter + #------------------------------------------------------------------------- + + def init_prefilter(self): + self.prefilter_manager = PrefilterManager(self, config=self.config) + + #------------------------------------------------------------------------- + # Utilities + #------------------------------------------------------------------------- + + def getoutput(self, cmd): + return getoutput(self.var_expand(cmd,depth=2), + header=self.system_header, + verbose=self.system_verbose) + + def getoutputerror(self, cmd): + return getoutputerror(self.var_expand(cmd,depth=2), + header=self.system_header, + verbose=self.system_verbose) + + def var_expand(self,cmd,depth=0): + """Expand python variables in a string. + + The depth argument indicates how many frames above the caller should + be walked to look for the local namespace where to expand variables. + + The global namespace for expansion is always the user's interactive + namespace. + """ + + return str(ItplNS(cmd, + self.user_ns, # globals + # Skip our own frame in searching for locals: + sys._getframe(depth+1).f_locals # locals + )) + + def mktempfile(self,data=None): + """Make a new tempfile and return its filename. + + This makes a call to tempfile.mktemp, but it registers the created + filename internally so ipython cleans it up at exit time. + + Optional inputs: + + - data(None): if data is given, it gets written out to the temp file + immediately, and the file is closed again.""" + + filename = tempfile.mktemp('.py','ipython_edit_') + self.tempfiles.append(filename) + + if data: + tmp_file = open(filename,'w') + tmp_file.write(data) + tmp_file.close() + return filename + + def write(self,data): + """Write a string to the default output""" + Term.cout.write(data) + + def write_err(self,data): + """Write a string to the default error output""" + Term.cerr.write(data) + + def ask_yes_no(self,prompt,default=True): + if self.quiet: + return True + return ask_yes_no(prompt,default) + + #------------------------------------------------------------------------- + # Things related to IPython exiting + #------------------------------------------------------------------------- + + def ask_exit(self): + """ Call for exiting. Can be overiden and used as a callback. """ + self.exit_now = True + + def exit(self): + """Handle interactive exit. + + This method calls the ask_exit callback.""" + if self.confirm_exit: + if self.ask_yes_no('Do you really want to exit ([y]/n)?','y'): + self.ask_exit() + else: + self.ask_exit() + + def atexit_operations(self): + """This will be executed at the time of exit. + + Saving of persistent data should be performed here. + """ + self.savehist() + + # Cleanup all tempfiles left around + for tfile in self.tempfiles: + try: + os.unlink(tfile) + except OSError: + pass + + # Clear all user namespaces to release all references cleanly. + self.reset() + + # Run user hooks + self.hooks.shutdown_hook() + + def cleanup(self): + self.restore_sys_module_state() + + diff --git a/IPython/Logger.py b/IPython/core/logger.py similarity index 100% rename from IPython/Logger.py rename to IPython/core/logger.py diff --git a/IPython/macro.py b/IPython/core/macro.py similarity index 91% rename from IPython/macro.py rename to IPython/core/macro.py index 30cf4fb..7ca39ed 100644 --- a/IPython/macro.py +++ b/IPython/core/macro.py @@ -7,10 +7,8 @@ # the file COPYING, distributed as part of this software. #***************************************************************************** -import IPython.ipapi - -from IPython.genutils import Term -from IPython.ipapi import IPyAutocall +from IPython.utils.genutils import Term +from IPython.core.autocall import IPyAutocall class Macro(IPyAutocall): """Simple class to store the value of macros as strings. diff --git a/IPython/Magic.py b/IPython/core/magic.py similarity index 88% rename from IPython/Magic.py rename to IPython/core/magic.py index 8f96c6a..e620ce7 100644 --- a/IPython/Magic.py +++ b/IPython/core/magic.py @@ -21,6 +21,7 @@ import os import pdb import pydoc import sys +import shutil import re import tempfile import time @@ -43,17 +44,20 @@ except ImportError: # Homebrewed import IPython -from IPython import Debugger, OInspect, wildcard -from IPython.FakeModule import FakeModule -from IPython.Itpl import Itpl, itpl, printpl,itplns -from IPython.PyColorize import Parser -from IPython.ipstruct import Struct -from IPython.macro import Macro -from IPython.genutils import * -from IPython import platutils -import IPython.generics -import IPython.ipapi -from IPython.ipapi import UsageError +from IPython.utils import wildcard +from IPython.core import debugger, oinspect +from IPython.core.error import TryNext +from IPython.core.fakemodule import FakeModule +from IPython.core.prefilter import ESC_MAGIC +from IPython.external.Itpl import Itpl, itpl, printpl,itplns +from IPython.utils.PyColorize import Parser +from IPython.utils.ipstruct import Struct +from IPython.core.macro import Macro +from IPython.utils.genutils import * +from IPython.core.page import page +from IPython.utils import platutils +import IPython.utils.generics +from IPython.core.error import UsageError from IPython.testing import decorators as testdec #*************************************************************************** @@ -203,9 +207,9 @@ python-profiler package from non-free.""") namespaces = [ ('Interactive', self.shell.user_ns), ('IPython internal', self.shell.internal_ns), ('Python builtin', __builtin__.__dict__), - ('Alias', self.shell.alias_table), + ('Alias', self.shell.alias_manager.alias_table), ] - alias_ns = self.shell.alias_table + alias_ns = self.shell.alias_manager.alias_table # initialize results to 'null' found = 0; obj = None; ospace = None; ds = None; @@ -242,7 +246,7 @@ python-profiler package from non-free.""") # Try to see if it's magic if not found: - if oname.startswith(self.shell.ESC_MAGIC): + if oname.startswith(ESC_MAGIC): oname = oname[1:] obj = getattr(self,'magic_'+oname,None) if obj is not None: @@ -270,10 +274,10 @@ python-profiler package from non-free.""") # Characters that need to be escaped for latex: escape_re = re.compile(r'(%|_|\$|#|&)',re.MULTILINE) # Magic command names as headers: - cmd_name_re = re.compile(r'^(%s.*?):' % self.shell.ESC_MAGIC, + cmd_name_re = re.compile(r'^(%s.*?):' % ESC_MAGIC, re.MULTILINE) # Magic commands - cmd_re = re.compile(r'(?P%s.+?\b)(?!\}\}:)' % self.shell.ESC_MAGIC, + cmd_re = re.compile(r'(?P%s.+?\b)(?!\}\}:)' % ESC_MAGIC, re.MULTILINE) # Paragraph continue par_re = re.compile(r'\\$',re.MULTILINE) @@ -374,10 +378,10 @@ python-profiler package from non-free.""") # Functions for IPython shell work (vars,funcs, config, etc) def magic_lsmagic(self, parameter_s = ''): """List currently available magic functions.""" - mesc = self.shell.ESC_MAGIC + mesc = ESC_MAGIC print 'Available magic functions:\n'+mesc+\ (' '+mesc).join(self.lsmagic()) - print '\n' + Magic.auto_status[self.shell.rc.automagic] + print '\n' + Magic.auto_status[self.shell.automagic] return None def magic_magic(self, parameter_s = ''): @@ -422,11 +426,11 @@ python-profiler package from non-free.""") if mode == 'rest': - rest_docs.append('**%s%s**::\n\n\t%s\n\n' %(self.shell.ESC_MAGIC, + rest_docs.append('**%s%s**::\n\n\t%s\n\n' %(ESC_MAGIC, fname,fndoc)) else: - magic_docs.append('%s%s:\n\t%s\n' %(self.shell.ESC_MAGIC, + magic_docs.append('%s%s:\n\t%s\n' %(ESC_MAGIC, fname,fndoc)) magic_docs = ''.join(magic_docs) @@ -469,22 +473,22 @@ ipythonrc file, placing a line like: will define %pf as a new name for %profile. -You can also call magics in code using the ipmagic() function, which IPython -automatically adds to the builtin namespace. Type 'ipmagic?' for details. +You can also call magics in code using the magic() function, which IPython +automatically adds to the builtin namespace. Type 'magic?' for details. For a list of the available magic functions, use %lsmagic. For a description of any of them, type %magic_name?, e.g. '%cd?'. Currently the magic system has the following functions:\n""" - mesc = self.shell.ESC_MAGIC + mesc = ESC_MAGIC outmsg = ("%s\n%s\n\nSummary of magic functions (from %slsmagic):" "\n\n%s%s\n\n%s" % (outmsg, magic_docs,mesc,mesc, (' '+mesc).join(self.lsmagic()), - Magic.auto_status[self.shell.rc.automagic] ) ) + Magic.auto_status[self.shell.automagic] ) ) - page(outmsg,screen_lines=self.shell.rc.screen_length) + page(outmsg,screen_lines=self.shell.usable_screen_length) def magic_autoindent(self, parameter_s = ''): @@ -511,15 +515,14 @@ Currently the magic system has the following functions:\n""" delete the variable (del var), the previously shadowed magic function becomes visible to automagic again.""" - rc = self.shell.rc arg = parameter_s.lower() if parameter_s in ('on','1','true'): - rc.automagic = True + self.shell.automagic = True elif parameter_s in ('off','0','false'): - rc.automagic = False + self.shell.automagic = False else: - rc.automagic = not rc.automagic - print '\n' + Magic.auto_status[rc.automagic] + self.shell.automagic = not self.shell.automagic + print '\n' + Magic.auto_status[self.shell.automagic] @testdec.skip_doctest def magic_autocall(self, parameter_s = ''): @@ -565,8 +568,6 @@ Currently the magic system has the following functions:\n""" # all-random (note for auto-testing) """ - rc = self.shell.rc - if parameter_s: arg = int(parameter_s) else: @@ -577,18 +578,18 @@ Currently the magic system has the following functions:\n""" return if arg in (0,1,2): - rc.autocall = arg + self.shell.autocall = arg else: # toggle - if rc.autocall: - self._magic_state.autocall_save = rc.autocall - rc.autocall = 0 + if self.shell.autocall: + self._magic_state.autocall_save = self.shell.autocall + self.shell.autocall = 0 else: try: - rc.autocall = self._magic_state.autocall_save + self.shell.autocall = self._magic_state.autocall_save except AttributeError: - rc.autocall = self._magic_state.autocall_save = 1 + self.shell.autocall = self._magic_state.autocall_save = 1 - print "Automatic calling is:",['OFF','Smart','Full'][rc.autocall] + print "Automatic calling is:",['OFF','Smart','Full'][self.shell.autocall] def magic_system_verbose(self, parameter_s = ''): """Set verbose printing of system calls. @@ -599,10 +600,13 @@ Currently the magic system has the following functions:\n""" val = bool(eval(parameter_s)) else: val = None - - self.shell.rc_set_toggle('system_verbose',val) + + if self.shell.system_verbose: + self.shell.system_verbose = False + else: + self.shell.system_verbose = True print "System verbose printing is:",\ - ['OFF','ON'][self.shell.rc.system_verbose] + ['OFF','ON'][self.shell.system_verbose] def magic_page(self, parameter_s=''): @@ -632,8 +636,8 @@ Currently the magic system has the following functions:\n""" def magic_profile(self, parameter_s=''): """Print your currently active IPyhton profile.""" - if self.shell.rc.profile: - printpl('Current IPython profile: $self.shell.rc.profile.') + if self.shell.profile: + printpl('Current IPython profile: $self.shell.profile.') else: print 'No profile active.' @@ -717,9 +721,9 @@ Currently the magic system has the following functions:\n""" if info.found: try: - IPython.generics.inspect_object(info.obj) + IPython.utils.generics.inspect_object(info.obj) return - except IPython.ipapi.TryNext: + except TryNext: pass # Get the docstring of the class property if it exists. path = oname.split('.') @@ -847,7 +851,7 @@ Currently the magic system has the following functions:\n""" elif opts.has_key('c'): ignore_case = False else: - ignore_case = not shell.rc.wildcards_case_sensitive + ignore_case = not shell.wildcards_case_sensitive # Build list of namespaces to search from user options def_search.extend(opt('s',[])) @@ -972,7 +976,7 @@ Currently the magic system has the following functions:\n""" return self.shell.user_ns[i] # some types are well known and can be shorter - abbrevs = {'IPython.macro.Macro' : 'Macro'} + abbrevs = {'IPython.core.macro.Macro' : 'Macro'} def type_name(v): tn = type(v).__name__ return abbrevs.get(tn,tn) @@ -1131,7 +1135,6 @@ Currently the magic system has the following functions:\n""" log_raw_input = 'r' in opts timestamp = 't' in opts - rc = self.shell.rc logger = self.shell.logger # if no args are given, the defaults set in the logger constructor by @@ -1148,11 +1151,12 @@ Currently the magic system has the following functions:\n""" # put logfname into rc struct as if it had been called on the command # line, so it ends up saved in the log header Save it in case we need # to restore it... - old_logfile = rc.opts.get('logfile','') + old_logfile = self.shell.logfile if logfname: logfname = os.path.expanduser(logfname) - rc.opts.logfile = logfname - loghead = self.shell.loghead_tpl % (rc.opts,rc.args) + self.shell.logfile = logfname + + loghead = '# IPython log file\n\n' try: started = logger.logstart(logfname,loghead,logmode, log_output,timestamp,log_raw_input) @@ -1265,7 +1269,6 @@ Currently the magic system has the following functions:\n""" If you want IPython to automatically do this on every exception, see the %pdb magic for more details. """ - self.shell.debugger(force=True) @testdec.skip_doctest @@ -1420,7 +1423,7 @@ Currently the magic system has the following functions:\n""" output = stdout_trap.getvalue() output = output.rstrip() - page(output,screen_lines=self.shell.rc.screen_length) + page(output,screen_lines=self.shell.usable_screen_length) print sys_exit, dump_file = opts.D[0] @@ -1561,14 +1564,14 @@ Currently the magic system has the following functions:\n""" filename = file_finder(arg_lst[0]) except IndexError: warn('you must provide at least a filename.') - print '\n%run:\n',OInspect.getdoc(self.magic_run) + print '\n%run:\n',oinspect.getdoc(self.magic_run) return except IOError,msg: error(msg) return if filename.lower().endswith('.ipy'): - self.api.runlines(open(filename).read()) + self.safe_execfile_ipy(filename) return # Control the response to exit() calls made by the script being run @@ -1621,7 +1624,7 @@ Currently the magic system has the following functions:\n""" stats = self.magic_prun('',0,opts,arg_lst,prog_ns) else: if opts.has_key('d'): - deb = Debugger.Pdb(self.shell.rc.colors) + deb = debugger.Pdb(self.shell.colors) # reset Breakpoint state, which is moronically kept # in a class bdb.Breakpoint.next = 1 @@ -1706,7 +1709,12 @@ Currently the magic system has the following functions:\n""" # (leaving dangling references). self.shell.cache_main_mod(prog_ns,filename) # update IPython interactive namespace - del prog_ns['__name__'] + + # Some forms of read errors on the file may mean the + # __name__ key was never set; using pop we don't have to + # worry about a possible KeyError. + prog_ns.pop('__name__', None) + self.shell.user_ns.update(prog_ns) finally: # It's a bit of a mystery why, but __builtins__ can change from @@ -1733,25 +1741,6 @@ Currently the magic system has the following functions:\n""" return stats - def magic_runlog(self, parameter_s =''): - """Run files as logs. - - Usage:\\ - %runlog file1 file2 ... - - Run the named files (treating them as log files) in sequence inside - the interpreter, and return to the prompt. This is much slower than - %run because each line is executed in a try/except block, but it - allows running files with syntax errors in them. - - Normally IPython will guess when a file is one of its own logfiles, so - you can typically use %run even for logs. This shorthand allows you to - force any file to be treated as a log file.""" - - for f in parameter_s.split(): - self.shell.safe_execfile(f,self.shell.user_ns, - self.shell.user_ns,islog=1) - @testdec.skip_doctest def magic_timeit(self, parameter_s =''): """Time execution of a Python statement or expression @@ -2055,7 +2044,7 @@ Currently the magic system has the following functions:\n""" #print 'rng',ranges # dbg lines = self.extract_input_slices(ranges,opts.has_key('r')) macro = Macro(lines) - self.shell.user_ns.update({name:macro}) + self.shell.define_macro(name, macro) print 'Macro `%s` created. To execute, type its name (without quotes).' % name print 'Macro contents:' print macro, @@ -2252,7 +2241,7 @@ Currently the magic system has the following functions:\n""" If you wish to write your own editor hook, you can put it in a configuration file which you load at startup time. The default hook - is defined in the IPython.hooks module, and you can use that as a + is defined in the IPython.core.hooks module, and you can use that as a starting example for further modifications. That file also has general instructions on how to set a new hook for use once you've defined it.""" @@ -2385,7 +2374,7 @@ Currently the magic system has the following functions:\n""" sys.stdout.flush() try: self.shell.hooks.editor(filename,lineno) - except IPython.ipapi.TryNext: + except TryNext: warn('Could not open editor') return @@ -2461,7 +2450,7 @@ Currently the magic system has the following functions:\n""" # local shortcut shell = self.shell - import IPython.rlineimpl as readline + import IPython.utils.rlineimpl as readline if not readline.have_readline and sys.platform == "win32": msg = """\ @@ -2486,7 +2475,7 @@ Defaulting color scheme to 'NoColor'""" except: color_switch_err('prompt') else: - shell.rc.colors = \ + shell.colors = \ shell.outputcache.color_table.active_scheme_name # Set exception colors try: @@ -2503,7 +2492,7 @@ Defaulting color scheme to 'NoColor'""" color_switch_err('system exception handler') # Set info (for 'object?') colors - if shell.rc.color_info: + if shell.color_info: try: shell.inspector.set_active_scheme(new_scheme) except: @@ -2522,17 +2511,17 @@ Defaulting color scheme to 'NoColor'""" than more) in your system, using colored object information displays will not work properly. Test it and see.""" - self.shell.rc.color_info = 1 - self.shell.rc.color_info - self.magic_colors(self.shell.rc.colors) + self.shell.color_info = not self.shell.color_info + self.magic_colors(self.shell.colors) print 'Object introspection functions have now coloring:', - print ['OFF','ON'][self.shell.rc.color_info] + print ['OFF','ON'][int(self.shell.color_info)] def magic_Pprint(self, parameter_s=''): """Toggle pretty printing on/off.""" - self.shell.rc.pprint = 1 - self.shell.rc.pprint + self.shell.pprint = 1 - self.shell.pprint print 'Pretty printing has been turned', \ - ['OFF','ON'][self.shell.rc.pprint] + ['OFF','ON'][self.shell.pprint] def magic_exit(self, parameter_s=''): """Exit IPython, confirming if configured to do so. @@ -2611,52 +2600,27 @@ Defaulting color scheme to 'NoColor'""" par = parameter_s.strip() if not par: stored = self.db.get('stored_aliases', {} ) - atab = self.shell.alias_table - aliases = atab.keys() - aliases.sort() - res = [] - showlast = [] - for alias in aliases: - special = False - try: - tgt = atab[alias][1] - except (TypeError, AttributeError): - # unsubscriptable? probably a callable - tgt = atab[alias] - special = True - # 'interesting' aliases - if (alias in stored or - special or - alias.lower() != os.path.splitext(tgt)[0].lower() or - ' ' in tgt): - showlast.append((alias, tgt)) - else: - res.append((alias, tgt )) - - # show most interesting aliases last - res.extend(showlast) - print "Total number of aliases:",len(aliases) - return res + aliases = sorted(self.shell.alias_manager.aliases) + # for k, v in stored: + # atab.append(k, v[0]) + + print "Total number of aliases:", len(aliases) + return aliases + + # Now try to define a new one try: - alias,cmd = par.split(None,1) + alias,cmd = par.split(None, 1) except: - print OInspect.getdoc(self.magic_alias) + print oinspect.getdoc(self.magic_alias) else: - nargs = cmd.count('%s') - if nargs>0 and cmd.find('%l')>=0: - error('The %s and %l specifiers are mutually exclusive ' - 'in alias definitions.') - else: # all looks OK - self.shell.alias_table[alias] = (nargs,cmd) - self.shell.alias_table_validate(verbose=0) + self.shell.alias_manager.soft_define_alias(alias, cmd) # end magic_alias def magic_unalias(self, parameter_s = ''): """Remove an alias""" aname = parameter_s.strip() - if aname in self.shell.alias_table: - del self.shell.alias_table[aname] + self.shell.alias_manager.undefine_alias(aname) stored = self.db.get('stored_aliases', {} ) if aname in stored: print "Removing %stored alias",aname @@ -2677,24 +2641,21 @@ Defaulting color scheme to 'NoColor'""" This function also resets the root module cache of module completer, used on slow filesystems. """ - - - ip = self.api + from IPython.core.alias import InvalidAliasError # for the benefit of module completer in ipy_completers.py - del ip.db['rootmodules'] + del self.db['rootmodules'] path = [os.path.abspath(os.path.expanduser(p)) for p in os.environ.get('PATH','').split(os.pathsep)] path = filter(os.path.isdir,path) - - alias_table = self.shell.alias_table + syscmdlist = [] + # Now define isexec in a cross platform manner. if os.name == 'posix': isexec = lambda fname:os.path.isfile(fname) and \ os.access(fname,os.X_OK) else: - try: winext = os.environ['pathext'].replace(';','|').replace('.','') except KeyError: @@ -2704,6 +2665,8 @@ Defaulting color scheme to 'NoColor'""" execre = re.compile(r'(.*)\.(%s)$' % winext,re.IGNORECASE) isexec = lambda fname:os.path.isfile(fname) and execre.match(fname) savedir = os.getcwd() + + # Now walk the paths looking for executables to alias. try: # write the whole loop for posix/Windows so we don't have an if in # the innermost part @@ -2711,14 +2674,16 @@ Defaulting color scheme to 'NoColor'""" for pdir in path: os.chdir(pdir) for ff in os.listdir(pdir): - if isexec(ff) and ff not in self.shell.no_alias: - # each entry in the alias table must be (N,name), - # where N is the number of positional arguments of the - # alias. - # Dots will be removed from alias names, since ipython - # assumes names with dots to be python code - alias_table[ff.replace('.','')] = (0,ff) - syscmdlist.append(ff) + if isexec(ff): + try: + # Removes dots from the name since ipython + # will assume names with dots to be python. + self.shell.alias_manager.define_alias( + ff.replace('.',''), ff) + except InvalidAliasError: + pass + else: + syscmdlist.append(ff) else: for pdir in path: os.chdir(pdir) @@ -2727,17 +2692,15 @@ Defaulting color scheme to 'NoColor'""" if isexec(ff) and base.lower() not in self.shell.no_alias: if ext.lower() == '.exe': ff = base - alias_table[base.lower().replace('.','')] = (0,ff) - syscmdlist.append(ff) - # Make sure the alias table doesn't contain keywords or builtins - self.shell.alias_table_validate() - # Call again init_auto_alias() so we get 'rm -i' and other - # modified aliases since %rehashx will probably clobber them - - # no, we don't want them. if %rehashx clobbers them, good, - # we'll probably get better versions - # self.shell.init_auto_alias() - db = ip.db + try: + # Removes dots from the name since ipython + # will assume names with dots to be python. + self.shell.alias_manager.define_alias( + base.lower().replace('.',''), ff) + except InvalidAliasError: + pass + syscmdlist.append(ff) + db = self.db db['syscmdlist'] = syscmdlist finally: os.chdir(savedir) @@ -2847,9 +2810,8 @@ Defaulting color scheme to 'NoColor'""" if ps: try: os.chdir(os.path.expanduser(ps)) - if self.shell.rc.term_title: - #print 'set term title:',self.shell.rc.term_title # dbg - platutils.set_term_title('IPy ' + abbrev_cwd()) + if self.shell.term_title: + platutils.set_term_title('IPython: ' + abbrev_cwd()) except OSError: print sys.exc_info()[1] else: @@ -2861,8 +2823,8 @@ Defaulting color scheme to 'NoColor'""" else: os.chdir(self.shell.home_dir) - if self.shell.rc.term_title: - platutils.set_term_title("IPy ~") + if self.shell.term_title: + platutils.set_term_title('IPython: ' + '~') cwd = os.getcwd() dhist = self.shell.user_ns['_dh'] @@ -3162,10 +3124,10 @@ Defaulting color scheme to 'NoColor'""" """ start = parameter_s.strip() - esc_magic = self.shell.ESC_MAGIC + esc_magic = ESC_MAGIC # Identify magic commands even if automagic is on (which means # the in-memory version is different from that typed by the user). - if self.shell.rc.automagic: + if self.shell.automagic: start_magic = esc_magic+start else: start_magic = start @@ -3259,7 +3221,7 @@ Defaulting color scheme to 'NoColor'""" return page(self.shell.pycolorize(cont), - screen_lines=self.shell.rc.screen_length) + screen_lines=self.shell.usable_screen_length) def _rerun_pasted(self): """ Rerun a previously pasted command. @@ -3273,7 +3235,7 @@ Defaulting color scheme to 'NoColor'""" def _get_pasted_lines(self, sentinel): """ Yield pasted lines until the user enters the given sentinel value. """ - from IPython import iplib + from IPython.core import iplib print "Pasting code; enter '%s' alone on the line to stop." % sentinel while True: l = iplib.raw_input_original(':') @@ -3344,7 +3306,7 @@ Defaulting color scheme to 'NoColor'""" See also -------- - %paste: automatically pull code from clipboard. + paste: automatically pull code from clipboard. """ opts,args = self.parse_options(parameter_s,'rs:',mode='string') @@ -3364,7 +3326,8 @@ Defaulting color scheme to 'NoColor'""" """Allows you to paste & execute a pre-formatted code block from clipboard. The text is pulled directly from the clipboard without user - intervention. + intervention and printed back on the screen before execution (unless + the -q flag is given to force quiet mode). The block is dedented prior to execution to enable execution of method definitions. '>' and '+' characters at the beginning of a line are @@ -3376,16 +3339,21 @@ Defaulting color scheme to 'NoColor'""" You can also pass a variable name as an argument, e.g. '%paste foo'. This assigns the pasted block to variable 'foo' as string, without dedenting or executing it (preceding >>> and + is still stripped) + + Options + ------- - '%paste -r' re-executes the block previously entered by cpaste. + -r: re-executes the block previously entered by cpaste. + + -q: quiet mode: do not echo the pasted text back to the terminal. IPython statements (magics, shell escapes) are not supported (yet). See also -------- - %cpaste: manually paste code into terminal until you mark its end. + cpaste: manually paste code into terminal until you mark its end. """ - opts,args = self.parse_options(parameter_s,'r:',mode='string') + opts,args = self.parse_options(parameter_s,'rq',mode='string') par = args.strip() if opts.has_key('r'): self._rerun_pasted() @@ -3393,42 +3361,23 @@ Defaulting color scheme to 'NoColor'""" text = self.shell.hooks.clipboard_get() block = self._strip_pasted_lines_for_code(text.splitlines()) + + # By default, echo back to terminal unless quiet mode is requested + if not opts.has_key('q'): + write = self.shell.write + write(self.shell.pycolorize(block)) + if not block.endswith('\n'): + write('\n') + write("## -- End pasted text --\n") + self._execute_block(block, par) def magic_quickref(self,arg): """ Show a quick reference sheet """ - import IPython.usage - qr = IPython.usage.quick_reference + self.magic_magic('-brief') + import IPython.core.usage + qr = IPython.core.usage.quick_reference + self.magic_magic('-brief') page(qr) - - def magic_upgrade(self,arg): - """ Upgrade your IPython installation - - This will copy the config files that don't yet exist in your - ipython dir from the system config dir. Use this after upgrading - IPython if you don't wish to delete your .ipython dir. - - Call with -nolegacy to get rid of ipythonrc* files (recommended for - new users) - - """ - ip = self.getapi() - ipinstallation = path(IPython.__file__).dirname() - upgrade_script = '%s "%s"' % (sys.executable,ipinstallation / 'upgrade_dir.py') - src_config = ipinstallation / 'UserConfig' - userdir = path(ip.options.ipythondir) - cmd = '%s "%s" "%s"' % (upgrade_script, src_config, userdir) - print ">",cmd - shell(cmd) - if arg == '-nolegacy': - legacy = userdir.files('ipythonrc*') - print "Nuking legacy files:",legacy - - [p.remove() for p in legacy] - suffix = (sys.platform == 'win32' and '.ini' or '') - (userdir / ('ipythonrc' + suffix)).write_text('# Empty, see ipy_user_conf.py\n') - def magic_doctest_mode(self,parameter_s=''): """Toggle doctest mode on and off. @@ -3451,13 +3400,12 @@ Defaulting color scheme to 'NoColor'""" """ # XXX - Fix this to have cleaner activate/deactivate calls. - from IPython.Extensions import InterpreterPasteInput as ipaste - from IPython.ipstruct import Struct + from IPython.extensions import InterpreterPasteInput as ipaste + from IPython.utils.ipstruct import Struct # Shorthands shell = self.shell oc = shell.outputcache - rc = shell.rc meta = shell.meta # dstore is a data store kept in the instance metadata bag to track any # changes we make, so we can undo them later. @@ -3466,12 +3414,12 @@ Defaulting color scheme to 'NoColor'""" # save a few values we'll need to recover later mode = save_dstore('mode',False) - save_dstore('rc_pprint',rc.pprint) + save_dstore('rc_pprint',shell.pprint) save_dstore('xmode',shell.InteractiveTB.mode) - save_dstore('rc_separate_out',rc.separate_out) - save_dstore('rc_separate_out2',rc.separate_out2) - save_dstore('rc_prompts_pad_left',rc.prompts_pad_left) - save_dstore('rc_separate_in',rc.separate_in) + save_dstore('rc_separate_out',shell.separate_out) + save_dstore('rc_separate_out2',shell.separate_out2) + save_dstore('rc_prompts_pad_left',shell.prompts_pad_left) + save_dstore('rc_separate_in',shell.separate_in) if mode == False: # turn on @@ -3489,7 +3437,7 @@ Defaulting color scheme to 'NoColor'""" oc.prompt1.pad_left = oc.prompt2.pad_left = \ oc.prompt_out.pad_left = False - rc.pprint = False + shell.pprint = False shell.magic_xmode('Plain') @@ -3497,9 +3445,9 @@ Defaulting color scheme to 'NoColor'""" # turn off ipaste.deactivate_prefilter() - oc.prompt1.p_template = rc.prompt_in1 - oc.prompt2.p_template = rc.prompt_in2 - oc.prompt_out.p_template = rc.prompt_out + oc.prompt1.p_template = shell.prompt_in1 + oc.prompt2.p_template = shell.prompt_in2 + oc.prompt_out.p_template = shell.prompt_out oc.input_sep = oc.prompt1.sep = dstore.rc_separate_in @@ -3518,4 +3466,115 @@ Defaulting color scheme to 'NoColor'""" print 'Doctest mode is:', print ['OFF','ON'][dstore.mode] + def magic_gui(self, parameter_s=''): + """Enable or disable IPython GUI event loop integration. + + %gui [-a] [GUINAME] + + This magic replaces IPython's threaded shells that were activated + using the (pylab/wthread/etc.) command line flags. GUI toolkits + can now be enabled, disabled and swtiched at runtime and keyboard + interrupts should work without any problems. The following toolkits + are supports: wxPython, PyQt4, PyGTK, and Tk:: + + %gui wx # enable wxPython event loop integration + %gui qt4|qt # enable PyQt4 event loop integration + %gui gtk # enable PyGTK event loop integration + %gui tk # enable Tk event loop integration + %gui # disable all event loop integration + + WARNING: after any of these has been called you can simply create + an application object, but DO NOT start the event loop yourself, as + we have already handled that. + + If you want us to create an appropriate application object add the + "-a" flag to your command:: + + %gui -a wx + + This is highly recommended for most users. + """ + from IPython.lib import inputhook + if "-a" in parameter_s: + app = True + else: + app = False + if not parameter_s: + inputhook.clear_inputhook() + elif 'wx' in parameter_s: + return inputhook.enable_wx(app) + elif ('qt4' in parameter_s) or ('qt' in parameter_s): + return inputhook.enable_qt4(app) + elif 'gtk' in parameter_s: + return inputhook.enable_gtk(app) + elif 'tk' in parameter_s: + return inputhook.enable_tk(app) + + def magic_load_ext(self, module_str): + """Load an IPython extension by its module name.""" + self.load_extension(module_str) + + def magic_unload_ext(self, module_str): + """Unload an IPython extension by its module name.""" + self.unload_extension(module_str) + + def magic_reload_ext(self, module_str): + """Reload an IPython extension by its module name.""" + self.reload_extension(module_str) + + def magic_install_profiles(self, s): + """Install the default IPython profiles into the .ipython dir. + + If the default profiles have already been installed, they will not + be overwritten. You can force overwriting them by using the ``-o`` + option:: + + In [1]: %install_profiles -o + """ + if '-o' in s: + overwrite = True + else: + overwrite = False + from IPython.config import profile + profile_dir = os.path.split(profile.__file__)[0] + ipython_dir = self.ipython_dir + files = os.listdir(profile_dir) + + to_install = [] + for f in files: + if f.startswith('ipython_config'): + src = os.path.join(profile_dir, f) + dst = os.path.join(ipython_dir, f) + if (not os.path.isfile(dst)) or overwrite: + to_install.append((f, src, dst)) + if len(to_install)>0: + print "Installing profiles to: ", ipython_dir + for (f, src, dst) in to_install: + shutil.copy(src, dst) + print " %s" % f + + def magic_install_default_config(self, s): + """Install IPython's default config file into the .ipython dir. + + If the default config file (:file:`ipython_config.py`) is already + installed, it will not be overwritten. You can force overwriting + by using the ``-o`` option:: + + In [1]: %install_default_config + """ + if '-o' in s: + overwrite = True + else: + overwrite = False + from IPython.config import default + config_dir = os.path.split(default.__file__)[0] + ipython_dir = self.ipython_dir + default_config_file_name = 'ipython_config.py' + src = os.path.join(config_dir, default_config_file_name) + dst = os.path.join(ipython_dir, default_config_file_name) + if (not os.path.isfile(dst)) or overwrite: + shutil.copy(src, dst) + print "Installing default config file: %s" % dst + + # end Magic diff --git a/IPython/OInspect.py b/IPython/core/oinspect.py similarity index 89% rename from IPython/OInspect.py rename to IPython/core/oinspect.py index ecef640..a0cfb54 100644 --- a/IPython/OInspect.py +++ b/IPython/core/oinspect.py @@ -27,11 +27,12 @@ import sys import types # IPython's own -from IPython import PyColorize -from IPython.genutils import page,indent,Term -from IPython.Itpl import itpl -from IPython.wildcard import list_namespace -from IPython.ColorANSI import * +from IPython.utils import PyColorize +from IPython.utils.genutils import indent, Term +from IPython.core.page import page +from IPython.external.Itpl import itpl +from IPython.utils.wildcard import list_namespace +from IPython.utils.coloransi import * #**************************************************************************** # HACK!!! This is a crude fix for bugs in python 2.3's inspect module. We diff --git a/IPython/OutputTrap.py b/IPython/core/outputtrap.py similarity index 100% rename from IPython/OutputTrap.py rename to IPython/core/outputtrap.py diff --git a/IPython/core/page.py b/IPython/core/page.py new file mode 100644 index 0000000..f07c1b5 --- /dev/null +++ b/IPython/core/page.py @@ -0,0 +1,308 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +Paging capabilities for IPython.core + +Authors: + +* Brian Granger +* Fernando Perez + +Notes +----- + +For now this uses ipapi, so it can't be in IPython.utils. If we can get +rid of that dependency, we could move it there. +----- +""" + +#----------------------------------------------------------------------------- +# Copyright (C) 2008-2009 The IPython Development Team +# +# Distributed under the terms of the BSD License. The full license is in +# the file COPYING, distributed as part of this software. +#----------------------------------------------------------------------------- + +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- + +import os +import re +import sys + +from IPython.core import ipapi +from IPython.core.error import TryNext +from IPython.utils.genutils import ( + chop, Term, USE_CURSES +) + +if os.name == "nt": + from IPython.utils.winconsole import get_console_size + + +#----------------------------------------------------------------------------- +# Classes and functions +#----------------------------------------------------------------------------- + +esc_re = re.compile(r"(\x1b[^m]+m)") + +def page_dumb(strng,start=0,screen_lines=25): + """Very dumb 'pager' in Python, for when nothing else works. + + Only moves forward, same interface as page(), except for pager_cmd and + mode.""" + + out_ln = strng.splitlines()[start:] + screens = chop(out_ln,screen_lines-1) + if len(screens) == 1: + print >>Term.cout, os.linesep.join(screens[0]) + else: + last_escape = "" + for scr in screens[0:-1]: + hunk = os.linesep.join(scr) + print >>Term.cout, last_escape + hunk + if not page_more(): + return + esc_list = esc_re.findall(hunk) + if len(esc_list) > 0: + last_escape = esc_list[-1] + print >>Term.cout, last_escape + os.linesep.join(screens[-1]) + +#---------------------------------------------------------------------------- +def page(strng,start=0,screen_lines=0,pager_cmd = None): + """Print a string, piping through a pager after a certain length. + + The screen_lines parameter specifies the number of *usable* lines of your + terminal screen (total lines minus lines you need to reserve to show other + information). + + If you set screen_lines to a number <=0, page() will try to auto-determine + your screen size and will only use up to (screen_size+screen_lines) for + printing, paging after that. That is, if you want auto-detection but need + to reserve the bottom 3 lines of the screen, use screen_lines = -3, and for + auto-detection without any lines reserved simply use screen_lines = 0. + + If a string won't fit in the allowed lines, it is sent through the + specified pager command. If none given, look for PAGER in the environment, + and ultimately default to less. + + If no system pager works, the string is sent through a 'dumb pager' + written in python, very simplistic. + """ + + # Some routines may auto-compute start offsets incorrectly and pass a + # negative value. Offset to 0 for robustness. + start = max(0,start) + + # first, try the hook + ip = ipapi.get() + if ip: + try: + ip.hooks.show_in_pager(strng) + return + except TryNext: + pass + + # Ugly kludge, but calling curses.initscr() flat out crashes in emacs + TERM = os.environ.get('TERM','dumb') + if TERM in ['dumb','emacs'] and os.name != 'nt': + print strng + return + # chop off the topmost part of the string we don't want to see + str_lines = strng.split(os.linesep)[start:] + str_toprint = os.linesep.join(str_lines) + num_newlines = len(str_lines) + len_str = len(str_toprint) + + # Dumb heuristics to guesstimate number of on-screen lines the string + # takes. Very basic, but good enough for docstrings in reasonable + # terminals. If someone later feels like refining it, it's not hard. + numlines = max(num_newlines,int(len_str/80)+1) + + if os.name == "nt": + screen_lines_def = get_console_size(defaulty=25)[1] + else: + screen_lines_def = 25 # default value if we can't auto-determine + + # auto-determine screen size + if screen_lines <= 0: + if TERM=='xterm' or TERM=='xterm-color': + use_curses = USE_CURSES + else: + # curses causes problems on many terminals other than xterm. + use_curses = False + if use_curses: + import termios + import curses + # There is a bug in curses, where *sometimes* it fails to properly + # initialize, and then after the endwin() call is made, the + # terminal is left in an unusable state. Rather than trying to + # check everytime for this (by requesting and comparing termios + # flags each time), we just save the initial terminal state and + # unconditionally reset it every time. It's cheaper than making + # the checks. + term_flags = termios.tcgetattr(sys.stdout) + scr = curses.initscr() + screen_lines_real,screen_cols = scr.getmaxyx() + curses.endwin() + # Restore terminal state in case endwin() didn't. + termios.tcsetattr(sys.stdout,termios.TCSANOW,term_flags) + # Now we have what we needed: the screen size in rows/columns + screen_lines += screen_lines_real + #print '***Screen size:',screen_lines_real,'lines x',\ + #screen_cols,'columns.' # dbg + else: + screen_lines += screen_lines_def + + #print 'numlines',numlines,'screenlines',screen_lines # dbg + if numlines <= screen_lines : + #print '*** normal print' # dbg + print >>Term.cout, str_toprint + else: + # Try to open pager and default to internal one if that fails. + # All failure modes are tagged as 'retval=1', to match the return + # value of a failed system command. If any intermediate attempt + # sets retval to 1, at the end we resort to our own page_dumb() pager. + pager_cmd = get_pager_cmd(pager_cmd) + pager_cmd += ' ' + get_pager_start(pager_cmd,start) + if os.name == 'nt': + if pager_cmd.startswith('type'): + # The default WinXP 'type' command is failing on complex strings. + retval = 1 + else: + tmpname = tempfile.mktemp('.txt') + tmpfile = file(tmpname,'wt') + tmpfile.write(strng) + tmpfile.close() + cmd = "%s < %s" % (pager_cmd,tmpname) + if os.system(cmd): + retval = 1 + else: + retval = None + os.remove(tmpname) + else: + try: + retval = None + # if I use popen4, things hang. No idea why. + #pager,shell_out = os.popen4(pager_cmd) + pager = os.popen(pager_cmd,'w') + pager.write(strng) + pager.close() + retval = pager.close() # success returns None + except IOError,msg: # broken pipe when user quits + if msg.args == (32,'Broken pipe'): + retval = None + else: + retval = 1 + except OSError: + # Other strange problems, sometimes seen in Win2k/cygwin + retval = 1 + if retval is not None: + page_dumb(strng,screen_lines=screen_lines) + +#---------------------------------------------------------------------------- +def page_file(fname,start = 0, pager_cmd = None): + """Page a file, using an optional pager command and starting line. + """ + + pager_cmd = get_pager_cmd(pager_cmd) + pager_cmd += ' ' + get_pager_start(pager_cmd,start) + + try: + if os.environ['TERM'] in ['emacs','dumb']: + raise EnvironmentError + xsys(pager_cmd + ' ' + fname) + except: + try: + if start > 0: + start -= 1 + page(open(fname).read(),start) + except: + print 'Unable to show file',`fname` + +#---------------------------------------------------------------------------- +def get_pager_cmd(pager_cmd = None): + """Return a pager command. + + Makes some attempts at finding an OS-correct one.""" + + if os.name == 'posix': + default_pager_cmd = 'less -r' # -r for color control sequences + elif os.name in ['nt','dos']: + default_pager_cmd = 'type' + + if pager_cmd is None: + try: + pager_cmd = os.environ['PAGER'] + except: + pager_cmd = default_pager_cmd + return pager_cmd + +#----------------------------------------------------------------------------- +def get_pager_start(pager,start): + """Return the string for paging files with an offset. + + This is the '+N' argument which less and more (under Unix) accept. + """ + + if pager in ['less','more']: + if start: + start_string = '+' + str(start) + else: + start_string = '' + else: + start_string = '' + return start_string + +#---------------------------------------------------------------------------- +# (X)emacs on W32 doesn't like to be bypassed with msvcrt.getch() +if os.name == 'nt' and os.environ.get('TERM','dumb') != 'emacs': + import msvcrt + def page_more(): + """ Smart pausing between pages + + @return: True if need print more lines, False if quit + """ + Term.cout.write('---Return to continue, q to quit--- ') + ans = msvcrt.getch() + if ans in ("q", "Q"): + result = False + else: + result = True + Term.cout.write("\b"*37 + " "*37 + "\b"*37) + return result +else: + def page_more(): + ans = raw_input('---Return to continue, q to quit--- ') + if ans.lower().startswith('q'): + return False + else: + return True + +#---------------------------------------------------------------------------- +def snip_print(str,width = 75,print_full = 0,header = ''): + """Print a string snipping the midsection to fit in width. + + print_full: mode control: + - 0: only snip long strings + - 1: send to page() directly. + - 2: snip long strings and ask for full length viewing with page() + Return 1 if snipping was necessary, 0 otherwise.""" + + if print_full == 1: + page(header+str) + return 0 + + print header, + if len(str) < width: + print str + snip = 0 + else: + whalf = int((width -5)/2) + print str[:whalf] + ' <...> ' + str[-whalf:] + snip = 1 + if snip and print_full == 2: + if raw_input(header+' Snipped. View (y/n)? [N]').lower() == 'y': + page(str) + return snip \ No newline at end of file diff --git a/IPython/core/prefilter.py b/IPython/core/prefilter.py new file mode 100755 index 0000000..2a8ca69 --- /dev/null +++ b/IPython/core/prefilter.py @@ -0,0 +1,995 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +Prefiltering components. + +Prefilters transform user input before it is exec'd by Python. These +transforms are used to implement additional syntax such as !ls and %magic. + +Authors: + +* Brian Granger +* Fernando Perez +* Dan Milstein +* Ville Vainio +""" + +#----------------------------------------------------------------------------- +# Copyright (C) 2008-2009 The IPython Development Team +# +# Distributed under the terms of the BSD License. The full license is in +# the file COPYING, distributed as part of this software. +#----------------------------------------------------------------------------- + +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- + +import __builtin__ +import codeop +import keyword +import os +import re +import sys + +from IPython.core.alias import AliasManager +from IPython.core.autocall import IPyAutocall +from IPython.core.component import Component +from IPython.core.splitinput import split_user_input +from IPython.core.page import page + +from IPython.utils.traitlets import List, Int, Any, Str, CBool, Bool +from IPython.utils.genutils import make_quoted_expr, Term +from IPython.utils.autoattr import auto_attr + +#----------------------------------------------------------------------------- +# Global utilities, errors and constants +#----------------------------------------------------------------------------- + +# Warning, these cannot be changed unless various regular expressions +# are updated in a number of places. Not great, but at least we told you. +ESC_SHELL = '!' +ESC_SH_CAP = '!!' +ESC_HELP = '?' +ESC_MAGIC = '%' +ESC_QUOTE = ',' +ESC_QUOTE2 = ';' +ESC_PAREN = '/' + + +class PrefilterError(Exception): + pass + + +# RegExp to identify potential function names +re_fun_name = re.compile(r'[a-zA-Z_]([a-zA-Z0-9_.]*) *$') + +# RegExp to exclude strings with this start from autocalling. In +# particular, all binary operators should be excluded, so that if foo is +# callable, foo OP bar doesn't become foo(OP bar), which is invalid. The +# characters '!=()' don't need to be checked for, as the checkPythonChars +# routine explicitely does so, to catch direct calls and rebindings of +# existing names. + +# Warning: the '-' HAS TO BE AT THE END of the first group, otherwise +# it affects the rest of the group in square brackets. +re_exclude_auto = re.compile(r'^[,&^\|\*/\+-]' + r'|^is |^not |^in |^and |^or ') + +# try to catch also methods for stuff in lists/tuples/dicts: off +# (experimental). For this to work, the line_split regexp would need +# to be modified so it wouldn't break things at '['. That line is +# nasty enough that I shouldn't change it until I can test it _well_. +#self.re_fun_name = re.compile (r'[a-zA-Z_]([a-zA-Z0-9_.\[\]]*) ?$') + + +# Handler Check Utilities +def is_shadowed(identifier, ip): + """Is the given identifier defined in one of the namespaces which shadow + the alias and magic namespaces? Note that an identifier is different + than ifun, because it can not contain a '.' character.""" + # This is much safer than calling ofind, which can change state + return (identifier in ip.user_ns \ + or identifier in ip.internal_ns \ + or identifier in ip.ns_table['builtin']) + + +#----------------------------------------------------------------------------- +# The LineInfo class used throughout +#----------------------------------------------------------------------------- + + +class LineInfo(object): + """A single line of input and associated info. + + Includes the following as properties: + + line + The original, raw line + + continue_prompt + Is this line a continuation in a sequence of multiline input? + + pre + The initial esc character or whitespace. + + pre_char + The escape character(s) in pre or the empty string if there isn't one. + Note that '!!' is a possible value for pre_char. Otherwise it will + always be a single character. + + pre_whitespace + The leading whitespace from pre if it exists. If there is a pre_char, + this is just ''. + + ifun + The 'function part', which is basically the maximal initial sequence + of valid python identifiers and the '.' character. This is what is + checked for alias and magic transformations, used for auto-calling, + etc. + + the_rest + Everything else on the line. + """ + def __init__(self, line, continue_prompt): + self.line = line + self.continue_prompt = continue_prompt + self.pre, self.ifun, self.the_rest = split_user_input(line) + + self.pre_char = self.pre.strip() + if self.pre_char: + self.pre_whitespace = '' # No whitespace allowd before esc chars + else: + self.pre_whitespace = self.pre + + self._oinfo = None + + def ofind(self, ip): + """Do a full, attribute-walking lookup of the ifun in the various + namespaces for the given IPython InteractiveShell instance. + + Return a dict with keys: found,obj,ospace,ismagic + + Note: can cause state changes because of calling getattr, but should + only be run if autocall is on and if the line hasn't matched any + other, less dangerous handlers. + + Does cache the results of the call, so can be called multiple times + without worrying about *further* damaging state. + """ + if not self._oinfo: + self._oinfo = ip._ofind(self.ifun) + return self._oinfo + + def __str__(self): + return "Lineinfo [%s|%s|%s]" %(self.pre,self.ifun,self.the_rest) + + +#----------------------------------------------------------------------------- +# Main Prefilter manager +#----------------------------------------------------------------------------- + + +class PrefilterManager(Component): + """Main prefilter component. + + The IPython prefilter is run on all user input before it is run. The + prefilter consumes lines of input and produces transformed lines of + input. + + The iplementation consists of two phases: + + 1. Transformers + 2. Checkers and handlers + + Over time, we plan on deprecating the checkers and handlers and doing + everything in the transformers. + + The transformers are instances of :class:`PrefilterTransformer` and have + a single method :meth:`transform` that takes a line and returns a + transformed line. The transformation can be accomplished using any + tool, but our current ones use regular expressions for speed. We also + ship :mod:`pyparsing` in :mod:`IPython.external` for use in transformers. + + After all the transformers have been run, the line is fed to the checkers, + which are instances of :class:`PrefilterChecker`. The line is passed to + the :meth:`check` method, which either returns `None` or a + :class:`PrefilterHandler` instance. If `None` is returned, the other + checkers are tried. If an :class:`PrefilterHandler` instance is returned, + the line is passed to the :meth:`handle` method of the returned + handler and no further checkers are tried. + + Both transformers and checkers have a `priority` attribute, that determines + the order in which they are called. Smaller priorities are tried first. + + Both transformers and checkers also have `enabled` attribute, which is + a boolean that determines if the instance is used. + + Users or developers can change the priority or enabled attribute of + transformers or checkers, but they must call the :meth:`sort_checkers` + or :meth:`sort_transformers` method after changing the priority. + """ + + multi_line_specials = CBool(True, config=True) + + def __init__(self, parent, config=None): + super(PrefilterManager, self).__init__(parent, config=config) + self.init_transformers() + self.init_handlers() + self.init_checkers() + + @auto_attr + def shell(self): + return Component.get_instances( + root=self.root, + klass='IPython.core.iplib.InteractiveShell')[0] + + #------------------------------------------------------------------------- + # API for managing transformers + #------------------------------------------------------------------------- + + def init_transformers(self): + """Create the default transformers.""" + self._transformers = [] + for transformer_cls in _default_transformers: + transformer_cls(self, config=self.config) + + def sort_transformers(self): + """Sort the transformers by priority. + + This must be called after the priority of a transformer is changed. + The :meth:`register_transformer` method calls this automatically. + """ + self._transformers.sort(cmp=lambda x,y: x.priority-y.priority) + + @property + def transformers(self): + """Return a list of checkers, sorted by priority.""" + return self._transformers + + def register_transformer(self, transformer): + """Register a transformer instance.""" + if transformer not in self._transformers: + self._transformers.append(transformer) + self.sort_transformers() + + def unregister_transformer(self, transformer): + """Unregister a transformer instance.""" + if transformer in self._transformers: + self._transformers.remove(transformer) + + #------------------------------------------------------------------------- + # API for managing checkers + #------------------------------------------------------------------------- + + def init_checkers(self): + """Create the default checkers.""" + self._checkers = [] + for checker in _default_checkers: + checker(self, config=self.config) + + def sort_checkers(self): + """Sort the checkers by priority. + + This must be called after the priority of a checker is changed. + The :meth:`register_checker` method calls this automatically. + """ + self._checkers.sort(cmp=lambda x,y: x.priority-y.priority) + + @property + def checkers(self): + """Return a list of checkers, sorted by priority.""" + return self._checkers + + def register_checker(self, checker): + """Register a checker instance.""" + if checker not in self._checkers: + self._checkers.append(checker) + self.sort_checkers() + + def unregister_checker(self, checker): + """Unregister a checker instance.""" + if checker in self._checkers: + self._checkers.remove(checker) + + #------------------------------------------------------------------------- + # API for managing checkers + #------------------------------------------------------------------------- + + def init_handlers(self): + """Create the default handlers.""" + self._handlers = {} + self._esc_handlers = {} + for handler in _default_handlers: + handler(self, config=self.config) + + @property + def handlers(self): + """Return a dict of all the handlers.""" + return self._handlers + + def register_handler(self, name, handler, esc_strings): + """Register a handler instance by name with esc_strings.""" + self._handlers[name] = handler + for esc_str in esc_strings: + self._esc_handlers[esc_str] = handler + + def unregister_handler(self, name, handler, esc_strings): + """Unregister a handler instance by name with esc_strings.""" + try: + del self._handlers[name] + except KeyError: + pass + for esc_str in esc_strings: + h = self._esc_handlers.get(esc_str) + if h is handler: + del self._esc_handlers[esc_str] + + def get_handler_by_name(self, name): + """Get a handler by its name.""" + return self._handlers.get(name) + + def get_handler_by_esc(self, esc_str): + """Get a handler by its escape string.""" + return self._esc_handlers.get(esc_str) + + #------------------------------------------------------------------------- + # Main prefiltering API + #------------------------------------------------------------------------- + + def prefilter_line_info(self, line_info): + """Prefilter a line that has been converted to a LineInfo object. + + This implements the checker/handler part of the prefilter pipe. + """ + # print "prefilter_line_info: ", line_info + handler = self.find_handler(line_info) + return handler.handle(line_info) + + def find_handler(self, line_info): + """Find a handler for the line_info by trying checkers.""" + for checker in self.checkers: + if checker.enabled: + handler = checker.check(line_info) + if handler: + return handler + return self.get_handler_by_name('normal') + + def transform_line(self, line, continue_prompt): + """Calls the enabled transformers in order of increasing priority.""" + for transformer in self.transformers: + if transformer.enabled: + line = transformer.transform(line, continue_prompt) + return line + + def prefilter_line(self, line, continue_prompt): + """Prefilter a single input line as text. + + This method prefilters a single line of text by calling the + transformers and then the checkers/handlers. + """ + + # print "prefilter_line: ", line, continue_prompt + # All handlers *must* return a value, even if it's blank (''). + + # Lines are NOT logged here. Handlers should process the line as + # needed, update the cache AND log it (so that the input cache array + # stays synced). + + # save the line away in case we crash, so the post-mortem handler can + # record it + self.shell._last_input_line = line + + if not line: + # Return immediately on purely empty lines, so that if the user + # previously typed some whitespace that started a continuation + # prompt, he can break out of that loop with just an empty line. + # This is how the default python prompt works. + + # Only return if the accumulated input buffer was just whitespace! + if ''.join(self.shell.buffer).isspace(): + self.shell.buffer[:] = [] + return '' + + # At this point, we invoke our transformers. + if not continue_prompt or (continue_prompt and self.multi_line_specials): + line = self.transform_line(line, continue_prompt) + + # Now we compute line_info for the checkers and handlers + line_info = LineInfo(line, continue_prompt) + + # the input history needs to track even empty lines + stripped = line.strip() + + normal_handler = self.get_handler_by_name('normal') + if not stripped: + if not continue_prompt: + self.shell.outputcache.prompt_count -= 1 + + return normal_handler.handle(line_info) + + # special handlers are only allowed for single line statements + if continue_prompt and not self.multi_line_specials: + return normal_handler.handle(line_info) + + prefiltered = self.prefilter_line_info(line_info) + # print "prefiltered line: %r" % prefiltered + return prefiltered + + def prefilter_lines(self, lines, continue_prompt): + """Prefilter multiple input lines of text. + + This is the main entry point for prefiltering multiple lines of + input. This simply calls :meth:`prefilter_line` for each line of + input. + + This covers cases where there are multiple lines in the user entry, + which is the case when the user goes back to a multiline history + entry and presses enter. + """ + out = [] + for line in lines.rstrip('\n').split('\n'): + out.append(self.prefilter_line(line, continue_prompt)) + return '\n'.join(out) + + +#----------------------------------------------------------------------------- +# Prefilter transformers +#----------------------------------------------------------------------------- + + +class PrefilterTransformer(Component): + """Transform a line of user input.""" + + priority = Int(100, config=True) + shell = Any + prefilter_manager = Any + enabled = Bool(True, config=True) + + def __init__(self, parent, config=None): + super(PrefilterTransformer, self).__init__(parent, config=config) + self.prefilter_manager.register_transformer(self) + + @auto_attr + def shell(self): + return Component.get_instances( + root=self.root, + klass='IPython.core.iplib.InteractiveShell')[0] + + @auto_attr + def prefilter_manager(self): + return PrefilterManager.get_instances(root=self.root)[0] + + def transform(self, line, continue_prompt): + """Transform a line, returning the new one.""" + return None + + def __repr__(self): + return "<%s(priority=%r, enabled=%r)>" % ( + self.__class__.__name__, self.priority, self.enabled) + + +_assign_system_re = re.compile(r'(?P(\s*)([\w\.]+)((\s*,\s*[\w\.]+)*))' + r'\s*=\s*!(?P.*)') + + +class AssignSystemTransformer(PrefilterTransformer): + """Handle the `files = !ls` syntax.""" + + priority = Int(100, config=True) + + def transform(self, line, continue_prompt): + m = _assign_system_re.match(line) + if m is not None: + cmd = m.group('cmd') + lhs = m.group('lhs') + expr = make_quoted_expr("sc -l =%s" % cmd) + new_line = '%s = get_ipython().magic(%s)' % (lhs, expr) + return new_line + return line + + +_assign_magic_re = re.compile(r'(?P(\s*)([\w\.]+)((\s*,\s*[\w\.]+)*))' + r'\s*=\s*%(?P.*)') + +class AssignMagicTransformer(PrefilterTransformer): + """Handle the `a = %who` syntax.""" + + priority = Int(200, config=True) + + def transform(self, line, continue_prompt): + m = _assign_magic_re.match(line) + if m is not None: + cmd = m.group('cmd') + lhs = m.group('lhs') + expr = make_quoted_expr(cmd) + new_line = '%s = get_ipython().magic(%s)' % (lhs, expr) + return new_line + return line + + +#----------------------------------------------------------------------------- +# Prefilter checkers +#----------------------------------------------------------------------------- + + +class PrefilterChecker(Component): + """Inspect an input line and return a handler for that line.""" + + priority = Int(100, config=True) + shell = Any + prefilter_manager = Any + enabled = Bool(True, config=True) + + def __init__(self, parent, config=None): + super(PrefilterChecker, self).__init__(parent, config=config) + self.prefilter_manager.register_checker(self) + + @auto_attr + def shell(self): + return Component.get_instances( + root=self.root, + klass='IPython.core.iplib.InteractiveShell')[0] + + @auto_attr + def prefilter_manager(self): + return PrefilterManager.get_instances(root=self.root)[0] + + def check(self, line_info): + """Inspect line_info and return a handler instance or None.""" + return None + + def __repr__(self): + return "<%s(priority=%r, enabled=%r)>" % ( + self.__class__.__name__, self.priority, self.enabled) + + +class EmacsChecker(PrefilterChecker): + + priority = Int(100, config=True) + enabled = Bool(False, config=True) + + def check(self, line_info): + "Emacs ipython-mode tags certain input lines." + if line_info.line.endswith('# PYTHON-MODE'): + return self.prefilter_manager.get_handler_by_name('emacs') + else: + return None + + +class ShellEscapeChecker(PrefilterChecker): + + priority = Int(200, config=True) + + def check(self, line_info): + if line_info.line.lstrip().startswith(ESC_SHELL): + return self.prefilter_manager.get_handler_by_name('shell') + + +class IPyAutocallChecker(PrefilterChecker): + + priority = Int(300, config=True) + + def check(self, line_info): + "Instances of IPyAutocall in user_ns get autocalled immediately" + obj = self.shell.user_ns.get(line_info.ifun, None) + if isinstance(obj, IPyAutocall): + obj.set_ip(self.shell) + return self.prefilter_manager.get_handler_by_name('auto') + else: + return None + + +class MultiLineMagicChecker(PrefilterChecker): + + priority = Int(400, config=True) + + def check(self, line_info): + "Allow ! and !! in multi-line statements if multi_line_specials is on" + # Note that this one of the only places we check the first character of + # ifun and *not* the pre_char. Also note that the below test matches + # both ! and !!. + if line_info.continue_prompt \ + and self.prefilter_manager.multi_line_specials: + if line_info.ifun.startswith(ESC_MAGIC): + return self.prefilter_manager.get_handler_by_name('magic') + else: + return None + + +class EscCharsChecker(PrefilterChecker): + + priority = Int(500, config=True) + + def check(self, line_info): + """Check for escape character and return either a handler to handle it, + or None if there is no escape char.""" + if line_info.line[-1] == ESC_HELP \ + and line_info.pre_char != ESC_SHELL \ + and line_info.pre_char != ESC_SH_CAP: + # the ? can be at the end, but *not* for either kind of shell escape, + # because a ? can be a vaild final char in a shell cmd + return self.prefilter_manager.get_handler_by_name('help') + else: + # This returns None like it should if no handler exists + return self.prefilter_manager.get_handler_by_esc(line_info.pre_char) + + +class AssignmentChecker(PrefilterChecker): + + priority = Int(600, config=True) + + def check(self, line_info): + """Check to see if user is assigning to a var for the first time, in + which case we want to avoid any sort of automagic / autocall games. + + This allows users to assign to either alias or magic names true python + variables (the magic/alias systems always take second seat to true + python code). E.g. ls='hi', or ls,that=1,2""" + if line_info.the_rest: + if line_info.the_rest[0] in '=,': + return self.prefilter_manager.get_handler_by_name('normal') + else: + return None + + +class AutoMagicChecker(PrefilterChecker): + + priority = Int(700, config=True) + + def check(self, line_info): + """If the ifun is magic, and automagic is on, run it. Note: normal, + non-auto magic would already have been triggered via '%' in + check_esc_chars. This just checks for automagic. Also, before + triggering the magic handler, make sure that there is nothing in the + user namespace which could shadow it.""" + if not self.shell.automagic or not hasattr(self.shell,'magic_'+line_info.ifun): + return None + + # We have a likely magic method. Make sure we should actually call it. + if line_info.continue_prompt and not self.shell.multi_line_specials: + return None + + head = line_info.ifun.split('.',1)[0] + if is_shadowed(head, self.shell): + return None + + return self.prefilter_manager.get_handler_by_name('magic') + + +class AliasChecker(PrefilterChecker): + + priority = Int(800, config=True) + + @auto_attr + def alias_manager(self): + return AliasManager.get_instances(root=self.root)[0] + + def check(self, line_info): + "Check if the initital identifier on the line is an alias." + # Note: aliases can not contain '.' + head = line_info.ifun.split('.',1)[0] + if line_info.ifun not in self.alias_manager \ + or head not in self.alias_manager \ + or is_shadowed(head, self.shell): + return None + + return self.prefilter_manager.get_handler_by_name('alias') + + +class PythonOpsChecker(PrefilterChecker): + + priority = Int(900, config=True) + + def check(self, line_info): + """If the 'rest' of the line begins with a function call or pretty much + any python operator, we should simply execute the line (regardless of + whether or not there's a possible autocall expansion). This avoids + spurious (and very confusing) geattr() accesses.""" + if line_info.the_rest and line_info.the_rest[0] in '!=()<>,+*/%^&|': + return self.prefilter_manager.get_handler_by_name('normal') + else: + return None + + +class AutocallChecker(PrefilterChecker): + + priority = Int(1000, config=True) + + def check(self, line_info): + "Check if the initial word/function is callable and autocall is on." + if not self.shell.autocall: + return None + + oinfo = line_info.ofind(self.shell) # This can mutate state via getattr + if not oinfo['found']: + return None + + if callable(oinfo['obj']) \ + and (not re_exclude_auto.match(line_info.the_rest)) \ + and re_fun_name.match(line_info.ifun): + return self.prefilter_manager.get_handler_by_name('auto') + else: + return None + + +#----------------------------------------------------------------------------- +# Prefilter handlers +#----------------------------------------------------------------------------- + + +class PrefilterHandler(Component): + + handler_name = Str('normal') + esc_strings = List([]) + shell = Any + prefilter_manager = Any + + def __init__(self, parent, config=None): + super(PrefilterHandler, self).__init__(parent, config=config) + self.prefilter_manager.register_handler( + self.handler_name, + self, + self.esc_strings + ) + + @auto_attr + def shell(self): + return Component.get_instances( + root=self.root, + klass='IPython.core.iplib.InteractiveShell')[0] + + @auto_attr + def prefilter_manager(self): + return PrefilterManager.get_instances(root=self.root)[0] + + def handle(self, line_info): + # print "normal: ", line_info + """Handle normal input lines. Use as a template for handlers.""" + + # With autoindent on, we need some way to exit the input loop, and I + # don't want to force the user to have to backspace all the way to + # clear the line. The rule will be in this case, that either two + # lines of pure whitespace in a row, or a line of pure whitespace but + # of a size different to the indent level, will exit the input loop. + line = line_info.line + continue_prompt = line_info.continue_prompt + + if (continue_prompt and self.shell.autoindent and line.isspace() and + (0 < abs(len(line) - self.shell.indent_current_nsp) <= 2 or + (self.shell.buffer[-1]).isspace() )): + line = '' + + self.shell.log(line, line, continue_prompt) + return line + + def __str__(self): + return "<%s(name=%s)>" % (self.__class__.__name__, self.handler_name) + + +class AliasHandler(PrefilterHandler): + + handler_name = Str('alias') + + @auto_attr + def alias_manager(self): + return AliasManager.get_instances(root=self.root)[0] + + def handle(self, line_info): + """Handle alias input lines. """ + transformed = self.alias_manager.expand_aliases(line_info.ifun,line_info.the_rest) + # pre is needed, because it carries the leading whitespace. Otherwise + # aliases won't work in indented sections. + line_out = '%sget_ipython().system(%s)' % (line_info.pre_whitespace, + make_quoted_expr(transformed)) + + self.shell.log(line_info.line, line_out, line_info.continue_prompt) + return line_out + + +class ShellEscapeHandler(PrefilterHandler): + + handler_name = Str('shell') + esc_strings = List([ESC_SHELL, ESC_SH_CAP]) + + def handle(self, line_info): + """Execute the line in a shell, empty return value""" + magic_handler = self.prefilter_manager.get_handler_by_name('magic') + + line = line_info.line + if line.lstrip().startswith(ESC_SH_CAP): + # rewrite LineInfo's line, ifun and the_rest to properly hold the + # call to %sx and the actual command to be executed, so + # handle_magic can work correctly. Note that this works even if + # the line is indented, so it handles multi_line_specials + # properly. + new_rest = line.lstrip()[2:] + line_info.line = '%ssx %s' % (ESC_MAGIC, new_rest) + line_info.ifun = 'sx' + line_info.the_rest = new_rest + return magic_handler.handle(line_info) + else: + cmd = line.lstrip().lstrip(ESC_SHELL) + line_out = '%sget_ipython().system(%s)' % (line_info.pre_whitespace, + make_quoted_expr(cmd)) + # update cache/log and return + self.shell.log(line, line_out, line_info.continue_prompt) + return line_out + + +class MagicHandler(PrefilterHandler): + + handler_name = Str('magic') + esc_strings = List([ESC_MAGIC]) + + def handle(self, line_info): + """Execute magic functions.""" + ifun = line_info.ifun + the_rest = line_info.the_rest + cmd = '%sget_ipython().magic(%s)' % (line_info.pre_whitespace, + make_quoted_expr(ifun + " " + the_rest)) + self.shell.log(line_info.line, cmd, line_info.continue_prompt) + return cmd + + +class AutoHandler(PrefilterHandler): + + handler_name = Str('auto') + esc_strings = List([ESC_PAREN, ESC_QUOTE, ESC_QUOTE2]) + + def handle(self, line_info): + """Hande lines which can be auto-executed, quoting if requested.""" + line = line_info.line + ifun = line_info.ifun + the_rest = line_info.the_rest + pre = line_info.pre + continue_prompt = line_info.continue_prompt + obj = line_info.ofind(self)['obj'] + + #print 'pre <%s> ifun <%s> rest <%s>' % (pre,ifun,the_rest) # dbg + + # This should only be active for single-line input! + if continue_prompt: + self.log(line,line,continue_prompt) + return line + + force_auto = isinstance(obj, IPyAutocall) + auto_rewrite = True + + if pre == ESC_QUOTE: + # Auto-quote splitting on whitespace + newcmd = '%s("%s")' % (ifun,'", "'.join(the_rest.split()) ) + elif pre == ESC_QUOTE2: + # Auto-quote whole string + newcmd = '%s("%s")' % (ifun,the_rest) + elif pre == ESC_PAREN: + newcmd = '%s(%s)' % (ifun,",".join(the_rest.split())) + else: + # Auto-paren. + # We only apply it to argument-less calls if the autocall + # parameter is set to 2. We only need to check that autocall is < + # 2, since this function isn't called unless it's at least 1. + if not the_rest and (self.shell.autocall < 2) and not force_auto: + newcmd = '%s %s' % (ifun,the_rest) + auto_rewrite = False + else: + if not force_auto and the_rest.startswith('['): + if hasattr(obj,'__getitem__'): + # Don't autocall in this case: item access for an object + # which is BOTH callable and implements __getitem__. + newcmd = '%s %s' % (ifun,the_rest) + auto_rewrite = False + else: + # if the object doesn't support [] access, go ahead and + # autocall + newcmd = '%s(%s)' % (ifun.rstrip(),the_rest) + elif the_rest.endswith(';'): + newcmd = '%s(%s);' % (ifun.rstrip(),the_rest[:-1]) + else: + newcmd = '%s(%s)' % (ifun.rstrip(), the_rest) + + if auto_rewrite: + rw = self.shell.outputcache.prompt1.auto_rewrite() + newcmd + + try: + # plain ascii works better w/ pyreadline, on some machines, so + # we use it and only print uncolored rewrite if we have unicode + rw = str(rw) + print >>Term.cout, rw + except UnicodeEncodeError: + print "-------------->" + newcmd + + # log what is now valid Python, not the actual user input (without the + # final newline) + self.shell.log(line,newcmd,continue_prompt) + return newcmd + + +class HelpHandler(PrefilterHandler): + + handler_name = Str('help') + esc_strings = List([ESC_HELP]) + + def handle(self, line_info): + """Try to get some help for the object. + + obj? or ?obj -> basic information. + obj?? or ??obj -> more details. + """ + normal_handler = self.prefilter_manager.get_handler_by_name('normal') + line = line_info.line + # We need to make sure that we don't process lines which would be + # otherwise valid python, such as "x=1 # what?" + try: + codeop.compile_command(line) + except SyntaxError: + # We should only handle as help stuff which is NOT valid syntax + if line[0]==ESC_HELP: + line = line[1:] + elif line[-1]==ESC_HELP: + line = line[:-1] + self.shell.log(line, '#?'+line, line_info.continue_prompt) + if line: + #print 'line:<%r>' % line # dbg + self.shell.magic_pinfo(line) + else: + page(self.shell.usage, screen_lines=self.shell.usable_screen_length) + return '' # Empty string is needed here! + except: + raise + # Pass any other exceptions through to the normal handler + return normal_handler.handle(line_info) + else: + raise + # If the code compiles ok, we should handle it normally + return normal_handler.handle(line_info) + + +class EmacsHandler(PrefilterHandler): + + handler_name = Str('emacs') + esc_strings = List([]) + + def handle(self, line_info): + """Handle input lines marked by python-mode.""" + + # Currently, nothing is done. Later more functionality can be added + # here if needed. + + # The input cache shouldn't be updated + return line_info.line + + +#----------------------------------------------------------------------------- +# Defaults +#----------------------------------------------------------------------------- + + +_default_transformers = [ + AssignSystemTransformer, + AssignMagicTransformer +] + +_default_checkers = [ + EmacsChecker, + ShellEscapeChecker, + IPyAutocallChecker, + MultiLineMagicChecker, + EscCharsChecker, + AssignmentChecker, + AutoMagicChecker, + AliasChecker, + PythonOpsChecker, + AutocallChecker +] + +_default_handlers = [ + PrefilterHandler, + AliasHandler, + ShellEscapeHandler, + MagicHandler, + AutoHandler, + HelpHandler, + EmacsHandler +] + diff --git a/IPython/Prompts.py b/IPython/core/prompts.py similarity index 89% rename from IPython/Prompts.py rename to IPython/core/prompts.py index 21f1086..228edb7 100644 --- a/IPython/Prompts.py +++ b/IPython/core/prompts.py @@ -20,23 +20,24 @@ import sys import time # IPython's own -from IPython import ColorANSI -from IPython import Release +from IPython.utils import coloransi +from IPython.core import release from IPython.external.Itpl import ItplNS -from IPython.ipapi import TryNext -from IPython.ipstruct import Struct -from IPython.macro import Macro +from IPython.core.error import TryNext +from IPython.utils.ipstruct import Struct +from IPython.core.macro import Macro +import IPython.utils.generics -from IPython.genutils import * +from IPython.utils.genutils import * #**************************************************************************** #Color schemes for Prompts. -PromptColors = ColorANSI.ColorSchemeTable() -InputColors = ColorANSI.InputTermColors # just a shorthand -Colors = ColorANSI.TermColors # just a shorthand +PromptColors = coloransi.ColorSchemeTable() +InputColors = coloransi.InputTermColors # just a shorthand +Colors = coloransi.TermColors # just a shorthand -PromptColors.add_scheme(ColorANSI.ColorScheme( +PromptColors.add_scheme(coloransi.ColorScheme( 'NoColor', in_prompt = InputColors.NoColor, # Input prompt in_number = InputColors.NoColor, # Input prompt number @@ -50,7 +51,7 @@ PromptColors.add_scheme(ColorANSI.ColorScheme( )) # make some schemes as instances so we can copy them for modification easily: -__PColLinux = ColorANSI.ColorScheme( +__PColLinux = coloransi.ColorScheme( 'Linux', in_prompt = InputColors.Green, in_number = InputColors.LightGreen, @@ -169,7 +170,7 @@ prompt_specials_color = { # Carriage return r'\r': '\r', # Release version - r'\v': Release.version, + r'\v': release.version, # Root symbol ($ or #) r'\$': ROOT_SYMBOL, } @@ -185,7 +186,7 @@ prompt_specials_nocolor[r'\#'] = '${self.cache.prompt_count}' # with a color name which may begin with a letter used by any other of the # allowed specials. This of course means that \\C will never be allowed for # anything else. -input_colors = ColorANSI.InputTermColors +input_colors = coloransi.InputTermColors for _color in dir(input_colors): if _color[0] != '_': c_name = r'\C_'+_color @@ -545,8 +546,11 @@ class CachedOutput: # don't use print, puts an extra space cout_write(self.output_sep) outprompt = self.shell.hooks.generate_output_prompt() + # print "Got prompt: ", outprompt if self.do_full_cache: cout_write(outprompt) + else: + print "self.do_full_cache = False" # and now call a possibly user-defined print mechanism manipulated_val = self.display(arg) @@ -573,7 +577,7 @@ class CachedOutput: display, e.g. when your own objects need special formatting. """ try: - return IPython.generics.result_display(arg) + return IPython.utils.generics.result_display(arg) except TryNext: return self.shell.hooks.result_display(arg) diff --git a/IPython/core/quitter.py b/IPython/core/quitter.py new file mode 100644 index 0000000..f7151f1 --- /dev/null +++ b/IPython/core/quitter.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +A simple class for quitting IPython. + +Authors: + +* Brian Granger +""" + +#----------------------------------------------------------------------------- +# Copyright (C) 2008-2009 The IPython Development Team +# +# Distributed under the terms of the BSD License. The full license is in +# the file COPYING, distributed as part of this software. +#----------------------------------------------------------------------------- + +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- + + +class Quitter(object): + """Simple class to handle exit, similar to Python 2.5's. + + It handles exiting in an ipython-safe manner, which the one in Python 2.5 + doesn't do (obviously, since it doesn't know about ipython).""" + + def __init__(self, shell, name): + self.shell = shell + self.name = name + + def __repr__(self): + return 'Type %s() to exit.' % self.name + __str__ = __repr__ + + def __call__(self): + self.shell.exit() \ No newline at end of file diff --git a/IPython/Release.py b/IPython/core/release.py similarity index 93% rename from IPython/Release.py rename to IPython/core/release.py index 5a6e465..587afc5 100644 --- a/IPython/Release.py +++ b/IPython/core/release.py @@ -2,8 +2,8 @@ """Release data for the IPython project.""" #***************************************************************************** -# Copyright (C) 2001-2006 Fernando Perez -# +# Copyright (C) 2008-2009 The IPython Development Team +# Copyright (C) 2001-2008 Fernando Perez # Copyright (c) 2001 Janko Hauser and Nathaniel Gray # # @@ -21,9 +21,9 @@ name = 'ipython' # bdist_deb does not accept underscores (a Debian convention). development = True # change this to False to do a release -version_base = '0.10' +version_base = '0.11' branch = 'ipython' -revision = '1163' +revision = '1205' if development: if branch == 'ipython': @@ -100,7 +100,7 @@ site `_. license = 'BSD' -authors = {'Fernando' : ('Fernando Perez','fperez@colorado.edu'), +authors = {'Fernando' : ('Fernando Perez','fperez.net@gmail.com'), 'Janko' : ('Janko Hauser','jhauser@zscout.de'), 'Nathan' : ('Nathaniel Gray','n8gray@caltech.edu'), 'Ville' : ('Ville Vainio','vivainio@gmail.com'), diff --git a/IPython/shadowns.py b/IPython/core/shadowns.py similarity index 100% rename from IPython/shadowns.py rename to IPython/core/shadowns.py diff --git a/IPython/core/splitinput.py b/IPython/core/splitinput.py new file mode 100644 index 0000000..ebf323f --- /dev/null +++ b/IPython/core/splitinput.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +Simple utility for splitting user input. + +Authors: + +* Brian Granger +* Fernando Perez +""" + +#----------------------------------------------------------------------------- +# Copyright (C) 2008-2009 The IPython Development Team +# +# Distributed under the terms of the BSD License. The full license is in +# the file COPYING, distributed as part of this software. +#----------------------------------------------------------------------------- + +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- + +import re + +#----------------------------------------------------------------------------- +# Main function +#----------------------------------------------------------------------------- + + +# RegExp for splitting line contents into pre-char//first word-method//rest. +# For clarity, each group in on one line. + +# WARNING: update the regexp if the escapes in iplib are changed, as they +# are hardwired in. + +# Although it's not solely driven by the regex, note that: +# ,;/% only trigger if they are the first character on the line +# ! and !! trigger if they are first char(s) *or* follow an indent +# ? triggers as first or last char. + +# The three parts of the regex are: +# 1) pre: pre_char *or* initial whitespace +# 2) ifun: first word/method (mix of \w and '.') +# 3) the_rest: rest of line (separated from ifun by space if non-empty) +line_split = re.compile(r'^([,;/%?]|!!?|\s*)' + r'\s*([\w\.]+)' + r'(\s+.*$|$)') + +# r'[\w\.]+' +# r'\s*=\s*%.*' + +def split_user_input(line, pattern=None): + """Split user input into pre-char/whitespace, function part and rest. + + This is currently handles lines with '=' in them in a very inconsistent + manner. + """ + + if pattern is None: + pattern = line_split + match = pattern.match(line) + if not match: + # print "match failed for line '%s'" % line + try: + ifun, the_rest = line.split(None,1) + except ValueError: + # print "split failed for line '%s'" % line + ifun, the_rest = line,'' + pre = re.match('^(\s*)(.*)',line).groups()[0] + else: + pre,ifun,the_rest = match.groups() + + # ifun has to be a valid python identifier, so it better be only pure + # ascii, no unicode: + try: + ifun = ifun.encode('ascii') + except UnicodeEncodeError: + the_rest = ifun + u' ' + the_rest + ifun = u'' + + #print 'line:<%s>' % line # dbg + #print 'pre <%s> ifun <%s> rest <%s>' % (pre,ifun.strip(),the_rest) # dbg + return pre, ifun.strip(), the_rest.lstrip() diff --git a/IPython/tools/tests/__init__.py b/IPython/core/tests/__init__.py similarity index 100% rename from IPython/tools/tests/__init__.py rename to IPython/core/tests/__init__.py diff --git a/IPython/tests/obj_del.py b/IPython/core/tests/obj_del.py similarity index 93% rename from IPython/tests/obj_del.py rename to IPython/core/tests/obj_del.py index 8ea9d18..d925dc6 100644 --- a/IPython/tests/obj_del.py +++ b/IPython/core/tests/obj_del.py @@ -31,4 +31,5 @@ class A(object): a = A() # Now, we force an exit, the caller will check that the del printout was given -_ip.IP.ask_exit() +_ip = get_ipython() +_ip.ask_exit() diff --git a/IPython/tests/refbug.py b/IPython/core/tests/refbug.py similarity index 96% rename from IPython/tests/refbug.py rename to IPython/core/tests/refbug.py index 99aca19..6b2ae2b 100644 --- a/IPython/tests/refbug.py +++ b/IPython/core/tests/refbug.py @@ -18,7 +18,7 @@ test_magic.py calls it. #----------------------------------------------------------------------------- import sys -from IPython import ipapi +from IPython.core import ipapi #----------------------------------------------------------------------------- # Globals diff --git a/IPython/tests/tclass.py b/IPython/core/tests/tclass.py similarity index 100% rename from IPython/tests/tclass.py rename to IPython/core/tests/tclass.py diff --git a/IPython/core/tests/test_component.py b/IPython/core/tests/test_component.py new file mode 100644 index 0000000..9774320 --- /dev/null +++ b/IPython/core/tests/test_component.py @@ -0,0 +1,214 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +Tests for IPython.core.component + +Authors: + +* Brian Granger +* Fernando Perez (design help) +""" + +#----------------------------------------------------------------------------- +# Copyright (C) 2008-2009 The IPython Development Team +# +# Distributed under the terms of the BSD License. The full license is in +# the file COPYING, distributed as part of this software. +#----------------------------------------------------------------------------- + +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- + +from unittest import TestCase + +from IPython.core.component import Component, ComponentError +from IPython.utils.traitlets import ( + TraitletError, Int, Float, Str +) +from IPython.config.loader import Config + + +#----------------------------------------------------------------------------- +# Test cases +#----------------------------------------------------------------------------- + + +class TestComponentMeta(TestCase): + + def test_get_instances(self): + class BaseComponent(Component): + pass + c1 = BaseComponent(None) + c2 = BaseComponent(c1) + self.assertEquals(BaseComponent.get_instances(),[c1,c2]) + + def test_get_instances_subclass(self): + class MyComponent(Component): + pass + class MyOtherComponent(MyComponent): + pass + c1 = MyComponent(None) + c2 = MyOtherComponent(c1) + c3 = MyOtherComponent(c2) + self.assertEquals(MyComponent.get_instances(), [c1, c2, c3]) + self.assertEquals(MyOtherComponent.get_instances(), [c2, c3]) + + def test_get_instances_root(self): + class MyComponent(Component): + pass + class MyOtherComponent(MyComponent): + pass + c1 = MyComponent(None) + c2 = MyOtherComponent(c1) + c3 = MyOtherComponent(c2) + c4 = MyComponent(None) + c5 = MyComponent(c4) + self.assertEquals(MyComponent.get_instances(root=c1), [c1, c2, c3]) + self.assertEquals(MyComponent.get_instances(root=c4), [c4, c5]) + + +class TestComponent(TestCase): + + def test_parent_child(self): + c1 = Component(None) + c2 = Component(c1) + c3 = Component(c1) + c4 = Component(c3) + self.assertEquals(c1.parent, None) + self.assertEquals(c2.parent, c1) + self.assertEquals(c3.parent, c1) + self.assertEquals(c4.parent, c3) + self.assertEquals(c1.children, [c2, c3]) + self.assertEquals(c2.children, []) + self.assertEquals(c3.children, [c4]) + self.assertEquals(c4.children, []) + + def test_root(self): + c1 = Component(None) + c2 = Component(c1) + c3 = Component(c1) + c4 = Component(c3) + self.assertEquals(c1.root, c1.root) + self.assertEquals(c2.root, c1) + self.assertEquals(c3.root, c1) + self.assertEquals(c4.root, c1) + + def test_change_parent(self): + c1 = Component(None) + c2 = Component(None) + c3 = Component(c1) + self.assertEquals(c3.root, c1) + self.assertEquals(c3.parent, c1) + self.assertEquals(c1.children,[c3]) + c3.parent = c2 + self.assertEquals(c3.root, c2) + self.assertEquals(c3.parent, c2) + self.assertEquals(c2.children,[c3]) + self.assertEquals(c1.children,[]) + + def test_subclass_parent(self): + c1 = Component(None) + self.assertRaises(TraitletError, setattr, c1, 'parent', 10) + + class MyComponent(Component): + pass + c1 = Component(None) + c2 = MyComponent(c1) + self.assertEquals(MyComponent.parent.this_class, Component) + self.assertEquals(c2.parent, c1) + + def test_bad_root(self): + c1 = Component(None) + c2 = Component(None) + c3 = Component(None) + self.assertRaises(ComponentError, setattr, c1, 'root', c2) + c1.parent = c2 + self.assertEquals(c1.root, c2) + self.assertRaises(ComponentError, setattr, c1, 'root', c3) + + +class TestComponentConfig(TestCase): + + def test_default(self): + c1 = Component(None) + c2 = Component(c1) + c3 = Component(c2) + self.assertEquals(c1.config, c2.config) + self.assertEquals(c2.config, c3.config) + + def test_custom(self): + config = Config() + config.foo = 'foo' + config.bar = 'bar' + c1 = Component(None, config=config) + c2 = Component(c1) + c3 = Component(c2) + self.assertEquals(c1.config, config) + self.assertEquals(c2.config, config) + self.assertEquals(c3.config, config) + # Test that copies are not made + self.assert_(c1.config is config) + self.assert_(c2.config is config) + self.assert_(c3.config is config) + self.assert_(c1.config is c2.config) + self.assert_(c2.config is c3.config) + + def test_inheritance(self): + class MyComponent(Component): + a = Int(1, config=True) + b = Float(1.0, config=True) + c = Str('no config') + config = Config() + config.MyComponent.a = 2 + config.MyComponent.b = 2.0 + c1 = MyComponent(None, config=config) + c2 = MyComponent(c1) + self.assertEquals(c1.a, config.MyComponent.a) + self.assertEquals(c1.b, config.MyComponent.b) + self.assertEquals(c2.a, config.MyComponent.a) + self.assertEquals(c2.b, config.MyComponent.b) + c4 = MyComponent(c2, config=Config()) + self.assertEquals(c4.a, 1) + self.assertEquals(c4.b, 1.0) + + def test_parent(self): + class Foo(Component): + a = Int(0, config=True) + b = Str('nope', config=True) + class Bar(Foo): + b = Str('gotit', config=False) + c = Float(config=True) + config = Config() + config.Foo.a = 10 + config.Foo.b = "wow" + config.Bar.b = 'later' + config.Bar.c = 100.0 + f = Foo(None, config=config) + b = Bar(f) + self.assertEquals(f.a, 10) + self.assertEquals(f.b, 'wow') + self.assertEquals(b.b, 'gotit') + self.assertEquals(b.c, 100.0) + + +class TestComponentName(TestCase): + + def test_default(self): + class MyComponent(Component): + pass + c1 = Component(None) + c2 = MyComponent(None) + c3 = Component(c2) + self.assertNotEquals(c1.name, c2.name) + self.assertNotEquals(c1.name, c3.name) + + def test_manual(self): + class MyComponent(Component): + pass + c1 = Component(None, name='foo') + c2 = MyComponent(None, name='bar') + c3 = Component(c2, name='bah') + self.assertEquals(c1.name, 'foo') + self.assertEquals(c2.name, 'bar') + self.assertEquals(c3.name, 'bah') diff --git a/IPython/tests/test_fakemodule.py b/IPython/core/tests/test_fakemodule.py similarity index 92% rename from IPython/tests/test_fakemodule.py rename to IPython/core/tests/test_fakemodule.py index 6325439..c8f0b3d 100644 --- a/IPython/tests/test_fakemodule.py +++ b/IPython/core/tests/test_fakemodule.py @@ -3,7 +3,7 @@ import nose.tools as nt -from IPython.FakeModule import FakeModule, init_fakemod_dict +from IPython.core.fakemodule import FakeModule, init_fakemod_dict # Make a fakemod and check a few properties def test_mk_fakemod(): diff --git a/IPython/core/tests/test_imports.py b/IPython/core/tests/test_imports.py new file mode 100644 index 0000000..6257539 --- /dev/null +++ b/IPython/core/tests/test_imports.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python +# encoding: utf-8 + +def test_import_completer(): + from IPython.core import completer + +def test_import_crashhandler(): + from IPython.core import crashhandler + +def test_import_debugger(): + from IPython.core import debugger + +def test_import_fakemodule(): + from IPython.core import fakemodule + +def test_import_excolors(): + from IPython.core import excolors + +def test_import_history(): + from IPython.core import history + +def test_import_hooks(): + from IPython.core import hooks + +def test_import_ipapi(): + from IPython.core import ipapi + +def test_import_iplib(): + from IPython.core import iplib + +def test_import_logger(): + from IPython.core import logger + +def test_import_macro(): + from IPython.core import macro + +def test_import_magic(): + from IPython.core import magic + +def test_import_oinspect(): + from IPython.core import oinspect + +def test_import_outputtrap(): + from IPython.core import outputtrap + +def test_import_prefilter(): + from IPython.core import prefilter + +def test_import_prompts(): + from IPython.core import prompts + +def test_import_release(): + from IPython.core import release + +def test_import_shadowns(): + from IPython.core import shadowns + +def test_import_ultratb(): + from IPython.core import ultratb + +def test_import_usage(): + from IPython.core import usage diff --git a/IPython/tests/test_iplib.py b/IPython/core/tests/test_iplib.py similarity index 73% rename from IPython/tests/test_iplib.py rename to IPython/core/tests/test_iplib.py index 723343c..8a972ae 100644 --- a/IPython/tests/test_iplib.py +++ b/IPython/core/tests/test_iplib.py @@ -13,7 +13,9 @@ import tempfile import nose.tools as nt # our own packages -from IPython import ipapi, iplib +from IPython.core import iplib +from IPython.core import ipapi + #----------------------------------------------------------------------------- # Globals @@ -36,8 +38,6 @@ if ip is None: # consistency when the test suite is being run via iptest from IPython.testing.plugin import ipdoctest ip = ipapi.get() - -IP = ip.IP # This is the actual IPython shell 'raw' object. #----------------------------------------------------------------------------- # Test functions @@ -45,36 +45,13 @@ IP = ip.IP # This is the actual IPython shell 'raw' object. def test_reset(): """reset must clear most namespaces.""" - IP.reset() # first, it should run without error + ip.reset() # first, it should run without error # Then, check that most namespaces end up empty - for ns in IP.ns_refs_table: - if ns is IP.user_ns: + for ns in ip.ns_refs_table: + if ns is ip.user_ns: # The user namespace is reset with some data, so we can't check for # it being empty continue nt.assert_equals(len(ns),0) - -# make sure that user_setup can be run re-entrantly in 'install' mode. -def test_user_setup(): - # use a lambda to pass kwargs to the generator - user_setup = lambda a,k: iplib.user_setup(*a,**k) - kw = dict(mode='install', interactive=False) - - # Call the user setup and verify that the directory exists - yield user_setup, (ip.options.ipythondir,''), kw - yield os.path.isdir, ip.options.ipythondir - - # Now repeat the operation with a non-existent directory. Check both that - # the call succeeds and that the directory is created. - tmpdir = tempfile.mktemp(prefix='ipython-test-') - # Use a try with an empty except because try/finally doesn't work with a - # yield in Python 2.4. - try: - yield user_setup, (tmpdir,''), kw - yield os.path.isdir, tmpdir - except: - pass - # Clean up the temp dir once done - shutil.rmtree(tmpdir) \ No newline at end of file diff --git a/IPython/tests/test_magic.py b/IPython/core/tests/test_magic.py similarity index 74% rename from IPython/tests/test_magic.py rename to IPython/core/tests/test_magic.py index 18211b3..9e39be4 100644 --- a/IPython/tests/test_magic.py +++ b/IPython/core/tests/test_magic.py @@ -7,10 +7,11 @@ import os import sys import tempfile import types +from cStringIO import StringIO import nose.tools as nt -from IPython.platutils import find_cmd, get_long_path_name +from IPython.utils.platutils import find_cmd, get_long_path_name from IPython.testing import decorators as dec from IPython.testing import tools as tt @@ -19,14 +20,15 @@ from IPython.testing import tools as tt def test_rehashx(): # clear up everything - _ip.IP.alias_table.clear() + _ip = get_ipython() + _ip.alias_manager.alias_table.clear() del _ip.db['syscmdlist'] _ip.magic('rehashx') # Practically ALL ipython development systems will have more than 10 aliases - yield (nt.assert_true, len(_ip.IP.alias_table) > 10) - for key, val in _ip.IP.alias_table.items(): + yield (nt.assert_true, len(_ip.alias_manager.alias_table) > 10) + for key, val in _ip.alias_manager.alias_table.items(): # we must strip dots from alias names nt.assert_true('.' not in key) @@ -43,7 +45,6 @@ def doctest_hist_f(): In [10]: tfile = tempfile.mktemp('.py','tmp-ipython-') In [11]: %hist -n -f $tfile 3 - """ @@ -52,7 +53,7 @@ def doctest_hist_r(): XXX - This test is not recording the output correctly. Not sure why... - In [20]: 'hist' in _ip.IP.lsmagic() + In [20]: 'hist' in _ip.lsmagic() Out[20]: True In [6]: x=1 @@ -65,11 +66,12 @@ def doctest_hist_r(): # This test is known to fail on win32. # See ticket https://bugs.launchpad.net/bugs/366334 def test_obj_del(): + _ip = get_ipython() """Test that object's __del__ methods are called on exit.""" test_dir = os.path.dirname(__file__) del_file = os.path.join(test_dir,'obj_del.py') ipython_cmd = find_cmd('ipython') - out = _ip.IP.getoutput('%s %s' % (ipython_cmd, del_file)) + out = _ip.getoutput('%s %s' % (ipython_cmd, del_file)) nt.assert_equals(out,'obj_del.py: object A deleted') @@ -77,8 +79,8 @@ def test_shist(): # Simple tests of ShadowHist class - test generator. import os, shutil, tempfile - from IPython.Extensions import pickleshare - from IPython.history import ShadowHist + from IPython.utils import pickleshare + from IPython.core.history import ShadowHist tfile = tempfile.mktemp('','tmp-ipython-') @@ -98,7 +100,7 @@ def test_shist(): @dec.skipif_not_numpy def test_numpy_clear_array_undec(): - from IPython.Extensions import clearcmd + from IPython.extensions import clearcmd _ip.ex('import numpy as np') _ip.ex('a = np.empty(2)') @@ -124,7 +126,7 @@ def doctest_refbug(): """Very nasty problem with references held by multiple runs of a script. See: https://bugs.launchpad.net/ipython/+bug/269966 - In [1]: _ip.IP.clear_main_mod_cache() + In [1]: _ip.clear_main_mod_cache() In [2]: run refbug @@ -164,7 +166,6 @@ def doctest_run_ns2(): tclass.py: deleting object: C-first_pass """ -@dec.skip_win32 def doctest_run_builtins(): """Check that %run doesn't damage __builtins__ via a doctest. @@ -177,24 +178,34 @@ def doctest_run_builtins(): In [2]: bid1 = id(__builtins__) - In [3]: f = tempfile.NamedTemporaryFile() + In [3]: fname = tempfile.mkstemp()[1] + + In [3]: f = open(fname,'w') In [4]: f.write('pass\\n') In [5]: f.flush() - In [6]: print 'B1:',type(__builtins__) - B1: + In [6]: print type(__builtins__) + - In [7]: %run $f.name + In [7]: %run "$fname" + + In [7]: f.close() In [8]: bid2 = id(__builtins__) - In [9]: print 'B2:',type(__builtins__) - B2: + In [9]: print type(__builtins__) + In [10]: bid1 == bid2 Out[10]: True + + In [12]: try: + ....: os.unlink(fname) + ....: except: + ....: pass + ....: """ # For some tests, it will be handy to organize them in a class with a common @@ -204,34 +215,28 @@ class TestMagicRun(object): def setup(self): """Make a valid python temp file.""" - f = tempfile.NamedTemporaryFile() + fname = tempfile.mkstemp()[1] + f = open(fname,'w') f.write('pass\n') f.flush() self.tmpfile = f + self.fname = fname def run_tmpfile(self): + _ip = get_ipython() # This fails on Windows if self.tmpfile.name has spaces or "~" in it. # See below and ticket https://bugs.launchpad.net/bugs/366353 - _ip.magic('run %s' % self.tmpfile.name) + _ip.magic('run "%s"' % self.fname) - # See https://bugs.launchpad.net/bugs/366353 - @dec.skip_if_not_win32 - def test_run_tempfile_path(self): - tt.assert_equals(True,False,"%run doesn't work with tempfile paths on win32.") - - # See https://bugs.launchpad.net/bugs/366353 - @dec.skip_win32 def test_builtins_id(self): """Check that %run doesn't damage __builtins__ """ - + _ip = get_ipython() # Test that the id of __builtins__ is not modified by %run bid1 = id(_ip.user_ns['__builtins__']) self.run_tmpfile() bid2 = id(_ip.user_ns['__builtins__']) tt.assert_equals(bid1, bid2) - # See https://bugs.launchpad.net/bugs/366353 - @dec.skip_win32 def test_builtins_type(self): """Check that the type of __builtins__ doesn't change with %run. @@ -239,33 +244,45 @@ class TestMagicRun(object): be a dict (it should be a module) by a previous use of %run. So we also check explicitly that it really is a module: """ + _ip = get_ipython() self.run_tmpfile() tt.assert_equals(type(_ip.user_ns['__builtins__']),type(sys)) - # See https://bugs.launchpad.net/bugs/366353 - @dec.skip_win32 def test_prompts(self): """Test that prompts correctly generate after %run""" self.run_tmpfile() - p2 = str(_ip.IP.outputcache.prompt2).strip() + _ip = get_ipython() + p2 = str(_ip.outputcache.prompt2).strip() nt.assert_equals(p2[:3], '...') def teardown(self): self.tmpfile.close() + try: + os.unlink(self.fname) + except: + # On Windows, even though we close the file, we still can't delete + # it. I have no clue why + pass # Multiple tests for clipboard pasting def test_paste(): - - def paste(txt): + _ip = get_ipython() + def paste(txt, flags='-q'): + """Paste input text, by default in quiet mode""" hooks.clipboard_get = lambda : txt - _ip.magic('paste') + _ip.magic('paste '+flags) # Inject fake clipboard hook but save original so we can restore it later - hooks = _ip.IP.hooks + hooks = _ip.hooks user_ns = _ip.user_ns original_clip = hooks.clipboard_get try: + # This try/except with an emtpy except clause is here only because + # try/yield/finally is invalid syntax in Python 2.4. This will be + # removed when we drop 2.4-compatibility, and the emtpy except below + # will be changed to a finally. + # Run tests with fake clipboard function user_ns.pop('x', None) paste('x=1') @@ -285,6 +302,30 @@ def test_paste(): yield (nt.assert_equal, user_ns['x'], [1,2,3]) yield (nt.assert_equal, user_ns['y'], [1,4,9]) + # Now, test that paste -r works + user_ns.pop('x', None) + yield (nt.assert_false, 'x' in user_ns) + _ip.magic('paste -r') + yield (nt.assert_equal, user_ns['x'], [1,2,3]) + + # Also test paste echoing, by temporarily faking the writer + w = StringIO() + writer = _ip.write + _ip.write = w.write + code = """ + a = 100 + b = 200""" + try: + paste(code,'') + out = w.getvalue() + finally: + _ip.write = writer + yield (nt.assert_equal, user_ns['a'], 100) + yield (nt.assert_equal, user_ns['b'], 200) + yield (nt.assert_equal, out, code+"\n## -- End pasted text --\n") + finally: + # This should be in a finally clause, instead of the bare except above. # Restore original hook hooks.clipboard_get = original_clip + diff --git a/IPython/kernel/core/ultraTB.py b/IPython/core/ultratb.py similarity index 91% rename from IPython/kernel/core/ultraTB.py rename to IPython/core/ultratb.py index 1dcbb2f..ddaf0b2 100644 --- a/IPython/kernel/core/ultraTB.py +++ b/IPython/core/ultratb.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- """ -ultraTB.py -- Spice up your tracebacks! +ultratb.py -- Spice up your tracebacks! * ColorTB I've always found it a bit hard to visually parse tracebacks in Python. The @@ -9,8 +9,8 @@ traceback in a manner similar to what you would expect from a syntax-highlightin text editor. Installation instructions for ColorTB: - import sys,ultraTB - sys.excepthook = ultraTB.ColorTB() + import sys,ultratb + sys.excepthook = ultratb.ColorTB() * VerboseTB I've also included a port of Ka-Ping Yee's "cgitb.py" that produces all kinds @@ -37,8 +37,8 @@ Note: Installation instructions for ColorTB: - import sys,ultraTB - sys.excepthook = ultraTB.VerboseTB() + import sys,ultratb + sys.excepthook = ultratb.VerboseTB() Note: Much of the code in this module was lifted verbatim from the standard library module 'traceback.py' and Ka-Ping Yee's 'cgitb.py'. @@ -69,7 +69,8 @@ possible inclusion in future releases. # the file COPYING, distributed as part of this software. #***************************************************************************** -# Required modules +from __future__ import with_statement + import inspect import keyword import linecache @@ -85,15 +86,17 @@ import types # For purposes of monkeypatching inspect to fix a bug in it. from inspect import getsourcefile, getfile, getmodule,\ - ismodule, isclass, ismethod, isfunction, istraceback, isframe, iscode + ismodule, isclass, ismethod, isfunction, istraceback, isframe, iscode # IPython's own modules # Modified pdb which doesn't damage IPython's readline handling -from IPython import Debugger, PyColorize, ipapi -from IPython.ipstruct import Struct -from IPython.excolors import exception_colors -from IPython.genutils import Term,uniq_stable,error,info +from IPython.utils import PyColorize +from IPython.core import debugger, ipapi +from IPython.core.display_trap import DisplayTrap +from IPython.utils.ipstruct import Struct +from IPython.core.excolors import exception_colors +from IPython.utils.genutils import Term, uniq_stable, error, info # Globals # amount of space to put line numbers before verbose tracebacks @@ -102,7 +105,7 @@ INDENT_SIZE = 8 # Default color scheme. This is used, for example, by the traceback # formatter. When running in an actual IPython instance, the user's rc.colors # value is used, but havinga module global makes this functionality available -# to users of ultraTB who are NOT running inside ipython. +# to users of ultratb who are NOT running inside ipython. DEFAULT_SCHEME = 'NoColor' #--------------------------------------------------------------------------- @@ -267,10 +270,12 @@ def _formatTracebackLines(lnum, index, lines, Colors, lvals=None,scheme=None): # This lets us get fully syntax-highlighted tracebacks. if scheme is None: - try: - scheme = ipapi.get().IP.rc.colors - except: + ipinst = ipapi.get() + if ipinst is not None: + scheme = ipinst.colors + else: scheme = DEFAULT_SCHEME + _line_format = _parser.format2 for line in lines: @@ -320,7 +325,7 @@ class TBTools: self.old_scheme = color_scheme # save initial value for toggles if call_pdb: - self.pdb = Debugger.Pdb(self.color_scheme_table.active_scheme_name) + self.pdb = debugger.Pdb(self.color_scheme_table.active_scheme_name) else: self.pdb = None @@ -489,7 +494,9 @@ class ListTB(TBTools): # vds:>> if have_filedata: - ipapi.get().IP.hooks.synchronize_with_editor(filename, lineno, 0) + ipinst = ipapi.get() + if ipinst is not None: + ipinst.hooks.synchronize_with_editor(filename, lineno, 0) # vds:<< return list @@ -809,7 +816,9 @@ class VerboseTB(TBTools): filepath, lnum = records[-1][1:3] #print "file:", str(file), "linenb", str(lnum) # dbg filepath = os.path.abspath(filepath) - ipapi.get().IP.hooks.synchronize_with_editor(filepath, lnum, 0) + ipinst = ipapi.get() + if ipinst is not None: + ipinst.hooks.synchronize_with_editor(filepath, lnum, 0) # vds: << # return all our info assembled as a single string @@ -837,28 +846,25 @@ class VerboseTB(TBTools): if force or self.call_pdb: if self.pdb is None: - self.pdb = Debugger.Pdb( + self.pdb = debugger.Pdb( self.color_scheme_table.active_scheme_name) # the system displayhook may have changed, restore the original # for pdb - dhook = sys.displayhook - sys.displayhook = sys.__displayhook__ - self.pdb.reset() - # Find the right frame so we don't pop up inside ipython itself - if hasattr(self,'tb'): - etb = self.tb - else: - etb = self.tb = sys.last_traceback - while self.tb.tb_next is not None: - self.tb = self.tb.tb_next - try: + display_trap = DisplayTrap(None, sys.__displayhook__) + with display_trap: + self.pdb.reset() + # Find the right frame so we don't pop up inside ipython itself + if hasattr(self,'tb'): + etb = self.tb + else: + etb = self.tb = sys.last_traceback + while self.tb.tb_next is not None: + self.tb = self.tb.tb_next if etb and etb.tb_next: etb = etb.tb_next self.pdb.botframe = etb.tb_frame self.pdb.interaction(self.tb.tb_frame, self.tb) - finally: - sys.displayhook = dhook - + if hasattr(self,'tb'): del self.tb diff --git a/IPython/usage.py b/IPython/core/usage.py similarity index 66% rename from IPython/usage.py rename to IPython/core/usage.py index 7620d96..1fdd8c9 100644 --- a/IPython/usage.py +++ b/IPython/core/usage.py @@ -6,6 +6,9 @@ # the file COPYING, distributed as part of this software. #***************************************************************************** +import sys +from IPython.core import release + __doc__ = """ IPython -- An enhanced Interactive Python ========================================= @@ -37,84 +40,7 @@ USAGE in directories. In the rest of this text, we will refer to this directory as - IPYTHONDIR. - - -SPECIAL THREADING OPTIONS - The following special options are ONLY valid at the beginning of the - command line, and not later. This is because they control the initial- - ization of ipython itself, before the normal option-handling mechanism - is active. - - -gthread, -qthread, -q4thread, -wthread, -pylab - - Only ONE of these can be given, and it can only be given as the - first option passed to IPython (it will have no effect in any - other position). They provide threading support for the GTK, QT - and WXWidgets toolkits, and for the matplotlib library. - - With any of the first four options, IPython starts running a - separate thread for the graphical toolkit's operation, so that - you can open and control graphical elements from within an - IPython command line, without blocking. All four provide - essentially the same functionality, respectively for GTK, QT3, - QT4 and WXWidgets (via their Python interfaces). - - Note that with -wthread, you can additionally use the -wxversion - option to request a specific version of wx to be used. This - requires that you have the 'wxversion' Python module installed, - which is part of recent wxPython distributions. - - If -pylab is given, IPython loads special support for the mat- - plotlib library (http://matplotlib.sourceforge.net), allowing - interactive usage of any of its backends as defined in the - user's .matplotlibrc file. It automatically activates GTK, QT - or WX threading for IPyhton if the choice of matplotlib backend - requires it. It also modifies the %run command to correctly - execute (without blocking) any matplotlib-based script which - calls show() at the end. - - -tk The -g/q/q4/wthread options, and -pylab (if matplotlib is - configured to use GTK, QT or WX), will normally block Tk - graphical interfaces. This means that when GTK, QT or WX - threading is active, any attempt to open a Tk GUI will result in - a dead window, and possibly cause the Python interpreter to - crash. An extra option, -tk, is available to address this - issue. It can ONLY be given as a SECOND option after any of the - above (-gthread, -qthread, q4thread, -wthread or -pylab). - - If -tk is given, IPython will try to coordinate Tk threading - with GTK, QT or WX. This is however potentially unreliable, and - you will have to test on your platform and Python configuration - to determine whether it works for you. Debian users have - reported success, apparently due to the fact that Debian builds - all of Tcl, Tk, Tkinter and Python with pthreads support. Under - other Linux environments (such as Fedora Core 2/3), this option - has caused random crashes and lockups of the Python interpreter. - Under other operating systems (Mac OSX and Windows), you'll need - to try it to find out, since currently no user reports are - available. - - There is unfortunately no way for IPython to determine at run- - time whether -tk will work reliably or not, so you will need to - do some experiments before relying on it for regular work. - -A WARNING ABOUT SIGNALS AND THREADS - - When any of the thread systems (GTK, QT or WX) are active, either - directly or via -pylab with a threaded backend, it is impossible to - interrupt long-running Python code via Ctrl-C. IPython can not pass - the KeyboardInterrupt exception (or the underlying SIGINT) across - threads, so any long-running process started from IPython will run to - completion, or will have to be killed via an external (OS-based) - mechanism. - - To the best of my knowledge, this limitation is imposed by the Python - interpreter itself, and it comes from the difficulty of writing - portable signal/threaded code. If any user is an expert on this topic - and can suggest a better solution, I would love to hear about it. In - the IPython sources, look at the Shell.py module, and in particular at - the runcode() method. + IPYTHON_DIR. REGULAR OPTIONS After the above threading options have been given, regular options can @@ -132,16 +58,6 @@ REGULAR OPTIONS -h, --help Show summary of options. - -pylab This can only be given as the first option passed to IPython (it - will have no effect in any other position). It adds special sup- - port for the matplotlib library (http://matplotlib.source- - forge.net), allowing interactive usage of any of its backends as - defined in the user's .matplotlibrc file. It automatically - activates GTK or WX threading for IPyhton if the choice of mat- - plotlib backend requires it. It also modifies the @run command - to correctly execute (without blocking) any matplotlib-based - script which calls show() at the end. - -autocall Make IPython automatically call any callable object even if you didn't type explicit parentheses. For example, 'str 43' becomes @@ -234,9 +150,9 @@ REGULAR OPTIONS here (in case your default EDITOR is something like Emacs). -ipythondir - The name of your IPython configuration directory IPYTHONDIR. + The name of your IPython configuration directory IPYTHON_DIR. This can also be specified through the environment variable - IPYTHONDIR. + IPYTHON_DIR. -log|l Generate a log file of all input. The file is named ipython_log.py in your current directory (which prevents logs @@ -285,10 +201,10 @@ REGULAR OPTIONS -profile|p Assume that your config file is ipythonrc- (looks in cur- - rent dir first, then in IPYTHONDIR). This is a quick way to keep + rent dir first, then in IPYTHON_DIR). This is a quick way to keep and load multiple config files for different tasks, especially if you use the include option of config files. You can keep a - basic IPYTHONDIR/ipythonrc file and then have other 'profiles' + basic IPYTHON_DIR/ipythonrc file and then have other 'profiles' which include this one and load extra things for particular tasks. For example: @@ -329,7 +245,7 @@ REGULAR OPTIONS -rcfile Name of your IPython resource configuration file. normally IPython loads ipythonrc (from current directory) or - IPYTHONDIR/ipythonrc. If the loading of your config file fails, + IPYTHON_DIR/ipythonrc. If the loading of your config file fails, IPython starts with a bare bones configuration (no modules loaded at all). @@ -368,7 +284,7 @@ REGULAR OPTIONS Simply removes all input/output separators. -upgrade - Allows you to upgrade your IPYTHONDIR configuration when you + Allows you to upgrade your IPYTHON_DIR configuration when you install a new version of IPython. Since new versions may include new command lines options or example files, this copies updated ipythonrc-type files. However, it backs up (with a .old @@ -591,6 +507,18 @@ MAIN FEATURES >>> x = ,my_function /home/me # syntax error """ +interactive_usage_min = """\ +An enhanced console for Python. +Some of its features are: +- Readline support if the readline library is present. +- Tab completion in the local namespace. +- Logging of input, see command-line options. +- System shell escape via ! , eg !ls. +- Magic commands, starting with a % (like %ls, %pwd, %cd, etc.) +- Keeps track of locally defined variables via %who, %whos. +- Show object information with a ? eg ?x or x? (use ?? for more info). +""" + quick_reference = r""" IPython -- An enhanced Interactive Python - Quick Reference Card ================================================================ @@ -643,3 +571,18 @@ or python names. The following magic functions are currently available: """ + +quick_guide = """\ +? -> Introduction and overview of IPython's features. +%quickref -> Quick reference. +help -> Python's own help system. +object? -> Details about 'object'. ?object also works, ?? prints more.""" + +default_banner_parts = [ + 'Python %s' % (sys.version.split('\n')[0],), + 'Type "copyright", "credits" or "license" for more information.\n', + 'IPython %s -- An enhanced Interactive Python.' % (release.version,), + quick_guide +] + +default_banner = '\n'.join(default_banner_parts) diff --git a/IPython/Gnuplot2.py b/IPython/deathrow/Gnuplot2.py similarity index 99% rename from IPython/Gnuplot2.py rename to IPython/deathrow/Gnuplot2.py index cc2eb17..e085ccc 100644 --- a/IPython/Gnuplot2.py +++ b/IPython/deathrow/Gnuplot2.py @@ -25,7 +25,7 @@ import types import Gnuplot as Gnuplot_ori import Numeric -from IPython.genutils import popkey,xsys +from IPython.utils.genutils import popkey,xsys # needed by hardcopy(): gp = Gnuplot_ori.gp diff --git a/IPython/GnuplotInteractive.py b/IPython/deathrow/GnuplotInteractive.py similarity index 97% rename from IPython/GnuplotInteractive.py rename to IPython/deathrow/GnuplotInteractive.py index 074d2f5..ab837d7 100644 --- a/IPython/GnuplotInteractive.py +++ b/IPython/deathrow/GnuplotInteractive.py @@ -16,7 +16,8 @@ __all__ = ['Gnuplot','gp','gp_new','plot','plot2','splot','replot', 'gphelp'] import IPython.GnuplotRuntime as GRun -from IPython.genutils import page,warn +from IPython.utils.genutils import warn +from IPython.core.page import page # Set global names for interactive use Gnuplot = GRun.Gnuplot @@ -140,7 +141,7 @@ else: print """*** Type `gphelp` for help on the Gnuplot integration features.""" # Add the new magic functions to the class dict - from IPython.iplib import InteractiveShell + from IPython.core.iplib import InteractiveShell InteractiveShell.magic_gpc = magic_gpc InteractiveShell.magic_gp_set_default = magic_gp_set_default diff --git a/IPython/GnuplotRuntime.py b/IPython/deathrow/GnuplotRuntime.py similarity index 99% rename from IPython/GnuplotRuntime.py rename to IPython/deathrow/GnuplotRuntime.py index 8bd5185..e03cfe3 100644 --- a/IPython/GnuplotRuntime.py +++ b/IPython/deathrow/GnuplotRuntime.py @@ -53,7 +53,7 @@ __all__ = ['Gnuplot','gp','gp_new','Data','File','Func','GridData', 'pm3d_config','eps_fix_bbox'] import os,tempfile,sys -from IPython.genutils import getoutput +from IPython.utils.genutils import getoutput #--------------------------------------------------------------------------- # Notes on mouse support for Gnuplot.py diff --git a/IPython/Itpl.py b/IPython/deathrow/Itpl.py similarity index 100% rename from IPython/Itpl.py rename to IPython/deathrow/Itpl.py diff --git a/IPython/Extensions/PhysicalQInput.py b/IPython/deathrow/PhysicalQInput.py similarity index 96% rename from IPython/Extensions/PhysicalQInput.py rename to IPython/deathrow/PhysicalQInput.py index 04f05c0..fd53e8a 100644 --- a/IPython/Extensions/PhysicalQInput.py +++ b/IPython/deathrow/PhysicalQInput.py @@ -52,7 +52,7 @@ def prefilter_PQ(self,line,continuation): imported.""" from re import match - from IPython.iplib import InteractiveShell + from IPython.core.iplib import InteractiveShell # This regexp is what does the real work unit_split = match(r'\s*(\w+)\s*=\s*(-?\d*\.?\d*[eE]?-?\d*)\s+([a-zA-Z].*)', @@ -74,7 +74,7 @@ def prefilter_PQ(self,line,continuation): return InteractiveShell._prefilter(self,line,continuation) # Rebind this to be the new IPython prefilter: -from IPython.iplib import InteractiveShell +from IPython.core.iplib import InteractiveShell InteractiveShell.prefilter = prefilter_PQ # Clean up the namespace. diff --git a/IPython/Extensions/PhysicalQInteractive.py b/IPython/deathrow/PhysicalQInteractive.py similarity index 100% rename from IPython/Extensions/PhysicalQInteractive.py rename to IPython/deathrow/PhysicalQInteractive.py diff --git a/IPython/deathrow/__init__.py b/IPython/deathrow/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/IPython/deathrow/__init__.py diff --git a/IPython/Extensions/astyle.py b/IPython/deathrow/astyle.py similarity index 100% rename from IPython/Extensions/astyle.py rename to IPython/deathrow/astyle.py diff --git a/IPython/dtutils.py b/IPython/deathrow/dtutils.py similarity index 96% rename from IPython/dtutils.py rename to IPython/deathrow/dtutils.py index 37e7aa2..41e377f 100644 --- a/IPython/dtutils.py +++ b/IPython/deathrow/dtutils.py @@ -10,8 +10,8 @@ See the idoctest docstring below for usage details. import doctest import sys -import IPython.ipapi -ip = IPython.ipapi.get() +from IPython.core import ipapi +ip = ipapi.get() def rundoctest(text,ns=None,eraise=False): """Run a the input source as a doctest, in the caller's namespace. @@ -77,7 +77,7 @@ def idoctest(ns=None,eraise=False): if ns is None: ns = ip.user_ns - ip.IP.savehist() + ip.savehist() try: while True: line = raw_input() @@ -96,7 +96,7 @@ def idoctest(ns=None,eraise=False): print "KeyboardInterrupt - Discarding input." run_test = False - ip.IP.reloadhist() + ip.reloadhist() if run_test: # Extra blank line at the end to ensure that the final docstring has a diff --git a/IPython/Extensions/ibrowse.py b/IPython/deathrow/ibrowse.py similarity index 100% rename from IPython/Extensions/ibrowse.py rename to IPython/deathrow/ibrowse.py index 68a07c1..809b4c9 100644 --- a/IPython/Extensions/ibrowse.py +++ b/IPython/deathrow/ibrowse.py @@ -2,7 +2,7 @@ import curses, fcntl, signal, struct, tty, textwrap, inspect -from IPython import ipapi +from IPython.core import ipapi import astyle, ipipe diff --git a/IPython/Extensions/igrid.py b/IPython/deathrow/igrid.py similarity index 99% rename from IPython/Extensions/igrid.py rename to IPython/deathrow/igrid.py index a2d317f..b49793c 100644 --- a/IPython/Extensions/igrid.py +++ b/IPython/deathrow/igrid.py @@ -1,7 +1,7 @@ # -*- coding: iso-8859-1 -*- import ipipe, os, webbrowser, urllib -from IPython import ipapi +from IPython.core import ipapi import wx import wx.grid, wx.html diff --git a/IPython/Extensions/igrid_help.css b/IPython/deathrow/igrid_help.css similarity index 100% rename from IPython/Extensions/igrid_help.css rename to IPython/deathrow/igrid_help.css diff --git a/IPython/Extensions/igrid_help.html b/IPython/deathrow/igrid_help.html similarity index 100% rename from IPython/Extensions/igrid_help.html rename to IPython/deathrow/igrid_help.html diff --git a/IPython/Extensions/ipipe.py b/IPython/deathrow/ipipe.py similarity index 96% rename from IPython/Extensions/ipipe.py rename to IPython/deathrow/ipipe.py index 7f9029a..74215bc 100644 --- a/IPython/Extensions/ipipe.py +++ b/IPython/deathrow/ipipe.py @@ -133,12 +133,13 @@ from IPython.external import simplegeneric from IPython.external import path try: - from IPython import genutils, generics + from IPython.utils import genutils + from IPython.utils import generics except ImportError: genutils = None generics = None -from IPython import ipapi +from IPython.core import ipapi __all__ = [ @@ -1216,11 +1217,11 @@ class ils(Table): Examples:: >>> ils - + >>> ils("/usr/local/lib/python2.4") - IPython.Extensions.ipipe.ils('/usr/local/lib/python2.4') + IPython.extensions.ipipe.ils('/usr/local/lib/python2.4') >>> ils("~") - IPython.Extensions.ipipe.ils('/home/fperez') + IPython.extensions.ipipe.ils('/home/fperez') # all-random """ def __init__(self, base=os.curdir, dirs=True, files=True): @@ -1258,7 +1259,7 @@ class iglob(Table): Examples:: >>> iglob("*.py") - IPython.Extensions.ipipe.iglob('*.py') + IPython.extensions.ipipe.iglob('*.py') """ def __init__(self, glob): self.glob = glob @@ -1284,11 +1285,11 @@ class iwalk(Table): List all files and directories in a directory and it's subdirectory:: >>> iwalk - + >>> iwalk("/usr/lib") - IPython.Extensions.ipipe.iwalk('/usr/lib') + IPython.extensions.ipipe.iwalk('/usr/lib') >>> iwalk("~") - IPython.Extensions.ipipe.iwalk('/home/fperez') # random + IPython.extensions.ipipe.iwalk('/home/fperez') # random """ def __init__(self, base=os.curdir, dirs=True, files=True): @@ -1393,7 +1394,7 @@ class ipwd(Table): Example:: >>> ipwd | isort("uid") - + # random """ def __iter__(self): @@ -1579,7 +1580,7 @@ class ienv(Table): Example:: >>> ienv - + """ def __iter__(self): @@ -1601,9 +1602,9 @@ class ihist(Table): Example:: >>> ihist - + >>> ihist(True) # raw mode - # random + # random """ def __init__(self, raw=True): self.raw = raw @@ -1611,10 +1612,10 @@ class ihist(Table): def __iter__(self): api = ipapi.get() if self.raw: - for line in api.IP.input_hist_raw: + for line in api.input_hist_raw: yield line.rstrip("\n") else: - for line in api.IP.input_hist: + for line in api.input_hist: yield line.rstrip("\n") @@ -1638,12 +1639,12 @@ class ialias(Table): Example:: >>> ialias - + """ def __iter__(self): api = ipapi.get() - for (name, (args, command)) in api.IP.alias_table.iteritems(): + for (name, (args, command)) in api.alias_manager.alias_table.iteritems(): yield Alias(name, args, command) @@ -1701,10 +1702,10 @@ class ix(Table): Examples:: >>> ix("ps x") - IPython.Extensions.ipipe.ix('ps x') + IPython.extensions.ipipe.ix('ps x') >>> ix("find .") | ifile - at 0x8509d2c> + at 0x8509d2c> # random """ def __init__(self, cmd): @@ -1927,9 +1928,9 @@ class isort(Pipe): Examples:: >>> ils | isort("size") - + >>> ils | isort("_.isdir(), _.lower()", reverse=True) - + # all-random """ diff --git a/IPython/Extensions/ipy_constants.py b/IPython/deathrow/ipy_constants.py similarity index 99% rename from IPython/Extensions/ipy_constants.py rename to IPython/deathrow/ipy_constants.py index 59883cd..9e8ea5d 100644 --- a/IPython/Extensions/ipy_constants.py +++ b/IPython/deathrow/ipy_constants.py @@ -15,7 +15,7 @@ Website: physics.nist.gov/constants # inspired by maxima's physconst.mac by Cliff Yapp #from math import * # math MUST be imported BEFORE PhysicalQInteractive -from IPython.Extensions.PhysicalQInteractive import PhysicalQuantityInteractive +from IPython.extensions.PhysicalQInteractive import PhysicalQuantityInteractive # Math constants: diff --git a/IPython/Extensions/ipy_defaults.py b/IPython/deathrow/ipy_defaults.py similarity index 93% rename from IPython/Extensions/ipy_defaults.py rename to IPython/deathrow/ipy_defaults.py index 89ea3e2..665a124 100644 --- a/IPython/Extensions/ipy_defaults.py +++ b/IPython/deathrow/ipy_defaults.py @@ -12,9 +12,9 @@ ipy_profile_PROFILENAME etc. """ -import IPython.rlineimpl as readline -import IPython.ipapi -ip = IPython.ipapi.get() +import IPython.utils.rlineimpl as readline +from IPython.core import ipapi +ip = ipapi.get() o = ip.options diff --git a/IPython/Extensions/ipy_kitcfg.py b/IPython/deathrow/ipy_kitcfg.py similarity index 92% rename from IPython/Extensions/ipy_kitcfg.py rename to IPython/deathrow/ipy_kitcfg.py index d77f672..57409c8 100644 --- a/IPython/Extensions/ipy_kitcfg.py +++ b/IPython/deathrow/ipy_kitcfg.py @@ -16,7 +16,7 @@ def pylaunchers(): for f in fs: l = PyLauncher(f) n = os.path.splitext(f)[0] - ip.defalias(n, l) + ip.define_alias(n, l) ip.magic('store '+n) @@ -39,7 +39,7 @@ def main(): return os.environ["PATH"] = os.environ["PATH"] + ";" + kitroot() + "\\bin;" - ip.to_user_ns("pylaunchers") + ip.push("pylaunchers") cmds = ip.db.get('syscmdlist', None) if cmds is None: ip.magic('rehashx') @@ -63,8 +63,8 @@ def ipython_firstrun(ip): print "First run of ipykit - configuring" - ip.defalias('py',selflaunch) - ip.defalias('d','dir /w /og /on') + ip.define_alias('py',selflaunch) + ip.define_alias('d','dir /w /og /on') ip.magic('store py') ip.magic('store d') diff --git a/IPython/Extensions/ipy_legacy.py b/IPython/deathrow/ipy_legacy.py similarity index 86% rename from IPython/Extensions/ipy_legacy.py rename to IPython/deathrow/ipy_legacy.py index 2807dfd..aa7a148 100644 --- a/IPython/Extensions/ipy_legacy.py +++ b/IPython/deathrow/ipy_legacy.py @@ -9,12 +9,12 @@ Stuff that is considered obsolete / redundant is gradually moved here. """ -import IPython.ipapi -ip = IPython.ipapi.get() +from IPython.core import ipapi +ip = ipapi.get() import os,sys -from IPython.genutils import * +from IPython.utils.genutils import * # use rehashx @@ -43,7 +43,7 @@ def magic_rehash(self, parameter_s = ''): # aliases since %rehash will probably clobber them self.shell.init_auto_alias() -ip.expose_magic("rehash", magic_rehash) +ip.define_magic("rehash", magic_rehash) # Exit def magic_Quit(self, parameter_s=''): @@ -51,7 +51,7 @@ def magic_Quit(self, parameter_s=''): self.shell.ask_exit() -ip.expose_magic("Quit", magic_Quit) +ip.define_magic("Quit", magic_Quit) # make it autocallable fn if you really need it @@ -59,4 +59,4 @@ def magic_p(self, parameter_s=''): """Just a short alias for Python's 'print'.""" exec 'print ' + parameter_s in self.shell.user_ns -ip.expose_magic("p", magic_p) +ip.define_magic("p", magic_p) diff --git a/IPython/Extensions/ipy_p4.py b/IPython/deathrow/ipy_p4.py similarity index 88% rename from IPython/Extensions/ipy_p4.py rename to IPython/deathrow/ipy_p4.py index 448ce2a..0bda2fe 100644 --- a/IPython/Extensions/ipy_p4.py +++ b/IPython/deathrow/ipy_p4.py @@ -3,8 +3,8 @@ Add %p4 magic for pythonic p4 (Perforce) usage. """ -import IPython.ipapi -ip = IPython.ipapi.get() +from IPython.core import ipapi +ip = ipapi.get() import os,sys,marshal @@ -25,9 +25,9 @@ def p4_f(self, parameter_s=''): def p4d(fname): return os.popen('p4 where ' + fname).read().split()[0] -ip.to_user_ns("p4d") +ip.push("p4d") -ip.expose_magic('p4', p4_f) +ip.define_magic('p4', p4_f) p4_commands = """\ add admin annotate branch branches change changes changelist diff --git a/IPython/Extensions/ipy_profile_none.py b/IPython/deathrow/ipy_profile_none.py similarity index 100% rename from IPython/Extensions/ipy_profile_none.py rename to IPython/deathrow/ipy_profile_none.py diff --git a/IPython/Extensions/ipy_profile_numpy.py b/IPython/deathrow/ipy_profile_numpy.py similarity index 87% rename from IPython/Extensions/ipy_profile_numpy.py rename to IPython/deathrow/ipy_profile_numpy.py index a722627..6659884 100644 --- a/IPython/Extensions/ipy_profile_numpy.py +++ b/IPython/deathrow/ipy_profile_numpy.py @@ -5,11 +5,11 @@ This profile loads the math/cmath modules as well as all of numpy. It exposes numpy via the 'np' shorthand as well for convenience. """ -import IPython.ipapi +from IPython.core import ipapi import ipy_defaults def main(): - ip = IPython.ipapi.get() + ip = ipapi.get() try: ip.ex("import math,cmath") diff --git a/IPython/Extensions/ipy_profile_scipy.py b/IPython/deathrow/ipy_profile_scipy.py similarity index 90% rename from IPython/Extensions/ipy_profile_scipy.py rename to IPython/deathrow/ipy_profile_scipy.py index 907448a..25defe4 100644 --- a/IPython/Extensions/ipy_profile_scipy.py +++ b/IPython/deathrow/ipy_profile_scipy.py @@ -6,11 +6,11 @@ It exposes numpy and scipy via the 'np' and 'sp' shorthands as well for convenience. """ -import IPython.ipapi +from IPython.core import ipapi import ipy_defaults def main(): - ip = IPython.ipapi.get() + ip = ipapi.get() try: ip.ex("import math,cmath") diff --git a/IPython/Extensions/ipy_profile_sh.py b/IPython/deathrow/ipy_profile_sh.py similarity index 84% rename from IPython/Extensions/ipy_profile_sh.py rename to IPython/deathrow/ipy_profile_sh.py index f320a3d..571f5c4 100644 --- a/IPython/Extensions/ipy_profile_sh.py +++ b/IPython/deathrow/ipy_profile_sh.py @@ -7,7 +7,8 @@ shell mode and is recommended for users who don't care about pysh-mode compatibility) """ -from IPython import ipapi +from IPython.core import ipapi +from IPython.core.error import TryNext import os,re,textwrap # The import below effectively obsoletes your old-style ipythonrc[.ini], @@ -62,17 +63,17 @@ def main(): o.prompt_in2= r'\C_Green|\C_LightGreen\D\C_Green> ' o.prompt_out= '<\#> ' - from IPython import Release + from IPython.core import release import sys # Non-chatty banner - o.banner = "IPython %s [on Py %s]\n" % (Release.version,sys.version.split(None,1)[0]) + o.banner = "IPython %s [on Py %s]\n" % (release.version,sys.version.split(None,1)[0]) - ip.IP.default_option('cd','-q') - ip.IP.default_option('macro', '-r') + ip.default_option('cd','-q') + ip.default_option('macro', '-r') # If you only rarely want to execute the things you %edit... - #ip.IP.default_option('edit','-x') + #ip.default_option('edit','-x') o.prompts_pad_left="1" @@ -108,11 +109,11 @@ def main(): cmd = noext key = mapper(cmd) - if key not in ip.IP.alias_table: + if key not in ip.alias_manager.alias_table: # Dots will be removed from alias names, since ipython # assumes names with dots to be python code - ip.defalias(key.replace('.',''), cmd) + ip.define_alias(key.replace('.',''), cmd) # mglob combines 'find', recursion, exclusion... '%mglob?' to learn more ip.load("IPython.external.mglob") @@ -121,13 +122,13 @@ def main(): if sys.platform == 'win32': if 'cygwin' in os.environ['PATH'].lower(): # use the colors of cygwin ls (recommended) - ip.defalias('d', 'ls -F --color=auto') + ip.define_alias('d', 'ls -F --color=auto') else: # get icp, imv, imkdir, igrep, irm,... ip.load('ipy_fsops') # and the next best thing to real 'ls -F' - ip.defalias('d','dir /w /og /on') + ip.define_alias('d','dir /w /og /on') ip.set_hook('input_prefilter', slash_prefilter_f) extend_shell_behavior(ip) @@ -141,10 +142,10 @@ class LastArgFinder: ip = ipapi.get() if hist_idx is None: return str(self) - return ip.IP.input_hist_raw[hist_idx].strip().split()[-1] + return ip.input_hist_raw[hist_idx].strip().split()[-1] def __str__(self): ip = ipapi.get() - for cmd in reversed(ip.IP.input_hist_raw): + for cmd in reversed(ip.input_hist_raw): parts = cmd.strip().split() if len(parts) < 2 or parts[-1] in ['$LA', 'LA()']: continue @@ -156,10 +157,10 @@ def slash_prefilter_f(self,line): Removes the need for doing !./foo, !~/foo or !/bin/foo """ - import IPython.genutils + from IPython.utils import genutils if re.match('(?:[.~]|/[a-zA-Z_0-9]+)/', line): - return "_ip.system(" + IPython.genutils.make_quoted_expr(line)+")" - raise ipapi.TryNext + return "get_ipython().system(" + genutils.make_quoted_expr(line)+")" + raise TryNext # XXX You do not need to understand the next function! # This should probably be moved out of profile @@ -169,16 +170,16 @@ def extend_shell_behavior(ip): # Instead of making signature a global variable tie it to IPSHELL. # In future if it is required to distinguish between different # shells we can assign a signature per shell basis - ip.IP.__sig__ = 0xa005 + ip.__sig__ = 0xa005 # mark the IPSHELL with this signature - ip.IP.user_ns['__builtins__'].__dict__['__sig__'] = ip.IP.__sig__ + ip.user_ns['__builtins__'].__dict__['__sig__'] = ip.__sig__ - from IPython.Itpl import ItplNS - from IPython.genutils import shell + from IPython.external.Itpl import ItplNS + from IPython.utils.genutils import shell # utility to expand user variables via Itpl # xxx do something sensible with depth? - ip.IP.var_expand = lambda cmd, lvars=None, depth=2: \ - str(ItplNS(cmd, ip.IP.user_ns, get_locals())) + ip.var_expand = lambda cmd, lvars=None, depth=2: \ + str(ItplNS(cmd, ip.user_ns, get_locals())) def get_locals(): """ Substituting a variable through Itpl deep inside the IPSHELL stack @@ -194,7 +195,7 @@ def extend_shell_behavior(ip): getlvars = lambda fno: sys._getframe(fno+1).f_locals # trackback until we enter the IPSHELL frame_no = 1 - sig = ip.IP.__sig__ + sig = ip.__sig__ fsig = ~sig while fsig != sig : try: @@ -230,7 +231,7 @@ def extend_shell_behavior(ip): # We must start with a clean buffer, in case this is run from an # interactive IPython session (via a magic, for example). - ip.IP.resetbuffer() + ip.resetbuffer() lines = lines.split('\n') more = 0 command = '' @@ -251,9 +252,9 @@ def extend_shell_behavior(ip): command += line if command or more: # push to raw history, so hist line numbers stay in sync - ip.IP.input_hist_raw.append("# " + command + "\n") + ip.input_hist_raw.append("# " + command + "\n") - more = ip.IP.push(ip.IP.prefilter(command,more)) + more = ip.push_line(ip.prefilter(command,more)) command = '' # IPython's runsource returns None if there was an error # compiling the code. This allows us to stop processing right @@ -263,8 +264,8 @@ def extend_shell_behavior(ip): # final newline in case the input didn't have it, so that the code # actually does get executed if more: - ip.IP.push('\n') + ip.push_line('\n') - ip.IP.runlines = _runlines + ip.runlines = _runlines main() diff --git a/IPython/Extensions/ipy_profile_zope.py b/IPython/deathrow/ipy_profile_zope.py similarity index 95% rename from IPython/Extensions/ipy_profile_zope.py rename to IPython/deathrow/ipy_profile_zope.py index 172853c..5049de0 100644 --- a/IPython/Extensions/ipy_profile_zope.py +++ b/IPython/deathrow/ipy_profile_zope.py @@ -17,8 +17,8 @@ Authors # Distributed under the terms of the BSD License. The full license is in # the file COPYING, distributed as part of this software. -from IPython import ipapi -from IPython import Release +from IPython.core import ipapi +from IPython.core import release from types import StringType import sys import os @@ -278,7 +278,8 @@ class ZopeDebug(object): zope_debug = None def ipy_set_trace(): - import IPython; IPython.Debugger.Pdb().set_trace() + from IPython.core import debugger + debugger.Pdb().set_trace() def main(): global zope_debug @@ -298,7 +299,7 @@ def main(): # # I like my banner minimal. - o.banner = "ZOPE Py %s IPy %s\n" % (sys.version.split('\n')[0],Release.version) + o.banner = "ZOPE Py %s IPy %s\n" % (sys.version.split('\n')[0],release.version) print textwrap.dedent("""\ ZOPE mode iPython shell. diff --git a/IPython/Extensions/ipy_traits_completer.py b/IPython/deathrow/ipy_traits_completer.py similarity index 94% rename from IPython/Extensions/ipy_traits_completer.py rename to IPython/deathrow/ipy_traits_completer.py index 6d3e6f7..2dfe620 100644 --- a/IPython/Extensions/ipy_traits_completer.py +++ b/IPython/deathrow/ipy_traits_completer.py @@ -52,8 +52,9 @@ Notes from enthought.traits import api as T # IPython imports -from IPython.ipapi import TryNext, get as ipget -from IPython.genutils import dir2 +from IPython.core.error import TryNext +from IPython.core.ipapi import get as ipget +from IPython.utils.genutils import dir2 try: set except: diff --git a/IPython/Extensions/ipy_vimserver.py b/IPython/deathrow/ipy_vimserver.py similarity index 97% rename from IPython/Extensions/ipy_vimserver.py rename to IPython/deathrow/ipy_vimserver.py index f23b712..d055cd5 100644 --- a/IPython/Extensions/ipy_vimserver.py +++ b/IPython/deathrow/ipy_vimserver.py @@ -67,15 +67,16 @@ something. Instead of edit, use the vim magic. Thats it! """ -import IPython.ipapi -#import ipythonhooks +from IPython.core import ipapi +from IPython.core.error import TryNext + import socket, select import os, threading, subprocess import re ERRCONDS = select.POLLHUP|select.POLLERR SERVER = None -ip = IPython.ipapi.get() +ip = ipapi.get() # this listens to a unix domain socket in a separate thread, so that comms # between a vim instance and ipython can happen in a fun and productive way @@ -161,7 +162,7 @@ def shutdown_server(self): if SERVER: SERVER.stop() SERVER.join(3) - raise IPython.ipapi.TryNext + raise TryNext ip.set_hook('shutdown_hook', shutdown_server, 10) @@ -235,5 +236,5 @@ def vim(self, argstr): argstr = 'edit -x ' + argstr ip.magic(argstr) -ip.expose_magic('vim', vim) +ip.define_magic('vim', vim) diff --git a/IPython/Extensions/numeric_formats.py b/IPython/deathrow/numeric_formats.py similarity index 100% rename from IPython/Extensions/numeric_formats.py rename to IPython/deathrow/numeric_formats.py diff --git a/IPython/numutils.py b/IPython/deathrow/numutils.py similarity index 100% rename from IPython/numutils.py rename to IPython/deathrow/numutils.py diff --git a/IPython/Extensions/scitedirector.py b/IPython/deathrow/scitedirector.py similarity index 100% rename from IPython/Extensions/scitedirector.py rename to IPython/deathrow/scitedirector.py diff --git a/IPython/deathrow/tests/__init__.py b/IPython/deathrow/tests/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/IPython/deathrow/tests/__init__.py diff --git a/IPython/twshell.py b/IPython/deathrow/twshell.py similarity index 95% rename from IPython/twshell.py rename to IPython/deathrow/twshell.py index f00ab02..6ad78fe 100644 --- a/IPython/twshell.py +++ b/IPython/deathrow/twshell.py @@ -2,17 +2,20 @@ XXX - This module is missing proper docs. """ +# Tell nose to skip this module +__test__ = {} + import sys from twisted.internet import reactor, threads -from IPython.ipmaker import make_IPython -from IPython.iplib import InteractiveShell -from IPython.ipstruct import Struct +from IPython.core.ipmaker import make_IPython +from IPython.core.iplib import InteractiveShell +from IPython.utils.ipstruct import Struct import Queue,thread,threading,signal from signal import signal, SIGINT -from IPython.genutils import Term,warn,error,flag_calls, ask_yes_no -import shellglobals +from IPython.utils.genutils import Term,warn,error,flag_calls, ask_yes_no +from IPython.core import shellglobals def install_gtk2(): """ Install gtk2 reactor, needs to be called bef """ diff --git a/IPython/Extensions/__init__.py b/IPython/extensions/__init__.py similarity index 88% rename from IPython/Extensions/__init__.py rename to IPython/extensions/__init__.py index abc363b..22e892d 100644 --- a/IPython/Extensions/__init__.py +++ b/IPython/extensions/__init__.py @@ -6,7 +6,7 @@ PhysicalQ_Input for an example of how to do this). Any file located here can be called with an 'execfile =' option as - execfile = Extensions/filename.py + execfile = extensions/filename.py since the IPython directory itself is already part of the search path for files listed as 'execfile ='. diff --git a/IPython/extensions/parallelmagic.py b/IPython/extensions/parallelmagic.py new file mode 100644 index 0000000..f72b3b6 --- /dev/null +++ b/IPython/extensions/parallelmagic.py @@ -0,0 +1,205 @@ +#!/usr/bin/env python +# encoding: utf-8 + +"""Magic command interface for interactive parallel work.""" + +#----------------------------------------------------------------------------- +# Copyright (C) 2008-2009 The IPython Development Team +# +# Distributed under the terms of the BSD License. The full license is in +# the file COPYING, distributed as part of this software. +#----------------------------------------------------------------------------- + +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- + +import new + +from IPython.core.component import Component +from IPython.utils.traitlets import Bool, Any +from IPython.utils.autoattr import auto_attr + +#----------------------------------------------------------------------------- +# Definitions of magic functions for use with IPython +#----------------------------------------------------------------------------- + + +NO_ACTIVE_MULTIENGINE_CLIENT = """ +Use activate() on a MultiEngineClient object to activate it for magics. +""" + + +class ParalleMagicComponent(Component): + """A component to manage the %result, %px and %autopx magics.""" + + active_multiengine_client = Any() + verbose = Bool(False, config=True) + + def __init__(self, parent, name=None, config=None): + super(ParalleMagicComponent, self).__init__(parent, name=name, config=config) + self._define_magics() + # A flag showing if autopx is activated or not + self.autopx = False + + # Access other components like this rather than by a regular attribute. + # This won't lookup the InteractiveShell object until it is used and + # then it is cached. This is both efficient and couples this class + # more loosely to InteractiveShell. + @auto_attr + def shell(self): + return Component.get_instances( + root=self.root, + klass='IPython.core.iplib.InteractiveShell')[0] + + def _define_magics(self): + """Define the magic functions.""" + self.shell.define_magic('result', self.magic_result) + self.shell.define_magic('px', self.magic_px) + self.shell.define_magic('autopx', self.magic_autopx) + + def magic_result(self, ipself, parameter_s=''): + """Print the result of command i on all engines.. + + To use this a :class:`MultiEngineClient` instance must be created + and then activated by calling its :meth:`activate` method. + + Then you can do the following:: + + In [23]: %result + Out[23]: + + [0] In [6]: a = 10 + [1] In [6]: a = 10 + + In [22]: %result 6 + Out[22]: + + [0] In [6]: a = 10 + [1] In [6]: a = 10 + """ + if self.active_multiengine_client is None: + print NO_ACTIVE_MULTIENGINE_CLIENT + return + + try: + index = int(parameter_s) + except: + index = None + result = self.active_multiengine_client.get_result(index) + return result + + def magic_px(self, ipself, parameter_s=''): + """Executes the given python command in parallel. + + To use this a :class:`MultiEngineClient` instance must be created + and then activated by calling its :meth:`activate` method. + + Then you can do the following:: + + In [24]: %px a = 5 + Parallel execution on engines: all + Out[24]: + + [0] In [7]: a = 5 + [1] In [7]: a = 5 + """ + + if self.active_multiengine_client is None: + print NO_ACTIVE_MULTIENGINE_CLIENT + return + print "Parallel execution on engines: %s" % self.active_multiengine_client.targets + result = self.active_multiengine_client.execute(parameter_s) + return result + + def magic_autopx(self, ipself, parameter_s=''): + """Toggles auto parallel mode. + + To use this a :class:`MultiEngineClient` instance must be created + and then activated by calling its :meth:`activate` method. Once this + is called, all commands typed at the command line are send to + the engines to be executed in parallel. To control which engine + are used, set the ``targets`` attributed of the multiengine client + before entering ``%autopx`` mode. + + Then you can do the following:: + + In [25]: %autopx + %autopx to enabled + + In [26]: a = 10 + + [0] In [8]: a = 10 + [1] In [8]: a = 10 + + + In [27]: %autopx + %autopx disabled + """ + if self.autopx: + self._disable_autopx() + else: + self._enable_autopx() + + def _enable_autopx(self): + """Enable %autopx mode by saving the original runsource and installing + pxrunsource. + """ + if self.active_multiengine_client is None: + print NO_ACTIVE_MULTIENGINE_CLIENT + return + + self._original_runsource = self.shell.runsource + self.shell.runsource = new.instancemethod( + self.pxrunsource, self.shell, self.shell.__class__ + ) + self.autopx = True + print "%autopx enabled" + + def _disable_autopx(self): + """Disable %autopx by restoring the original InteractiveShell.runsource.""" + if self.autopx: + self.shell.runsource = self._original_runsource + self.autopx = False + print "%autopx disabled" + + def pxrunsource(self, ipself, source, filename="", symbol="single"): + """A parallel replacement for InteractiveShell.runsource.""" + + try: + code = ipself.compile(source, filename, symbol) + except (OverflowError, SyntaxError, ValueError): + # Case 1 + ipself.showsyntaxerror(filename) + return None + + if code is None: + # Case 2 + return True + + # Case 3 + # Because autopx is enabled, we now call executeAll or disable autopx if + # %autopx or autopx has been called + if 'get_ipython().magic("%autopx' in source or 'get_ipython().magic("autopx' in source: + self._disable_autopx() + return False + else: + try: + result = self.active_multiengine_client.execute(source) + except: + ipself.showtraceback() + else: + print result.__repr__() + return False + + +_loaded = False + + +def load_ipython_extension(ip): + """Load the extension in IPython.""" + global _loaded + if not _loaded: + prd = ParalleMagicComponent(ip, name='parallel_magic') + _loaded = True + diff --git a/IPython/extensions/pretty.py b/IPython/extensions/pretty.py new file mode 100644 index 0000000..3ff1bc3 --- /dev/null +++ b/IPython/extensions/pretty.py @@ -0,0 +1,222 @@ +"""Use pretty.py for configurable pretty-printing. + +To enable this extension in your configuration +file, add the following to :file:`ipython_config.py`:: + + c.Global.extensions = ['IPython.extensions.pretty'] + def dict_pprinter(obj, p, cycle): + return p.text("") + c.PrettyResultDisplay.verbose = True + c.PrettyResultDisplay.defaults_for_type = [ + (dict, dict_pprinter) + ] + c.PrettyResultDisplay.defaults_for_type_by_name = [ + ('numpy', 'dtype', 'IPython.extensions.pretty.dtype_pprinter') + ] + +This extension can also be loaded by using the ``%load_ext`` magic:: + + %load_ext IPython.extensions.pretty + +If this extension is enabled, you can always add additional pretty printers +by doing:: + + ip = get_ipython() + prd = ip.get_component('pretty_result_display') + import numpy + from IPython.extensions.pretty import dtype_pprinter + prd.for_type(numpy.dtype, dtype_pprinter) + + # If you don't want to have numpy imported until it needs to be: + prd.for_type_by_name('numpy', 'dtype', dtype_pprinter) +""" + +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- + +from IPython.core.error import TryNext +from IPython.external import pretty +from IPython.core.component import Component +from IPython.utils.traitlets import Bool, List +from IPython.utils.genutils import Term +from IPython.utils.autoattr import auto_attr +from IPython.utils.importstring import import_item + +#----------------------------------------------------------------------------- +# Code +#----------------------------------------------------------------------------- + + +_loaded = False + + +class PrettyResultDisplay(Component): + """A component for pretty printing on steroids.""" + + verbose = Bool(False, config=True) + + # A list of (type, func_name), like + # [(dict, 'my_dict_printer')] + # The final argument can also be a callable + defaults_for_type = List(default_value=[], config=True) + + # A list of (module_name, type_name, func_name), like + # [('numpy', 'dtype', 'IPython.extensions.pretty.dtype_pprinter')] + # The final argument can also be a callable + defaults_for_type_by_name = List(default_value=[], config=True) + + def __init__(self, parent, name=None, config=None): + super(PrettyResultDisplay, self).__init__(parent, name=name, config=config) + self._setup_defaults() + + def _setup_defaults(self): + """Initialize the default pretty printers.""" + for typ, func_name in self.defaults_for_type: + func = self._resolve_func_name(func_name) + self.for_type(typ, func) + for type_module, type_name, func_name in self.defaults_for_type_by_name: + func = self._resolve_func_name(func_name) + self.for_type_by_name(type_module, type_name, func) + + def _resolve_func_name(self, func_name): + if callable(func_name): + return func_name + elif isinstance(func_name, basestring): + return import_item(func_name) + else: + raise TypeError('func_name must be a str or callable, got: %r' % func_name) + + # Access other components like this rather than by a regular attribute. + # This won't lookup the InteractiveShell object until it is used and + # then it is cached. This is both efficient and couples this class + # more loosely to InteractiveShell. + @auto_attr + def shell(self): + return Component.get_instances( + root=self.root, + klass='IPython.core.iplib.InteractiveShell')[0] + + def __call__(self, otherself, arg): + """Uber-pretty-printing display hook. + + Called for displaying the result to the user. + """ + + if self.shell.pprint: + out = pretty.pretty(arg, verbose=self.verbose) + if '\n' in out: + # So that multi-line strings line up with the left column of + # the screen, instead of having the output prompt mess up + # their first line. + Term.cout.write('\n') + print >>Term.cout, out + else: + raise TryNext + + def for_type(self, typ, func): + """Add a pretty printer for a type.""" + return pretty.for_type(typ, func) + + def for_type_by_name(self, type_module, type_name, func): + """Add a pretty printer for a type by its name and module name.""" + return pretty.for_type_by_name(type_module, type_name, func) + + +#----------------------------------------------------------------------------- +# Initialization code for the extension +#----------------------------------------------------------------------------- + + +def load_ipython_extension(ip): + """Load the extension in IPython as a hook.""" + global _loaded + if not _loaded: + prd = PrettyResultDisplay(ip, name='pretty_result_display') + ip.set_hook('result_display', prd, priority=99) + _loaded = True + +def unload_ipython_extension(ip): + """Unload the extension.""" + # The hook system does not have a way to remove a hook so this is a pass + pass + + +#----------------------------------------------------------------------------- +# Example pretty printers +#----------------------------------------------------------------------------- + + +def dtype_pprinter(obj, p, cycle): + """ A pretty-printer for numpy dtype objects. + """ + if cycle: + return p.text('dtype(...)') + if hasattr(obj, 'fields'): + if obj.fields is None: + p.text(repr(obj)) + else: + p.begin_group(7, 'dtype([') + for i, field in enumerate(obj.descr): + if i > 0: + p.text(',') + p.breakable() + p.pretty(field) + p.end_group(7, '])') + + +#----------------------------------------------------------------------------- +# Tests +#----------------------------------------------------------------------------- + + +def test_pretty(): + """ + In [1]: from IPython.extensions import ipy_pretty + + In [2]: ipy_pretty.activate() + + In [3]: class A(object): + ...: def __repr__(self): + ...: return 'A()' + ...: + ...: + + In [4]: a = A() + + In [5]: a + Out[5]: A() + + In [6]: def a_pretty_printer(obj, p, cycle): + ...: p.text('') + ...: + ...: + + In [7]: ipy_pretty.for_type(A, a_pretty_printer) + + In [8]: a + Out[8]: + + In [9]: class B(object): + ...: def __repr__(self): + ...: return 'B()' + ...: + ...: + + In [10]: B.__module__, B.__name__ + Out[10]: ('__main__', 'B') + + In [11]: def b_pretty_printer(obj, p, cycle): + ....: p.text('') + ....: + ....: + + In [12]: ipy_pretty.for_type_by_name('__main__', 'B', b_pretty_printer) + + In [13]: b = B() + + In [14]: b + Out[14]: + """ + assert False, "This should only be doctested, not run." + diff --git a/IPython/extensions/tests/__init__.py b/IPython/extensions/tests/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/IPython/extensions/tests/__init__.py diff --git a/IPython/extensions/tests/test_pretty.py b/IPython/extensions/tests/test_pretty.py new file mode 100644 index 0000000..3f9a10b --- /dev/null +++ b/IPython/extensions/tests/test_pretty.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +Simple tests for :mod:`IPython.extensions.pretty`. +""" + +#----------------------------------------------------------------------------- +# Copyright (C) 2008-2009 The IPython Development Team +# +# Distributed under the terms of the BSD License. The full license is in +# the file COPYING, distributed as part of this software. +#----------------------------------------------------------------------------- + +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- + +import sys +from unittest import TestCase + +from IPython.core.component import Component, masquerade_as +from IPython.core.iplib import InteractiveShell +from IPython.extensions import pretty as pretty_ext +from IPython.external import pretty + +from IPython.utils.traitlets import Bool + +#----------------------------------------------------------------------------- +# Tests +#----------------------------------------------------------------------------- + + +class InteractiveShellStub(Component): + pprint = Bool(True) + +class A(object): + pass + +def a_pprinter(o, p, c): + return p.text("") + +class TestPrettyResultDisplay(TestCase): + + def setUp(self): + self.ip = InteractiveShellStub(None) + # This allows our stub to be retrieved instead of the real InteractiveShell + masquerade_as(self.ip, InteractiveShell) + self.prd = pretty_ext.PrettyResultDisplay(self.ip, name='pretty_result_display') + + def test_for_type(self): + self.prd.for_type(A, a_pprinter) + a = A() + result = pretty.pretty(a) + self.assertEquals(result, "") + + diff --git a/IPython/external/argparse.py b/IPython/external/argparse.py index d17290d..c8c6a4c 100644 --- a/IPython/external/argparse.py +++ b/IPython/external/argparse.py @@ -1,1867 +1,2278 @@ -# -*- coding: utf-8 -*- - -# Copyright � 2006 Steven J. Bethard . -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted under the terms of the 3-clause BSD -# license. No warranty expressed or implied. -# For details, see the accompanying file LICENSE.txt. - -"""Command-line parsing library - -This module is an optparse-inspired command-line parsing library that: - -* handles both optional and positional arguments -* produces highly informative usage messages -* supports parsers that dispatch to sub-parsers - -The following is a simple usage example that sums integers from the -command-line and writes the result to a file: - - parser = argparse.ArgumentParser( - description='sum the integers at the command line') - parser.add_argument( - 'integers', metavar='int', nargs='+', type=int, - help='an integer to be summed') - parser.add_argument( - '--log', default=sys.stdout, type=argparse.FileType('w'), - help='the file where the sum should be written') - args = parser.parse_args() - args.log.write('%s' % sum(args.integers)) - args.log.close() - -The module contains the following public classes: - - ArgumentParser -- The main entry point for command-line parsing. As the - example above shows, the add_argument() method is used to populate - the parser with actions for optional and positional arguments. Then - the parse_args() method is invoked to convert the args at the - command-line into an object with attributes. - - ArgumentError -- The exception raised by ArgumentParser objects when - there are errors with the parser's actions. Errors raised while - parsing the command-line are caught by ArgumentParser and emitted - as command-line messages. - - FileType -- A factory for defining types of files to be created. As the - example above shows, instances of FileType are typically passed as - the type= argument of add_argument() calls. - - Action -- The base class for parser actions. Typically actions are - selected by passing strings like 'store_true' or 'append_const' to - the action= argument of add_argument(). However, for greater - customization of ArgumentParser actions, subclasses of Action may - be defined and passed as the action= argument. - - HelpFormatter, RawDescriptionHelpFormatter -- Formatter classes which - may be passed as the formatter_class= argument to the - ArgumentParser constructor. HelpFormatter is the default, while - RawDescriptionHelpFormatter tells the parser not to perform any - line-wrapping on description text. - -All other classes in this module are considered implementation details. -(Also note that HelpFormatter and RawDescriptionHelpFormatter are only -considered public as object names -- the API of the formatter objects is -still considered an implementation detail.) -""" - -__version__ = '0.8.0' - -import os as _os -import re as _re -import sys as _sys -import textwrap as _textwrap - -from gettext import gettext as _ - -SUPPRESS = '==SUPPRESS==' - -OPTIONAL = '?' -ZERO_OR_MORE = '*' -ONE_OR_MORE = '+' -PARSER = '==PARSER==' - -# ============================= -# Utility functions and classes -# ============================= - -class _AttributeHolder(object): - """Abstract base class that provides __repr__. - - The __repr__ method returns a string in the format: - ClassName(attr=name, attr=name, ...) - The attributes are determined either by a class-level attribute, - '_kwarg_names', or by inspecting the instance __dict__. - """ - - def __repr__(self): - type_name = type(self).__name__ - arg_strings = [] - for arg in self._get_args(): - arg_strings.append(repr(arg)) - for name, value in self._get_kwargs(): - arg_strings.append('%s=%r' % (name, value)) - return '%s(%s)' % (type_name, ', '.join(arg_strings)) - - def _get_kwargs(self): - return sorted(self.__dict__.items()) - - def _get_args(self): - return [] - -def _ensure_value(namespace, name, value): - if getattr(namespace, name, None) is None: - setattr(namespace, name, value) - return getattr(namespace, name) - - - -# =============== -# Formatting Help -# =============== - -class HelpFormatter(object): - - def __init__(self, - prog, - indent_increment=2, - max_help_position=24, - width=None): - - # default setting for width - if width is None: - try: - width = int(_os.environ['COLUMNS']) - except (KeyError, ValueError): - width = 80 - width -= 2 - - self._prog = prog - self._indent_increment = indent_increment - self._max_help_position = max_help_position - self._width = width - - self._current_indent = 0 - self._level = 0 - self._action_max_length = 0 - - self._root_section = self._Section(self, None) - self._current_section = self._root_section - - self._whitespace_matcher = _re.compile(r'\s+') - self._long_break_matcher = _re.compile(r'\n\n\n+') - - # =============================== - # Section and indentation methods - # =============================== - - def _indent(self): - self._current_indent += self._indent_increment - self._level += 1 - - def _dedent(self): - self._current_indent -= self._indent_increment - assert self._current_indent >= 0, 'Indent decreased below 0.' - self._level -= 1 - - class _Section(object): - def __init__(self, formatter, parent, heading=None): - self.formatter = formatter - self.parent = parent - self.heading = heading - self.items = [] - - def format_help(self): - # format the indented section - if self.parent is not None: - self.formatter._indent() - join = self.formatter._join_parts - for func, args in self.items: - func(*args) - item_help = join(func(*args) for func, args in self.items) - if self.parent is not None: - self.formatter._dedent() - - # return nothing if the section was empty - if not item_help: - return '' - - # add the heading if the section was non-empty - if self.heading is not SUPPRESS and self.heading is not None: - current_indent = self.formatter._current_indent - heading = '%*s%s:\n' % (current_indent, '', self.heading) - else: - heading = '' - - # join the section-initial newline, the heading and the help - return join(['\n', heading, item_help, '\n']) - - def _add_item(self, func, args): - self._current_section.items.append((func, args)) - - # ======================== - # Message building methods - # ======================== - - def start_section(self, heading): - self._indent() - section = self._Section(self, self._current_section, heading) - self._add_item(section.format_help, []) - self._current_section = section - - def end_section(self): - self._current_section = self._current_section.parent - self._dedent() - - def add_text(self, text): - if text is not SUPPRESS and text is not None: - self._add_item(self._format_text, [text]) - - def add_usage(self, usage, optionals, positionals, prefix=None): - if usage is not SUPPRESS: - args = usage, optionals, positionals, prefix - self._add_item(self._format_usage, args) - - def add_argument(self, action): - if action.help is not SUPPRESS: - - # find all invocations - get_invocation = self._format_action_invocation - invocations = [get_invocation(action)] - for subaction in self._iter_indented_subactions(action): - invocations.append(get_invocation(subaction)) - - # update the maximum item length - invocation_length = max(len(s) for s in invocations) - action_length = invocation_length + self._current_indent - self._action_max_length = max(self._action_max_length, - action_length) - - # add the item to the list - self._add_item(self._format_action, [action]) - - def add_arguments(self, actions): - for action in actions: - self.add_argument(action) - - # ======================= - # Help-formatting methods - # ======================= - - def format_help(self): - help = self._root_section.format_help() % dict(prog=self._prog) - if help: - help = self._long_break_matcher.sub('\n\n', help) - help = help.strip('\n') + '\n' - return help - - def _join_parts(self, part_strings): - return ''.join(part - for part in part_strings - if part and part is not SUPPRESS) - - def _format_usage(self, usage, optionals, positionals, prefix): - if prefix is None: - prefix = _('usage: ') - - # if no optionals or positionals are available, usage is just prog - if usage is None and not optionals and not positionals: - usage = '%(prog)s' - - # if optionals and positionals are available, calculate usage - elif usage is None: - usage = '%(prog)s' % dict(prog=self._prog) - - # determine width of "usage: PROG" and width of text - prefix_width = len(prefix) + len(usage) + 1 - prefix_indent = self._current_indent + prefix_width - text_width = self._width - self._current_indent - - # put them on one line if they're short enough - format = self._format_actions_usage - action_usage = format(optionals + positionals) - if prefix_width + len(action_usage) + 1 < text_width: - usage = '%s %s' % (usage, action_usage) - - # if they're long, wrap optionals and positionals individually - else: - optional_usage = format(optionals) - positional_usage = format(positionals) - indent = ' ' * prefix_indent - - # usage is made of PROG, optionals and positionals - parts = [usage, ' '] - - # options always get added right after PROG - if optional_usage: - parts.append(_textwrap.fill( - optional_usage, text_width, - initial_indent=indent, - subsequent_indent=indent).lstrip()) - - # if there were options, put arguments on the next line - # otherwise, start them right after PROG - if positional_usage: - part = _textwrap.fill( - positional_usage, text_width, - initial_indent=indent, - subsequent_indent=indent).lstrip() - if optional_usage: - part = '\n' + indent + part - parts.append(part) - usage = ''.join(parts) - - # prefix with 'usage:' - return '%s%s\n\n' % (prefix, usage) - - def _format_actions_usage(self, actions): - parts = [] - for action in actions: - if action.help is SUPPRESS: - continue - - # produce all arg strings - if not action.option_strings: - parts.append(self._format_args(action, action.dest)) - - # produce the first way to invoke the option in brackets - else: - option_string = action.option_strings[0] - - # if the Optional doesn't take a value, format is: - # -s or --long - if action.nargs == 0: - part = '%s' % option_string - - # if the Optional takes a value, format is: - # -s ARGS or --long ARGS - else: - default = action.dest.upper() - args_string = self._format_args(action, default) - part = '%s %s' % (option_string, args_string) - - # make it look optional if it's not required - if not action.required: - part = '[%s]' % part - parts.append(part) - - return ' '.join(parts) - - def _format_text(self, text): - text_width = self._width - self._current_indent - indent = ' ' * self._current_indent - return self._fill_text(text, text_width, indent) + '\n\n' - - def _format_action(self, action): - # determine the required width and the entry label - help_position = min(self._action_max_length + 2, - self._max_help_position) - help_width = self._width - help_position - action_width = help_position - self._current_indent - 2 - action_header = self._format_action_invocation(action) - - # ho nelp; start on same line and add a final newline - if not action.help: - tup = self._current_indent, '', action_header - action_header = '%*s%s\n' % tup - - # short action name; start on the same line and pad two spaces - elif len(action_header) <= action_width: - tup = self._current_indent, '', action_width, action_header - action_header = '%*s%-*s ' % tup - indent_first = 0 - - # long action name; start on the next line - else: - tup = self._current_indent, '', action_header - action_header = '%*s%s\n' % tup - indent_first = help_position - - # collect the pieces of the action help - parts = [action_header] - - # if there was help for the action, add lines of help text - if action.help: - help_text = self._expand_help(action) - help_lines = self._split_lines(help_text, help_width) - parts.append('%*s%s\n' % (indent_first, '', help_lines[0])) - for line in help_lines[1:]: - parts.append('%*s%s\n' % (help_position, '', line)) - - # or add a newline if the description doesn't end with one - elif not action_header.endswith('\n'): - parts.append('\n') - - # if there are any sub-actions, add their help as well - for subaction in self._iter_indented_subactions(action): - parts.append(self._format_action(subaction)) - - # return a single string - return self._join_parts(parts) - - def _format_action_invocation(self, action): - if not action.option_strings: - return self._format_metavar(action, action.dest) - - else: - parts = [] - - # if the Optional doesn't take a value, format is: - # -s, --long - if action.nargs == 0: - parts.extend(action.option_strings) - - # if the Optional takes a value, format is: - # -s ARGS, --long ARGS - else: - default = action.dest.upper() - args_string = self._format_args(action, default) - for option_string in action.option_strings: - parts.append('%s %s' % (option_string, args_string)) - - return ', '.join(parts) - - def _format_metavar(self, action, default_metavar): - if action.metavar is not None: - name = action.metavar - elif action.choices is not None: - choice_strs = (str(choice) for choice in action.choices) - name = '{%s}' % ','.join(choice_strs) - else: - name = default_metavar - return name - - def _format_args(self, action, default_metavar): - name = self._format_metavar(action, default_metavar) - if action.nargs is None: - result = name - elif action.nargs == OPTIONAL: - result = '[%s]' % name - elif action.nargs == ZERO_OR_MORE: - result = '[%s [%s ...]]' % (name, name) - elif action.nargs == ONE_OR_MORE: - result = '%s [%s ...]' % (name, name) - elif action.nargs is PARSER: - result = '%s ...' % name - else: - result = ' '.join([name] * action.nargs) - return result - - def _expand_help(self, action): - params = dict(vars(action), prog=self._prog) - for name, value in params.items(): - if value is SUPPRESS: - del params[name] - if params.get('choices') is not None: - choices_str = ', '.join(str(c) for c in params['choices']) - params['choices'] = choices_str - return action.help % params - - def _iter_indented_subactions(self, action): - try: - get_subactions = action._get_subactions - except AttributeError: - pass - else: - self._indent() - for subaction in get_subactions(): - yield subaction - self._dedent() - - def _split_lines(self, text, width): - text = self._whitespace_matcher.sub(' ', text).strip() - return _textwrap.wrap(text, width) - - def _fill_text(self, text, width, indent): - text = self._whitespace_matcher.sub(' ', text).strip() - return _textwrap.fill(text, width, initial_indent=indent, - subsequent_indent=indent) - -class RawDescriptionHelpFormatter(HelpFormatter): - - def _fill_text(self, text, width, indent): - return ''.join(indent + line for line in text.splitlines(True)) - -class RawTextHelpFormatter(RawDescriptionHelpFormatter): - - def _split_lines(self, text, width): - return text.splitlines() - -# ===================== -# Options and Arguments -# ===================== - -class ArgumentError(Exception): - """ArgumentError(message, argument) - - Raised whenever there was an error creating or using an argument - (optional or positional). - - The string value of this exception is the message, augmented with - information about the argument that caused it. - """ - - def __init__(self, argument, message): - if argument.option_strings: - self.argument_name = '/'.join(argument.option_strings) - elif argument.metavar not in (None, SUPPRESS): - self.argument_name = argument.metavar - elif argument.dest not in (None, SUPPRESS): - self.argument_name = argument.dest - else: - self.argument_name = None - self.message = message - - def __str__(self): - if self.argument_name is None: - format = '%(message)s' - else: - format = 'argument %(argument_name)s: %(message)s' - return format % dict(message=self.message, - argument_name=self.argument_name) - -# ============== -# Action classes -# ============== - -class Action(_AttributeHolder): - """Action(*strings, **options) - - Action objects hold the information necessary to convert a - set of command-line arguments (possibly including an initial option - string) into the desired Python object(s). - - Keyword Arguments: - - option_strings -- A list of command-line option strings which - should be associated with this action. - - dest -- The name of the attribute to hold the created object(s) - - nargs -- The number of command-line arguments that should be consumed. - By default, one argument will be consumed and a single value will - be produced. Other values include: - * N (an integer) consumes N arguments (and produces a list) - * '?' consumes zero or one arguments - * '*' consumes zero or more arguments (and produces a list) - * '+' consumes one or more arguments (and produces a list) - Note that the difference between the default and nargs=1 is that - with the default, a single value will be produced, while with - nargs=1, a list containing a single value will be produced. - - const -- The value to be produced if the option is specified and the - option uses an action that takes no values. - - default -- The value to be produced if the option is not specified. - - type -- The type which the command-line arguments should be converted - to, should be one of 'string', 'int', 'float', 'complex' or a - callable object that accepts a single string argument. If None, - 'string' is assumed. - - choices -- A container of values that should be allowed. If not None, - after a command-line argument has been converted to the appropriate - type, an exception will be raised if it is not a member of this - collection. - - required -- True if the action must always be specified at the command - line. This is only meaningful for optional command-line arguments. - - help -- The help string describing the argument. - - metavar -- The name to be used for the option's argument with the help - string. If None, the 'dest' value will be used as the name. - """ - - - def __init__(self, - option_strings, - dest, - nargs=None, - const=None, - default=None, - type=None, - choices=None, - required=False, - help=None, - metavar=None): - self.option_strings = option_strings - self.dest = dest - self.nargs = nargs - self.const = const - self.default = default - self.type = type - self.choices = choices - self.required = required - self.help = help - self.metavar = metavar - - def _get_kwargs(self): - names = [ - 'option_strings', - 'dest', - 'nargs', - 'const', - 'default', - 'type', - 'choices', - 'help', - 'metavar' - ] - return [(name, getattr(self, name)) for name in names] - - def __call__(self, parser, namespace, values, option_string=None): - raise NotImplementedError(_('.__call__() not defined')) - -class _StoreAction(Action): - def __init__(self, - option_strings, - dest, - nargs=None, - const=None, - default=None, - type=None, - choices=None, - required=False, - help=None, - metavar=None): - if nargs == 0: - raise ValueError('nargs must be > 0') - if const is not None and nargs != OPTIONAL: - raise ValueError('nargs must be %r to supply const' % OPTIONAL) - super(_StoreAction, self).__init__( - option_strings=option_strings, - dest=dest, - nargs=nargs, - const=const, - default=default, - type=type, - choices=choices, - required=required, - help=help, - metavar=metavar) - - def __call__(self, parser, namespace, values, option_string=None): - setattr(namespace, self.dest, values) - -class _StoreConstAction(Action): - def __init__(self, - option_strings, - dest, - const, - default=None, - required=False, - help=None, - metavar=None): - super(_StoreConstAction, self).__init__( - option_strings=option_strings, - dest=dest, - nargs=0, - const=const, - default=default, - required=required, - help=help) - - def __call__(self, parser, namespace, values, option_string=None): - setattr(namespace, self.dest, self.const) - -class _StoreTrueAction(_StoreConstAction): - def __init__(self, - option_strings, - dest, - default=False, - required=False, - help=None): - super(_StoreTrueAction, self).__init__( - option_strings=option_strings, - dest=dest, - const=True, - default=default, - required=required, - help=help) - -class _StoreFalseAction(_StoreConstAction): - def __init__(self, - option_strings, - dest, - default=True, - required=False, - help=None): - super(_StoreFalseAction, self).__init__( - option_strings=option_strings, - dest=dest, - const=False, - default=default, - required=required, - help=help) - -class _AppendAction(Action): - def __init__(self, - option_strings, - dest, - nargs=None, - const=None, - default=None, - type=None, - choices=None, - required=False, - help=None, - metavar=None): - if nargs == 0: - raise ValueError('nargs must be > 0') - if const is not None and nargs != OPTIONAL: - raise ValueError('nargs must be %r to supply const' % OPTIONAL) - super(_AppendAction, self).__init__( - option_strings=option_strings, - dest=dest, - nargs=nargs, - const=const, - default=default, - type=type, - choices=choices, - required=required, - help=help, - metavar=metavar) - - def __call__(self, parser, namespace, values, option_string=None): - _ensure_value(namespace, self.dest, []).append(values) - -class _AppendConstAction(Action): - def __init__(self, - option_strings, - dest, - const, - default=None, - required=False, - help=None, - metavar=None): - super(_AppendConstAction, self).__init__( - option_strings=option_strings, - dest=dest, - nargs=0, - const=const, - default=default, - required=required, - help=help, - metavar=metavar) - - def __call__(self, parser, namespace, values, option_string=None): - _ensure_value(namespace, self.dest, []).append(self.const) - -class _CountAction(Action): - def __init__(self, - option_strings, - dest, - default=None, - required=False, - help=None): - super(_CountAction, self).__init__( - option_strings=option_strings, - dest=dest, - nargs=0, - default=default, - required=required, - help=help) - - def __call__(self, parser, namespace, values, option_string=None): - new_count = _ensure_value(namespace, self.dest, 0) + 1 - setattr(namespace, self.dest, new_count) - -class _HelpAction(Action): - def __init__(self, - option_strings, - dest=SUPPRESS, - default=SUPPRESS, - help=None): - super(_HelpAction, self).__init__( - option_strings=option_strings, - dest=dest, - default=default, - nargs=0, - help=help) - - def __call__(self, parser, namespace, values, option_string=None): - parser.print_help() - parser.exit() - -class _VersionAction(Action): - def __init__(self, - option_strings, - dest=SUPPRESS, - default=SUPPRESS, - help=None): - super(_VersionAction, self).__init__( - option_strings=option_strings, - dest=dest, - default=default, - nargs=0, - help=help) - - def __call__(self, parser, namespace, values, option_string=None): - parser.print_version() - parser.exit() - -class _SubParsersAction(Action): - - class _ChoicesPseudoAction(Action): - def __init__(self, name, help): - sup = super(_SubParsersAction._ChoicesPseudoAction, self) - sup.__init__(option_strings=[], dest=name, help=help) - - - def __init__(self, - option_strings, - prog, - parser_class, - dest=SUPPRESS, - help=None, - metavar=None): - - self._prog_prefix = prog - self._parser_class = parser_class - self._name_parser_map = {} - self._choices_actions = [] - - super(_SubParsersAction, self).__init__( - option_strings=option_strings, - dest=dest, - nargs=PARSER, - choices=self._name_parser_map, - help=help, - metavar=metavar) - - def add_parser(self, name, **kwargs): - # set prog from the existing prefix - if kwargs.get('prog') is None: - kwargs['prog'] = '%s %s' % (self._prog_prefix, name) - - # create a pseudo-action to hold the choice help - if 'help' in kwargs: - help = kwargs.pop('help') - choice_action = self._ChoicesPseudoAction(name, help) - self._choices_actions.append(choice_action) - - # create the parser and add it to the map - parser = self._parser_class(**kwargs) - self._name_parser_map[name] = parser - return parser - - def _get_subactions(self): - return self._choices_actions - - def __call__(self, parser, namespace, values, option_string=None): - parser_name = values[0] - arg_strings = values[1:] - - # set the parser name if requested - if self.dest is not SUPPRESS: - setattr(namespace, self.dest, parser_name) - - # select the parser - try: - parser = self._name_parser_map[parser_name] - except KeyError: - tup = parser_name, ', '.join(self._name_parser_map) - msg = _('unknown parser %r (choices: %s)' % tup) - raise ArgumentError(self, msg) - - # parse all the remaining options into the namespace - parser.parse_args(arg_strings, namespace) - - -# ============== -# Type classes -# ============== - -class FileType(object): - """Factory for creating file object types - - Instances of FileType are typically passed as type= arguments to the - ArgumentParser add_argument() method. - - Keyword Arguments: - mode -- A string indicating how the file is to be opened. Accepts the - same values as the builtin open() function. - bufsize -- The file's desired buffer size. Accepts the same values as - the builtin open() function. - """ - def __init__(self, mode='r', bufsize=None): - self._mode = mode - self._bufsize = bufsize - - def __call__(self, string): - # the special argument "-" means sys.std{in,out} - if string == '-': - if self._mode == 'r': - return _sys.stdin - elif self._mode == 'w': - return _sys.stdout - else: - msg = _('argument "-" with mode %r' % self._mode) - raise ValueError(msg) - - # all other arguments are used as file names - if self._bufsize: - return open(string, self._mode, self._bufsize) - else: - return open(string, self._mode) - - -# =========================== -# Optional and Positional Parsing -# =========================== - -class Namespace(_AttributeHolder): - - def __init__(self, **kwargs): - for name, value in kwargs.iteritems(): - setattr(self, name, value) - - def __eq__(self, other): - return vars(self) == vars(other) - - def __ne__(self, other): - return not (self == other) - - -class _ActionsContainer(object): - def __init__(self, - description, - prefix_chars, - argument_default, - conflict_handler): - super(_ActionsContainer, self).__init__() - - self.description = description - self.argument_default = argument_default - self.prefix_chars = prefix_chars - self.conflict_handler = conflict_handler - - # set up registries - self._registries = {} - - # register actions - self.register('action', None, _StoreAction) - self.register('action', 'store', _StoreAction) - self.register('action', 'store_const', _StoreConstAction) - self.register('action', 'store_true', _StoreTrueAction) - self.register('action', 'store_false', _StoreFalseAction) - self.register('action', 'append', _AppendAction) - self.register('action', 'append_const', _AppendConstAction) - self.register('action', 'count', _CountAction) - self.register('action', 'help', _HelpAction) - self.register('action', 'version', _VersionAction) - self.register('action', 'parsers', _SubParsersAction) - - # raise an exception if the conflict handler is invalid - self._get_handler() - - # action storage - self._optional_actions_list = [] - self._positional_actions_list = [] - self._positional_actions_full_list = [] - self._option_strings = {} - - # defaults storage - self._defaults = {} - - # ==================== - # Registration methods - # ==================== - - def register(self, registry_name, value, object): - registry = self._registries.setdefault(registry_name, {}) - registry[value] = object - - def _registry_get(self, registry_name, value, default=None): - return self._registries[registry_name].get(value, default) - - # ================================== - # Namespace default settings methods - # ================================== - - def set_defaults(self, **kwargs): - self._defaults.update(kwargs) - - # if these defaults match any existing arguments, replace - # the previous default on the object with the new one - for action_list in [self._option_strings.values(), - self._positional_actions_full_list]: - for action in action_list: - if action.dest in kwargs: - action.default = kwargs[action.dest] - - # ======================= - # Adding argument actions - # ======================= - - def add_argument(self, *args, **kwargs): - """ - add_argument(dest, ..., name=value, ...) - add_argument(option_string, option_string, ..., name=value, ...) - """ - - # if no positional args are supplied or only one is supplied and - # it doesn't look like an option string, parse a positional - # argument - chars = self.prefix_chars - if not args or len(args) == 1 and args[0][0] not in chars: - kwargs = self._get_positional_kwargs(*args, **kwargs) - - # otherwise, we're adding an optional argument - else: - kwargs = self._get_optional_kwargs(*args, **kwargs) - - # if no default was supplied, use the parser-level default - if 'default' not in kwargs: - dest = kwargs['dest'] - if dest in self._defaults: - kwargs['default'] = self._defaults[dest] - elif self.argument_default is not None: - kwargs['default'] = self.argument_default - - # create the action object, and add it to the parser - action_class = self._pop_action_class(kwargs) - action = action_class(**kwargs) - return self._add_action(action) - - def _add_action(self, action): - # resolve any conflicts - self._check_conflict(action) - - # add to optional or positional list - if action.option_strings: - self._optional_actions_list.append(action) - else: - self._positional_actions_list.append(action) - self._positional_actions_full_list.append(action) - action.container = self - - # index the action by any option strings it has - for option_string in action.option_strings: - self._option_strings[option_string] = action - - # return the created action - return action - - def _add_container_actions(self, container): - for action in container._optional_actions_list: - self._add_action(action) - for action in container._positional_actions_list: - self._add_action(action) - - def _get_positional_kwargs(self, dest, **kwargs): - # make sure required is not specified - if 'required' in kwargs: - msg = _("'required' is an invalid argument for positionals") - raise TypeError(msg) - - # return the keyword arguments with no option strings - return dict(kwargs, dest=dest, option_strings=[]) - - def _get_optional_kwargs(self, *args, **kwargs): - # determine short and long option strings - option_strings = [] - long_option_strings = [] - for option_string in args: - # error on one-or-fewer-character option strings - if len(option_string) < 2: - msg = _('invalid option string %r: ' - 'must be at least two characters long') - raise ValueError(msg % option_string) - - # error on strings that don't start with an appropriate prefix - if not option_string[0] in self.prefix_chars: - msg = _('invalid option string %r: ' - 'must start with a character %r') - tup = option_string, self.prefix_chars - raise ValueError(msg % tup) - - # error on strings that are all prefix characters - if not (set(option_string) - set(self.prefix_chars)): - msg = _('invalid option string %r: ' - 'must contain characters other than %r') - tup = option_string, self.prefix_chars - raise ValueError(msg % tup) - - # strings starting with two prefix characters are long options - option_strings.append(option_string) - if option_string[0] in self.prefix_chars: - if option_string[1] in self.prefix_chars: - long_option_strings.append(option_string) - - # infer destination, '--foo-bar' -> 'foo_bar' and '-x' -> 'x' - dest = kwargs.pop('dest', None) - if dest is None: - if long_option_strings: - dest_option_string = long_option_strings[0] - else: - dest_option_string = option_strings[0] - dest = dest_option_string.lstrip(self.prefix_chars) - dest = dest.replace('-', '_') - - # return the updated keyword arguments - return dict(kwargs, dest=dest, option_strings=option_strings) - - def _pop_action_class(self, kwargs, default=None): - action = kwargs.pop('action', default) - return self._registry_get('action', action, action) - - def _get_handler(self): - # determine function from conflict handler string - handler_func_name = '_handle_conflict_%s' % self.conflict_handler - try: - return getattr(self, handler_func_name) - except AttributeError: - msg = _('invalid conflict_resolution value: %r') - raise ValueError(msg % self.conflict_handler) - - def _check_conflict(self, action): - - # find all options that conflict with this option - confl_optionals = [] - for option_string in action.option_strings: - if option_string in self._option_strings: - confl_optional = self._option_strings[option_string] - confl_optionals.append((option_string, confl_optional)) - - # resolve any conflicts - if confl_optionals: - conflict_handler = self._get_handler() - conflict_handler(action, confl_optionals) - - def _handle_conflict_error(self, action, conflicting_actions): - message = _('conflicting option string(s): %s') - conflict_string = ', '.join(option_string - for option_string, action - in conflicting_actions) - raise ArgumentError(action, message % conflict_string) - - def _handle_conflict_resolve(self, action, conflicting_actions): - - # remove all conflicting options - for option_string, action in conflicting_actions: - - # remove the conflicting option - action.option_strings.remove(option_string) - self._option_strings.pop(option_string, None) - - # if the option now has no option string, remove it from the - # container holding it - if not action.option_strings: - action.container._optional_actions_list.remove(action) - - -class _ArgumentGroup(_ActionsContainer): - - def __init__(self, container, title=None, description=None, **kwargs): - # add any missing keyword arguments by checking the container - update = kwargs.setdefault - update('conflict_handler', container.conflict_handler) - update('prefix_chars', container.prefix_chars) - update('argument_default', container.argument_default) - super_init = super(_ArgumentGroup, self).__init__ - super_init(description=description, **kwargs) - - self.title = title - self._registries = container._registries - self._positional_actions_full_list = container._positional_actions_full_list - self._option_strings = container._option_strings - self._defaults = container._defaults - - -class ArgumentParser(_AttributeHolder, _ActionsContainer): - - def __init__(self, - prog=None, - usage=None, - description=None, - epilog=None, - version=None, - parents=[], - formatter_class=HelpFormatter, - prefix_chars='-', - argument_default=None, - conflict_handler='error', - add_help=True): - - superinit = super(ArgumentParser, self).__init__ - superinit(description=description, - prefix_chars=prefix_chars, - argument_default=argument_default, - conflict_handler=conflict_handler) - - # default setting for prog - if prog is None: - prog = _os.path.basename(_sys.argv[0]) - - self.prog = prog - self.usage = usage - self.epilog = epilog - self.version = version - self.formatter_class = formatter_class - self.add_help = add_help - - self._argument_group_class = _ArgumentGroup - self._has_subparsers = False - self._argument_groups = [] - - # register types - def identity(string): - return string - self.register('type', None, identity) - - # add help and version arguments if necessary - # (using explicit default to override global argument_default) - if self.add_help: - self.add_argument( - '-h', '--help', action='help', default=SUPPRESS, - help=_('show this help message and exit')) - if self.version: - self.add_argument( - '-v', '--version', action='version', default=SUPPRESS, - help=_("show program's version number and exit")) - - # add parent arguments and defaults - for parent in parents: - self._add_container_actions(parent) - try: - defaults = parent._defaults - except AttributeError: - pass - else: - self._defaults.update(defaults) - - # determines whether an "option" looks like a negative number - self._negative_number_matcher = _re.compile(r'^-\d+|-\d*.\d+$') - - - # ======================= - # Pretty __repr__ methods - # ======================= - - def _get_kwargs(self): - names = [ - 'prog', - 'usage', - 'description', - 'version', - 'formatter_class', - 'conflict_handler', - 'add_help', - ] - return [(name, getattr(self, name)) for name in names] - - # ================================== - # Optional/Positional adding methods - # ================================== - - def add_argument_group(self, *args, **kwargs): - group = self._argument_group_class(self, *args, **kwargs) - self._argument_groups.append(group) - return group - - def add_subparsers(self, **kwargs): - if self._has_subparsers: - self.error(_('cannot have multiple subparser arguments')) - - # add the parser class to the arguments if it's not present - kwargs.setdefault('parser_class', type(self)) - - # prog defaults to the usage message of this parser, skipping - # optional arguments and with no "usage:" prefix - if kwargs.get('prog') is None: - formatter = self._get_formatter() - formatter.add_usage(self.usage, [], - self._get_positional_actions(), '') - kwargs['prog'] = formatter.format_help().strip() - - # create the parsers action and add it to the positionals list - parsers_class = self._pop_action_class(kwargs, 'parsers') - action = parsers_class(option_strings=[], **kwargs) - self._positional_actions_list.append(action) - self._positional_actions_full_list.append(action) - self._has_subparsers = True - - # return the created parsers action - return action - - def _add_container_actions(self, container): - super(ArgumentParser, self)._add_container_actions(container) - try: - groups = container._argument_groups - except AttributeError: - pass - else: - for group in groups: - new_group = self.add_argument_group( - title=group.title, - description=group.description, - conflict_handler=group.conflict_handler) - new_group._add_container_actions(group) - - def _get_optional_actions(self): - actions = [] - actions.extend(self._optional_actions_list) - for argument_group in self._argument_groups: - actions.extend(argument_group._optional_actions_list) - return actions - - def _get_positional_actions(self): - return list(self._positional_actions_full_list) - - - # ===================================== - # Command line argument parsing methods - # ===================================== - - def parse_args(self, args=None, namespace=None): - # args default to the system args - if args is None: - args = _sys.argv[1:] - - # default Namespace built from parser defaults - if namespace is None: - namespace = Namespace() - - # add any action defaults that aren't present - optional_actions = self._get_optional_actions() - positional_actions = self._get_positional_actions() - for action in optional_actions + positional_actions: - if action.dest is not SUPPRESS: - if not hasattr(namespace, action.dest): - if action.default is not SUPPRESS: - default = action.default - if isinstance(action.default, basestring): - default = self._get_value(action, default) - setattr(namespace, action.dest, default) - - # add any parser defaults that aren't present - for dest, value in self._defaults.iteritems(): - if not hasattr(namespace, dest): - setattr(namespace, dest, value) - - # parse the arguments and exit if there are any errors - try: - result = self._parse_args(args, namespace) - except ArgumentError, err: - self.error(str(err)) - - # make sure all required optionals are present - for action in self._get_optional_actions(): - if action.required: - if getattr(result, action.dest, None) is None: - opt_strs = '/'.join(action.option_strings) - msg = _('option %s is required' % opt_strs) - self.error(msg) - - # return the parsed arguments - return result - - def _parse_args(self, arg_strings, namespace): - - # find all option indices, and determine the arg_string_pattern - # which has an 'O' if there is an option at an index, - # an 'A' if there is an argument, or a '-' if there is a '--' - option_string_indices = {} - arg_string_pattern_parts = [] - arg_strings_iter = iter(arg_strings) - for i, arg_string in enumerate(arg_strings_iter): - - # all args after -- are non-options - if arg_string == '--': - arg_string_pattern_parts.append('-') - for arg_string in arg_strings_iter: - arg_string_pattern_parts.append('A') - - # otherwise, add the arg to the arg strings - # and note the index if it was an option - else: - option_tuple = self._parse_optional(arg_string) - if option_tuple is None: - pattern = 'A' - else: - option_string_indices[i] = option_tuple - pattern = 'O' - arg_string_pattern_parts.append(pattern) - - # join the pieces together to form the pattern - arg_strings_pattern = ''.join(arg_string_pattern_parts) - - # converts arg strings to the appropriate and then takes the action - def take_action(action, argument_strings, option_string=None): - argument_values = self._get_values(action, argument_strings) - # take the action if we didn't receive a SUPPRESS value - # (e.g. from a default) - if argument_values is not SUPPRESS: - action(self, namespace, argument_values, option_string) - - # function to convert arg_strings into an optional action - def consume_optional(start_index): - - # determine the optional action and parse any explicit - # argument out of the option string - option_tuple = option_string_indices[start_index] - action, option_string, explicit_arg = option_tuple - - # loop because single-dash options can be chained - # (e.g. -xyz is the same as -x -y -z if no args are required) - match_argument = self._match_argument - action_tuples = [] - while True: - - # if we found no optional action, raise an error - if action is None: - self.error(_('no such option: %s') % option_string) - - # if there is an explicit argument, try to match the - # optional's string arguments to only this - if explicit_arg is not None: - arg_count = match_argument(action, 'A') - - # if the action is a single-dash option and takes no - # arguments, try to parse more single-dash options out - # of the tail of the option string - chars = self.prefix_chars - if arg_count == 0 and option_string[1] not in chars: - action_tuples.append((action, [], option_string)) - parse_optional = self._parse_optional - for char in self.prefix_chars: - option_string = char + explicit_arg - option_tuple = parse_optional(option_string) - if option_tuple[0] is not None: - break - else: - msg = _('ignored explicit argument %r') - raise ArgumentError(action, msg % explicit_arg) - - # set the action, etc. for the next loop iteration - action, option_string, explicit_arg = option_tuple - - # if the action expect exactly one argument, we've - # successfully matched the option; exit the loop - elif arg_count == 1: - stop = start_index + 1 - args = [explicit_arg] - action_tuples.append((action, args, option_string)) - break - - # error if a double-dash option did not use the - # explicit argument - else: - msg = _('ignored explicit argument %r') - raise ArgumentError(action, msg % explicit_arg) - - # if there is no explicit argument, try to match the - # optional's string arguments with the following strings - # if successful, exit the loop - else: - start = start_index + 1 - selected_patterns = arg_strings_pattern[start:] - arg_count = match_argument(action, selected_patterns) - stop = start + arg_count - args = arg_strings[start:stop] - action_tuples.append((action, args, option_string)) - break - - # add the Optional to the list and return the index at which - # the Optional's string args stopped - assert action_tuples - for action, args, option_string in action_tuples: - take_action(action, args, option_string) - return stop - - # the list of Positionals left to be parsed; this is modified - # by consume_positionals() - positionals = self._get_positional_actions() - - # function to convert arg_strings into positional actions - def consume_positionals(start_index): - # match as many Positionals as possible - match_partial = self._match_arguments_partial - selected_pattern = arg_strings_pattern[start_index:] - arg_counts = match_partial(positionals, selected_pattern) - - # slice off the appropriate arg strings for each Positional - # and add the Positional and its args to the list - for action, arg_count in zip(positionals, arg_counts): - args = arg_strings[start_index: start_index + arg_count] - start_index += arg_count - take_action(action, args) - - # slice off the Positionals that we just parsed and return the - # index at which the Positionals' string args stopped - positionals[:] = positionals[len(arg_counts):] - return start_index - - # consume Positionals and Optionals alternately, until we have - # passed the last option string - start_index = 0 - if option_string_indices: - max_option_string_index = max(option_string_indices) - else: - max_option_string_index = -1 - while start_index <= max_option_string_index: - - # consume any Positionals preceding the next option - next_option_string_index = min( - index - for index in option_string_indices - if index >= start_index) - if start_index != next_option_string_index: - positionals_end_index = consume_positionals(start_index) - - # only try to parse the next optional if we didn't consume - # the option string during the positionals parsing - if positionals_end_index > start_index: - start_index = positionals_end_index - continue - else: - start_index = positionals_end_index - - # if we consumed all the positionals we could and we're not - # at the index of an option string, there were unparseable - # arguments - if start_index not in option_string_indices: - msg = _('extra arguments found: %s') - extras = arg_strings[start_index:next_option_string_index] - self.error(msg % ' '.join(extras)) - - # consume the next optional and any arguments for it - start_index = consume_optional(start_index) - - # consume any positionals following the last Optional - stop_index = consume_positionals(start_index) - - # if we didn't consume all the argument strings, there were too - # many supplied - if stop_index != len(arg_strings): - extras = arg_strings[stop_index:] - self.error(_('extra arguments found: %s') % ' '.join(extras)) - - # if we didn't use all the Positional objects, there were too few - # arg strings supplied. - if positionals: - self.error(_('too few arguments')) - - # return the updated namespace - return namespace - - def _match_argument(self, action, arg_strings_pattern): - # match the pattern for this action to the arg strings - nargs_pattern = self._get_nargs_pattern(action) - match = _re.match(nargs_pattern, arg_strings_pattern) - - # raise an exception if we weren't able to find a match - if match is None: - nargs_errors = { - None:_('expected one argument'), - OPTIONAL:_('expected at most one argument'), - ONE_OR_MORE:_('expected at least one argument') - } - default = _('expected %s argument(s)') % action.nargs - msg = nargs_errors.get(action.nargs, default) - raise ArgumentError(action, msg) - - # return the number of arguments matched - return len(match.group(1)) - - def _match_arguments_partial(self, actions, arg_strings_pattern): - # progressively shorten the actions list by slicing off the - # final actions until we find a match - result = [] - for i in xrange(len(actions), 0, -1): - actions_slice = actions[:i] - pattern = ''.join(self._get_nargs_pattern(action) - for action in actions_slice) - match = _re.match(pattern, arg_strings_pattern) - if match is not None: - result.extend(len(string) for string in match.groups()) - break - - # return the list of arg string counts - return result - - def _parse_optional(self, arg_string): - # if it doesn't start with a prefix, it was meant to be positional - if not arg_string[0] in self.prefix_chars: - return None - - # if it's just dashes, it was meant to be positional - if not arg_string.strip('-'): - return None - - # if the option string is present in the parser, return the action - if arg_string in self._option_strings: - action = self._option_strings[arg_string] - return action, arg_string, None - - # search through all possible prefixes of the option string - # and all actions in the parser for possible interpretations - option_tuples = [] - prefix_tuples = self._get_option_prefix_tuples(arg_string) - for option_string in self._option_strings: - for option_prefix, explicit_arg in prefix_tuples: - if option_string.startswith(option_prefix): - action = self._option_strings[option_string] - tup = action, option_string, explicit_arg - option_tuples.append(tup) - break - - # if multiple actions match, the option string was ambiguous - if len(option_tuples) > 1: - options = ', '.join(opt_str for _, opt_str, _ in option_tuples) - tup = arg_string, options - self.error(_('ambiguous option: %s could match %s') % tup) - - # if exactly one action matched, this segmentation is good, - # so return the parsed action - elif len(option_tuples) == 1: - option_tuple, = option_tuples - return option_tuple - - # if it was not found as an option, but it looks like a negative - # number, it was meant to be positional - if self._negative_number_matcher.match(arg_string): - return None - - # it was meant to be an optional but there is no such option - # in this parser (though it might be a valid option in a subparser) - return None, arg_string, None - - def _get_option_prefix_tuples(self, option_string): - result = [] - - # option strings starting with two prefix characters are only - # split at the '=' - chars = self.prefix_chars - if option_string[0] in chars and option_string[1] in chars: - if '=' in option_string: - option_prefix, explicit_arg = option_string.split('=', 1) - else: - option_prefix = option_string - explicit_arg = None - tup = option_prefix, explicit_arg - result.append(tup) - - # option strings starting with a single prefix character are - # split at all indices - else: - for first_index, char in enumerate(option_string): - if char not in self.prefix_chars: - break - for i in xrange(len(option_string), first_index, -1): - tup = option_string[:i], option_string[i:] or None - result.append(tup) - - # return the collected prefix tuples - return result - - def _get_nargs_pattern(self, action): - # in all examples below, we have to allow for '--' args - # which are represented as '-' in the pattern - nargs = action.nargs - - # the default (None) is assumed to be a single argument - if nargs is None: - nargs_pattern = '(-*A-*)' - - # allow zero or one arguments - elif nargs == OPTIONAL: - nargs_pattern = '(-*A?-*)' - - # allow zero or more arguments - elif nargs == ZERO_OR_MORE: - nargs_pattern = '(-*[A-]*)' - - # allow one or more arguments - elif nargs == ONE_OR_MORE: - nargs_pattern = '(-*A[A-]*)' - - # allow one argument followed by any number of options or arguments - elif nargs is PARSER: - nargs_pattern = '(-*A[-AO]*)' - - # all others should be integers - else: - nargs_pattern = '(-*%s-*)' % '-*'.join('A' * nargs) - - # if this is an optional action, -- is not allowed - if action.option_strings: - nargs_pattern = nargs_pattern.replace('-*', '') - nargs_pattern = nargs_pattern.replace('-', '') - - # return the pattern - return nargs_pattern - - # ======================== - # Value conversion methods - # ======================== - - def _get_values(self, action, arg_strings): - # for everything but PARSER args, strip out '--' - if action.nargs is not PARSER: - arg_strings = [s for s in arg_strings if s != '--'] - - # optional argument produces a default when not present - if not arg_strings and action.nargs == OPTIONAL: - if action.option_strings: - value = action.const - else: - value = action.default - if isinstance(value, basestring): - value = self._get_value(action, value) - self._check_value(action, value) - - # when nargs='*' on a positional, if there were no command-line - # args, use the default if it is anything other than None - elif (not arg_strings and action.nargs == ZERO_OR_MORE and - not action.option_strings): - if action.default is not None: - value = action.default - else: - value = arg_strings - self._check_value(action, value) - - # single argument or optional argument produces a single value - elif len(arg_strings) == 1 and action.nargs in [None, OPTIONAL]: - arg_string, = arg_strings - value = self._get_value(action, arg_string) - self._check_value(action, value) - - # PARSER arguments convert all values, but check only the first - elif action.nargs is PARSER: - value = list(self._get_value(action, v) for v in arg_strings) - self._check_value(action, value[0]) - - # all other types of nargs produce a list - else: - value = list(self._get_value(action, v) for v in arg_strings) - for v in value: - self._check_value(action, v) - - # return the converted value - return value - - def _get_value(self, action, arg_string): - type_func = self._registry_get('type', action.type, action.type) - if not callable(type_func): - msg = _('%r is not callable') - raise ArgumentError(action, msg % type_func) - - # convert the value to the appropriate type - try: - result = type_func(arg_string) - - # TypeErrors or ValueErrors indicate errors - except (TypeError, ValueError): - name = getattr(action.type, '__name__', repr(action.type)) - msg = _('invalid %s value: %r') - raise ArgumentError(action, msg % (name, arg_string)) - - # return the converted value - return result - - def _check_value(self, action, value): - # converted value must be one of the choices (if specified) - if action.choices is not None and value not in action.choices: - tup = value, ', '.join(map(repr, action.choices)) - msg = _('invalid choice: %r (choose from %s)') % tup - raise ArgumentError(action, msg) - - - - # ======================= - # Help-formatting methods - # ======================= - - def format_usage(self): - formatter = self._get_formatter() - formatter.add_usage(self.usage, - self._get_optional_actions(), - self._get_positional_actions()) - return formatter.format_help() - - def format_help(self): - formatter = self._get_formatter() - - # usage - formatter.add_usage(self.usage, - self._get_optional_actions(), - self._get_positional_actions()) - - # description - formatter.add_text(self.description) - - # positionals - formatter.start_section(_('positional arguments')) - formatter.add_arguments(self._positional_actions_list) - formatter.end_section() - - # optionals - formatter.start_section(_('optional arguments')) - formatter.add_arguments(self._optional_actions_list) - formatter.end_section() - - # user-defined groups - for argument_group in self._argument_groups: - formatter.start_section(argument_group.title) - formatter.add_text(argument_group.description) - formatter.add_arguments(argument_group._positional_actions_list) - formatter.add_arguments(argument_group._optional_actions_list) - formatter.end_section() - - # epilog - formatter.add_text(self.epilog) - - # determine help from format above - return formatter.format_help() - - def format_version(self): - formatter = self._get_formatter() - formatter.add_text(self.version) - return formatter.format_help() - - def _get_formatter(self): - return self.formatter_class(prog=self.prog) - - # ===================== - # Help-printing methods - # ===================== - - def print_usage(self, file=None): - self._print_message(self.format_usage(), file) - - def print_help(self, file=None): - self._print_message(self.format_help(), file) - - def print_version(self, file=None): - self._print_message(self.format_version(), file) - - def _print_message(self, message, file=None): - if message: - if file is None: - file = _sys.stderr - file.write(message) - - - # =============== - # Exiting methods - # =============== - - def exit(self, status=0, message=None): - if message: - _sys.stderr.write(message) - _sys.exit(status) - - def error(self, message): - """error(message: string) - - Prints a usage message incorporating the message to stderr and - exits. - - If you override this in a subclass, it should not return -- it - should either exit or raise an exception. - """ - self.print_usage(_sys.stderr) - self.exit(2, _('%s: error: %s\n') % (self.prog, message)) +# -*- coding: utf-8 -*- + +# Copyright © 2006-2009 Steven J. Bethard . +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +"""Command-line parsing library + +This module is an optparse-inspired command-line parsing library that: + + - handles both optional and positional arguments + - produces highly informative usage messages + - supports parsers that dispatch to sub-parsers + +The following is a simple usage example that sums integers from the +command-line and writes the result to a file:: + + parser = argparse.ArgumentParser( + description='sum the integers at the command line') + parser.add_argument( + 'integers', metavar='int', nargs='+', type=int, + help='an integer to be summed') + parser.add_argument( + '--log', default=sys.stdout, type=argparse.FileType('w'), + help='the file where the sum should be written') + args = parser.parse_args() + args.log.write('%s' % sum(args.integers)) + args.log.close() + +The module contains the following public classes: + + - ArgumentParser -- The main entry point for command-line parsing. As the + example above shows, the add_argument() method is used to populate + the parser with actions for optional and positional arguments. Then + the parse_args() method is invoked to convert the args at the + command-line into an object with attributes. + + - ArgumentError -- The exception raised by ArgumentParser objects when + there are errors with the parser's actions. Errors raised while + parsing the command-line are caught by ArgumentParser and emitted + as command-line messages. + + - FileType -- A factory for defining types of files to be created. As the + example above shows, instances of FileType are typically passed as + the type= argument of add_argument() calls. + + - Action -- The base class for parser actions. Typically actions are + selected by passing strings like 'store_true' or 'append_const' to + the action= argument of add_argument(). However, for greater + customization of ArgumentParser actions, subclasses of Action may + be defined and passed as the action= argument. + + - HelpFormatter, RawDescriptionHelpFormatter, RawTextHelpFormatter, + ArgumentDefaultsHelpFormatter -- Formatter classes which + may be passed as the formatter_class= argument to the + ArgumentParser constructor. HelpFormatter is the default, + RawDescriptionHelpFormatter and RawTextHelpFormatter tell the parser + not to change the formatting for help text, and + ArgumentDefaultsHelpFormatter adds information about argument defaults + to the help. + +All other classes in this module are considered implementation details. +(Also note that HelpFormatter and RawDescriptionHelpFormatter are only +considered public as object names -- the API of the formatter objects is +still considered an implementation detail.) +""" + +__version__ = '1.0.1' +__all__ = [ + 'ArgumentParser', + 'ArgumentError', + 'Namespace', + 'Action', + 'FileType', + 'HelpFormatter', + 'RawDescriptionHelpFormatter', + 'RawTextHelpFormatter' + 'ArgumentDefaultsHelpFormatter', +] + + +import copy as _copy +import os as _os +import re as _re +import sys as _sys +import textwrap as _textwrap + +from gettext import gettext as _ + +try: + _set = set +except NameError: + from sets import Set as _set + +try: + _basestring = basestring +except NameError: + _basestring = str + +try: + _sorted = sorted +except NameError: + + def _sorted(iterable, reverse=False): + result = list(iterable) + result.sort() + if reverse: + result.reverse() + return result + +# silence Python 2.6 buggy warnings about Exception.message +if _sys.version_info[:2] == (2, 6): + import warnings + warnings.filterwarnings( + action='ignore', + message='BaseException.message has been deprecated as of Python 2.6', + category=DeprecationWarning, + module='argparse') + + +SUPPRESS = '==SUPPRESS==' + +OPTIONAL = '?' +ZERO_OR_MORE = '*' +ONE_OR_MORE = '+' +PARSER = '==PARSER==' + +# ============================= +# Utility functions and classes +# ============================= + +class _AttributeHolder(object): + """Abstract base class that provides __repr__. + + The __repr__ method returns a string in the format:: + ClassName(attr=name, attr=name, ...) + The attributes are determined either by a class-level attribute, + '_kwarg_names', or by inspecting the instance __dict__. + """ + + def __repr__(self): + type_name = type(self).__name__ + arg_strings = [] + for arg in self._get_args(): + arg_strings.append(repr(arg)) + for name, value in self._get_kwargs(): + arg_strings.append('%s=%r' % (name, value)) + return '%s(%s)' % (type_name, ', '.join(arg_strings)) + + def _get_kwargs(self): + return _sorted(self.__dict__.items()) + + def _get_args(self): + return [] + + +def _ensure_value(namespace, name, value): + if getattr(namespace, name, None) is None: + setattr(namespace, name, value) + return getattr(namespace, name) + + +# =============== +# Formatting Help +# =============== + +class HelpFormatter(object): + """Formatter for generating usage messages and argument help strings. + + Only the name of this class is considered a public API. All the methods + provided by the class are considered an implementation detail. + """ + + def __init__(self, + prog, + indent_increment=2, + max_help_position=24, + width=None): + + # default setting for width + if width is None: + try: + width = int(_os.environ['COLUMNS']) + except (KeyError, ValueError): + width = 80 + width -= 2 + + self._prog = prog + self._indent_increment = indent_increment + self._max_help_position = max_help_position + self._width = width + + self._current_indent = 0 + self._level = 0 + self._action_max_length = 0 + + self._root_section = self._Section(self, None) + self._current_section = self._root_section + + self._whitespace_matcher = _re.compile(r'\s+') + self._long_break_matcher = _re.compile(r'\n\n\n+') + + # =============================== + # Section and indentation methods + # =============================== + def _indent(self): + self._current_indent += self._indent_increment + self._level += 1 + + def _dedent(self): + self._current_indent -= self._indent_increment + assert self._current_indent >= 0, 'Indent decreased below 0.' + self._level -= 1 + + class _Section(object): + + def __init__(self, formatter, parent, heading=None): + self.formatter = formatter + self.parent = parent + self.heading = heading + self.items = [] + + def format_help(self): + # format the indented section + if self.parent is not None: + self.formatter._indent() + join = self.formatter._join_parts + for func, args in self.items: + func(*args) + item_help = join([func(*args) for func, args in self.items]) + if self.parent is not None: + self.formatter._dedent() + + # return nothing if the section was empty + if not item_help: + return '' + + # add the heading if the section was non-empty + if self.heading is not SUPPRESS and self.heading is not None: + current_indent = self.formatter._current_indent + heading = '%*s%s:\n' % (current_indent, '', self.heading) + else: + heading = '' + + # join the section-initial newline, the heading and the help + return join(['\n', heading, item_help, '\n']) + + def _add_item(self, func, args): + self._current_section.items.append((func, args)) + + # ======================== + # Message building methods + # ======================== + def start_section(self, heading): + self._indent() + section = self._Section(self, self._current_section, heading) + self._add_item(section.format_help, []) + self._current_section = section + + def end_section(self): + self._current_section = self._current_section.parent + self._dedent() + + def add_text(self, text): + if text is not SUPPRESS and text is not None: + self._add_item(self._format_text, [text]) + + def add_usage(self, usage, actions, groups, prefix=None): + if usage is not SUPPRESS: + args = usage, actions, groups, prefix + self._add_item(self._format_usage, args) + + def add_argument(self, action): + if action.help is not SUPPRESS: + + # find all invocations + get_invocation = self._format_action_invocation + invocations = [get_invocation(action)] + for subaction in self._iter_indented_subactions(action): + invocations.append(get_invocation(subaction)) + + # update the maximum item length + invocation_length = max([len(s) for s in invocations]) + action_length = invocation_length + self._current_indent + self._action_max_length = max(self._action_max_length, + action_length) + + # add the item to the list + self._add_item(self._format_action, [action]) + + def add_arguments(self, actions): + for action in actions: + self.add_argument(action) + + # ======================= + # Help-formatting methods + # ======================= + def format_help(self): + help = self._root_section.format_help() + if help: + help = self._long_break_matcher.sub('\n\n', help) + help = help.strip('\n') + '\n' + return help + + def _join_parts(self, part_strings): + return ''.join([part + for part in part_strings + if part and part is not SUPPRESS]) + + def _format_usage(self, usage, actions, groups, prefix): + if prefix is None: + prefix = _('usage: ') + + # if usage is specified, use that + if usage is not None: + usage = usage % dict(prog=self._prog) + + # if no optionals or positionals are available, usage is just prog + elif usage is None and not actions: + usage = '%(prog)s' % dict(prog=self._prog) + + # if optionals and positionals are available, calculate usage + elif usage is None: + prog = '%(prog)s' % dict(prog=self._prog) + + # split optionals from positionals + optionals = [] + positionals = [] + for action in actions: + if action.option_strings: + optionals.append(action) + else: + positionals.append(action) + + # build full usage string + format = self._format_actions_usage + action_usage = format(optionals + positionals, groups) + usage = ' '.join([s for s in [prog, action_usage] if s]) + + # wrap the usage parts if it's too long + text_width = self._width - self._current_indent + if len(prefix) + len(usage) > text_width: + + # break usage into wrappable parts + part_regexp = r'\(.*?\)+|\[.*?\]+|\S+' + opt_usage = format(optionals, groups) + pos_usage = format(positionals, groups) + opt_parts = _re.findall(part_regexp, opt_usage) + pos_parts = _re.findall(part_regexp, pos_usage) + assert ' '.join(opt_parts) == opt_usage + assert ' '.join(pos_parts) == pos_usage + + # helper for wrapping lines + def get_lines(parts, indent, prefix=None): + lines = [] + line = [] + if prefix is not None: + line_len = len(prefix) - 1 + else: + line_len = len(indent) - 1 + for part in parts: + if line_len + 1 + len(part) > text_width: + lines.append(indent + ' '.join(line)) + line = [] + line_len = len(indent) - 1 + line.append(part) + line_len += len(part) + 1 + if line: + lines.append(indent + ' '.join(line)) + if prefix is not None: + lines[0] = lines[0][len(indent):] + return lines + + # if prog is short, follow it with optionals or positionals + if len(prefix) + len(prog) <= 0.75 * text_width: + indent = ' ' * (len(prefix) + len(prog) + 1) + if opt_parts: + lines = get_lines([prog] + opt_parts, indent, prefix) + lines.extend(get_lines(pos_parts, indent)) + elif pos_parts: + lines = get_lines([prog] + pos_parts, indent, prefix) + else: + lines = [prog] + + # if prog is long, put it on its own line + else: + indent = ' ' * len(prefix) + parts = opt_parts + pos_parts + lines = get_lines(parts, indent) + if len(lines) > 1: + lines = [] + lines.extend(get_lines(opt_parts, indent)) + lines.extend(get_lines(pos_parts, indent)) + lines = [prog] + lines + + # join lines into usage + usage = '\n'.join(lines) + + # prefix with 'usage:' + return '%s%s\n\n' % (prefix, usage) + + def _format_actions_usage(self, actions, groups): + # find group indices and identify actions in groups + group_actions = _set() + inserts = {} + for group in groups: + try: + start = actions.index(group._group_actions[0]) + except ValueError: + continue + else: + end = start + len(group._group_actions) + if actions[start:end] == group._group_actions: + for action in group._group_actions: + group_actions.add(action) + if not group.required: + inserts[start] = '[' + inserts[end] = ']' + else: + inserts[start] = '(' + inserts[end] = ')' + for i in range(start + 1, end): + inserts[i] = '|' + + # collect all actions format strings + parts = [] + for i, action in enumerate(actions): + + # suppressed arguments are marked with None + # remove | separators for suppressed arguments + if action.help is SUPPRESS: + parts.append(None) + if inserts.get(i) == '|': + inserts.pop(i) + elif inserts.get(i + 1) == '|': + inserts.pop(i + 1) + + # produce all arg strings + elif not action.option_strings: + part = self._format_args(action, action.dest) + + # if it's in a group, strip the outer [] + if action in group_actions: + if part[0] == '[' and part[-1] == ']': + part = part[1:-1] + + # add the action string to the list + parts.append(part) + + # produce the first way to invoke the option in brackets + else: + option_string = action.option_strings[0] + + # if the Optional doesn't take a value, format is: + # -s or --long + if action.nargs == 0: + part = '%s' % option_string + + # if the Optional takes a value, format is: + # -s ARGS or --long ARGS + else: + default = action.dest.upper() + args_string = self._format_args(action, default) + part = '%s %s' % (option_string, args_string) + + # make it look optional if it's not required or in a group + if not action.required and action not in group_actions: + part = '[%s]' % part + + # add the action string to the list + parts.append(part) + + # insert things at the necessary indices + for i in _sorted(inserts, reverse=True): + parts[i:i] = [inserts[i]] + + # join all the action items with spaces + text = ' '.join([item for item in parts if item is not None]) + + # clean up separators for mutually exclusive groups + open = r'[\[(]' + close = r'[\])]' + text = _re.sub(r'(%s) ' % open, r'\1', text) + text = _re.sub(r' (%s)' % close, r'\1', text) + text = _re.sub(r'%s *%s' % (open, close), r'', text) + text = _re.sub(r'\(([^|]*)\)', r'\1', text) + text = text.strip() + + # return the text + return text + + def _format_text(self, text): + text_width = self._width - self._current_indent + indent = ' ' * self._current_indent + return self._fill_text(text, text_width, indent) + '\n\n' + + def _format_action(self, action): + # determine the required width and the entry label + help_position = min(self._action_max_length + 2, + self._max_help_position) + help_width = self._width - help_position + action_width = help_position - self._current_indent - 2 + action_header = self._format_action_invocation(action) + + # ho nelp; start on same line and add a final newline + if not action.help: + tup = self._current_indent, '', action_header + action_header = '%*s%s\n' % tup + + # short action name; start on the same line and pad two spaces + elif len(action_header) <= action_width: + tup = self._current_indent, '', action_width, action_header + action_header = '%*s%-*s ' % tup + indent_first = 0 + + # long action name; start on the next line + else: + tup = self._current_indent, '', action_header + action_header = '%*s%s\n' % tup + indent_first = help_position + + # collect the pieces of the action help + parts = [action_header] + + # if there was help for the action, add lines of help text + if action.help: + help_text = self._expand_help(action) + help_lines = self._split_lines(help_text, help_width) + parts.append('%*s%s\n' % (indent_first, '', help_lines[0])) + for line in help_lines[1:]: + parts.append('%*s%s\n' % (help_position, '', line)) + + # or add a newline if the description doesn't end with one + elif not action_header.endswith('\n'): + parts.append('\n') + + # if there are any sub-actions, add their help as well + for subaction in self._iter_indented_subactions(action): + parts.append(self._format_action(subaction)) + + # return a single string + return self._join_parts(parts) + + def _format_action_invocation(self, action): + if not action.option_strings: + metavar, = self._metavar_formatter(action, action.dest)(1) + return metavar + + else: + parts = [] + + # if the Optional doesn't take a value, format is: + # -s, --long + if action.nargs == 0: + parts.extend(action.option_strings) + + # if the Optional takes a value, format is: + # -s ARGS, --long ARGS + else: + default = action.dest.upper() + args_string = self._format_args(action, default) + for option_string in action.option_strings: + parts.append('%s %s' % (option_string, args_string)) + + return ', '.join(parts) + + def _metavar_formatter(self, action, default_metavar): + if action.metavar is not None: + result = action.metavar + elif action.choices is not None: + choice_strs = [str(choice) for choice in action.choices] + result = '{%s}' % ','.join(choice_strs) + else: + result = default_metavar + + def format(tuple_size): + if isinstance(result, tuple): + return result + else: + return (result, ) * tuple_size + return format + + def _format_args(self, action, default_metavar): + get_metavar = self._metavar_formatter(action, default_metavar) + if action.nargs is None: + result = '%s' % get_metavar(1) + elif action.nargs == OPTIONAL: + result = '[%s]' % get_metavar(1) + elif action.nargs == ZERO_OR_MORE: + result = '[%s [%s ...]]' % get_metavar(2) + elif action.nargs == ONE_OR_MORE: + result = '%s [%s ...]' % get_metavar(2) + elif action.nargs is PARSER: + result = '%s ...' % get_metavar(1) + else: + formats = ['%s' for _ in range(action.nargs)] + result = ' '.join(formats) % get_metavar(action.nargs) + return result + + def _expand_help(self, action): + params = dict(vars(action), prog=self._prog) + for name in list(params): + if params[name] is SUPPRESS: + del params[name] + if params.get('choices') is not None: + choices_str = ', '.join([str(c) for c in params['choices']]) + params['choices'] = choices_str + return self._get_help_string(action) % params + + def _iter_indented_subactions(self, action): + try: + get_subactions = action._get_subactions + except AttributeError: + pass + else: + self._indent() + for subaction in get_subactions(): + yield subaction + self._dedent() + + def _split_lines(self, text, width): + text = self._whitespace_matcher.sub(' ', text).strip() + return _textwrap.wrap(text, width) + + def _fill_text(self, text, width, indent): + text = self._whitespace_matcher.sub(' ', text).strip() + return _textwrap.fill(text, width, initial_indent=indent, + subsequent_indent=indent) + + def _get_help_string(self, action): + return action.help + + +class RawDescriptionHelpFormatter(HelpFormatter): + """Help message formatter which retains any formatting in descriptions. + + Only the name of this class is considered a public API. All the methods + provided by the class are considered an implementation detail. + """ + + def _fill_text(self, text, width, indent): + return ''.join([indent + line for line in text.splitlines(True)]) + + +class RawTextHelpFormatter(RawDescriptionHelpFormatter): + """Help message formatter which retains formatting of all help text. + + Only the name of this class is considered a public API. All the methods + provided by the class are considered an implementation detail. + """ + + def _split_lines(self, text, width): + return text.splitlines() + + +class ArgumentDefaultsHelpFormatter(HelpFormatter): + """Help message formatter which adds default values to argument help. + + Only the name of this class is considered a public API. All the methods + provided by the class are considered an implementation detail. + """ + + def _get_help_string(self, action): + help = action.help + if '%(default)' not in action.help: + if action.default is not SUPPRESS: + defaulting_nargs = [OPTIONAL, ZERO_OR_MORE] + if action.option_strings or action.nargs in defaulting_nargs: + help += ' (default: %(default)s)' + return help + + +# ===================== +# Options and Arguments +# ===================== + +def _get_action_name(argument): + if argument is None: + return None + elif argument.option_strings: + return '/'.join(argument.option_strings) + elif argument.metavar not in (None, SUPPRESS): + return argument.metavar + elif argument.dest not in (None, SUPPRESS): + return argument.dest + else: + return None + + +class ArgumentError(Exception): + """An error from creating or using an argument (optional or positional). + + The string value of this exception is the message, augmented with + information about the argument that caused it. + """ + + def __init__(self, argument, message): + self.argument_name = _get_action_name(argument) + self.message = message + + def __str__(self): + if self.argument_name is None: + format = '%(message)s' + else: + format = 'argument %(argument_name)s: %(message)s' + return format % dict(message=self.message, + argument_name=self.argument_name) + +# ============== +# Action classes +# ============== + +class Action(_AttributeHolder): + """Information about how to convert command line strings to Python objects. + + Action objects are used by an ArgumentParser to represent the information + needed to parse a single argument from one or more strings from the + command line. The keyword arguments to the Action constructor are also + all attributes of Action instances. + + Keyword Arguments: + + - option_strings -- A list of command-line option strings which + should be associated with this action. + + - dest -- The name of the attribute to hold the created object(s) + + - nargs -- The number of command-line arguments that should be + consumed. By default, one argument will be consumed and a single + value will be produced. Other values include: + - N (an integer) consumes N arguments (and produces a list) + - '?' consumes zero or one arguments + - '*' consumes zero or more arguments (and produces a list) + - '+' consumes one or more arguments (and produces a list) + Note that the difference between the default and nargs=1 is that + with the default, a single value will be produced, while with + nargs=1, a list containing a single value will be produced. + + - const -- The value to be produced if the option is specified and the + option uses an action that takes no values. + + - default -- The value to be produced if the option is not specified. + + - type -- The type which the command-line arguments should be converted + to, should be one of 'string', 'int', 'float', 'complex' or a + callable object that accepts a single string argument. If None, + 'string' is assumed. + + - choices -- A container of values that should be allowed. If not None, + after a command-line argument has been converted to the appropriate + type, an exception will be raised if it is not a member of this + collection. + + - required -- True if the action must always be specified at the + command line. This is only meaningful for optional command-line + arguments. + + - help -- The help string describing the argument. + + - metavar -- The name to be used for the option's argument with the + help string. If None, the 'dest' value will be used as the name. + """ + + def __init__(self, + option_strings, + dest, + nargs=None, + const=None, + default=None, + type=None, + choices=None, + required=False, + help=None, + metavar=None): + self.option_strings = option_strings + self.dest = dest + self.nargs = nargs + self.const = const + self.default = default + self.type = type + self.choices = choices + self.required = required + self.help = help + self.metavar = metavar + + def _get_kwargs(self): + names = [ + 'option_strings', + 'dest', + 'nargs', + 'const', + 'default', + 'type', + 'choices', + 'help', + 'metavar', + ] + return [(name, getattr(self, name)) for name in names] + + def __call__(self, parser, namespace, values, option_string=None): + raise NotImplementedError(_('.__call__() not defined')) + + +class _StoreAction(Action): + + def __init__(self, + option_strings, + dest, + nargs=None, + const=None, + default=None, + type=None, + choices=None, + required=False, + help=None, + metavar=None): + if nargs == 0: + raise ValueError('nargs for store actions must be > 0; if you ' + 'have nothing to store, actions such as store ' + 'true or store const may be more appropriate') + if const is not None and nargs != OPTIONAL: + raise ValueError('nargs must be %r to supply const' % OPTIONAL) + super(_StoreAction, self).__init__( + option_strings=option_strings, + dest=dest, + nargs=nargs, + const=const, + default=default, + type=type, + choices=choices, + required=required, + help=help, + metavar=metavar) + + def __call__(self, parser, namespace, values, option_string=None): + setattr(namespace, self.dest, values) + + +class _StoreConstAction(Action): + + def __init__(self, + option_strings, + dest, + const, + default=None, + required=False, + help=None, + metavar=None): + super(_StoreConstAction, self).__init__( + option_strings=option_strings, + dest=dest, + nargs=0, + const=const, + default=default, + required=required, + help=help) + + def __call__(self, parser, namespace, values, option_string=None): + setattr(namespace, self.dest, self.const) + + +class _StoreTrueAction(_StoreConstAction): + + def __init__(self, + option_strings, + dest, + default=False, + required=False, + help=None): + super(_StoreTrueAction, self).__init__( + option_strings=option_strings, + dest=dest, + const=True, + default=default, + required=required, + help=help) + + +class _StoreFalseAction(_StoreConstAction): + + def __init__(self, + option_strings, + dest, + default=True, + required=False, + help=None): + super(_StoreFalseAction, self).__init__( + option_strings=option_strings, + dest=dest, + const=False, + default=default, + required=required, + help=help) + + +class _AppendAction(Action): + + def __init__(self, + option_strings, + dest, + nargs=None, + const=None, + default=None, + type=None, + choices=None, + required=False, + help=None, + metavar=None): + if nargs == 0: + raise ValueError('nargs for append actions must be > 0; if arg ' + 'strings are not supplying the value to append, ' + 'the append const action may be more appropriate') + if const is not None and nargs != OPTIONAL: + raise ValueError('nargs must be %r to supply const' % OPTIONAL) + super(_AppendAction, self).__init__( + option_strings=option_strings, + dest=dest, + nargs=nargs, + const=const, + default=default, + type=type, + choices=choices, + required=required, + help=help, + metavar=metavar) + + def __call__(self, parser, namespace, values, option_string=None): + items = _copy.copy(_ensure_value(namespace, self.dest, [])) + items.append(values) + setattr(namespace, self.dest, items) + + +class _AppendConstAction(Action): + + def __init__(self, + option_strings, + dest, + const, + default=None, + required=False, + help=None, + metavar=None): + super(_AppendConstAction, self).__init__( + option_strings=option_strings, + dest=dest, + nargs=0, + const=const, + default=default, + required=required, + help=help, + metavar=metavar) + + def __call__(self, parser, namespace, values, option_string=None): + items = _copy.copy(_ensure_value(namespace, self.dest, [])) + items.append(self.const) + setattr(namespace, self.dest, items) + + +class _CountAction(Action): + + def __init__(self, + option_strings, + dest, + default=None, + required=False, + help=None): + super(_CountAction, self).__init__( + option_strings=option_strings, + dest=dest, + nargs=0, + default=default, + required=required, + help=help) + + def __call__(self, parser, namespace, values, option_string=None): + new_count = _ensure_value(namespace, self.dest, 0) + 1 + setattr(namespace, self.dest, new_count) + + +class _HelpAction(Action): + + def __init__(self, + option_strings, + dest=SUPPRESS, + default=SUPPRESS, + help=None): + super(_HelpAction, self).__init__( + option_strings=option_strings, + dest=dest, + default=default, + nargs=0, + help=help) + + def __call__(self, parser, namespace, values, option_string=None): + parser.print_help() + parser.exit() + + +class _VersionAction(Action): + + def __init__(self, + option_strings, + dest=SUPPRESS, + default=SUPPRESS, + help=None): + super(_VersionAction, self).__init__( + option_strings=option_strings, + dest=dest, + default=default, + nargs=0, + help=help) + + def __call__(self, parser, namespace, values, option_string=None): + parser.print_version() + parser.exit() + + +class _SubParsersAction(Action): + + class _ChoicesPseudoAction(Action): + + def __init__(self, name, help): + sup = super(_SubParsersAction._ChoicesPseudoAction, self) + sup.__init__(option_strings=[], dest=name, help=help) + + def __init__(self, + option_strings, + prog, + parser_class, + dest=SUPPRESS, + help=None, + metavar=None): + + self._prog_prefix = prog + self._parser_class = parser_class + self._name_parser_map = {} + self._choices_actions = [] + + super(_SubParsersAction, self).__init__( + option_strings=option_strings, + dest=dest, + nargs=PARSER, + choices=self._name_parser_map, + help=help, + metavar=metavar) + + def add_parser(self, name, **kwargs): + # set prog from the existing prefix + if kwargs.get('prog') is None: + kwargs['prog'] = '%s %s' % (self._prog_prefix, name) + + # create a pseudo-action to hold the choice help + if 'help' in kwargs: + help = kwargs.pop('help') + choice_action = self._ChoicesPseudoAction(name, help) + self._choices_actions.append(choice_action) + + # create the parser and add it to the map + parser = self._parser_class(**kwargs) + self._name_parser_map[name] = parser + return parser + + def _get_subactions(self): + return self._choices_actions + + def __call__(self, parser, namespace, values, option_string=None): + parser_name = values[0] + arg_strings = values[1:] + + # set the parser name if requested + if self.dest is not SUPPRESS: + setattr(namespace, self.dest, parser_name) + + # select the parser + try: + parser = self._name_parser_map[parser_name] + except KeyError: + tup = parser_name, ', '.join(self._name_parser_map) + msg = _('unknown parser %r (choices: %s)' % tup) + raise ArgumentError(self, msg) + + # parse all the remaining options into the namespace + parser.parse_args(arg_strings, namespace) + + +# ============== +# Type classes +# ============== + +class FileType(object): + """Factory for creating file object types + + Instances of FileType are typically passed as type= arguments to the + ArgumentParser add_argument() method. + + Keyword Arguments: + - mode -- A string indicating how the file is to be opened. Accepts the + same values as the builtin open() function. + - bufsize -- The file's desired buffer size. Accepts the same values as + the builtin open() function. + """ + + def __init__(self, mode='r', bufsize=None): + self._mode = mode + self._bufsize = bufsize + + def __call__(self, string): + # the special argument "-" means sys.std{in,out} + if string == '-': + if 'r' in self._mode: + return _sys.stdin + elif 'w' in self._mode: + return _sys.stdout + else: + msg = _('argument "-" with mode %r' % self._mode) + raise ValueError(msg) + + # all other arguments are used as file names + if self._bufsize: + return open(string, self._mode, self._bufsize) + else: + return open(string, self._mode) + + def __repr__(self): + args = [self._mode, self._bufsize] + args_str = ', '.join([repr(arg) for arg in args if arg is not None]) + return '%s(%s)' % (type(self).__name__, args_str) + +# =========================== +# Optional and Positional Parsing +# =========================== + +class Namespace(_AttributeHolder): + """Simple object for storing attributes. + + Implements equality by attribute names and values, and provides a simple + string representation. + """ + + def __init__(self, **kwargs): + for name in kwargs: + setattr(self, name, kwargs[name]) + + def __eq__(self, other): + return vars(self) == vars(other) + + def __ne__(self, other): + return not (self == other) + + +class _ActionsContainer(object): + + def __init__(self, + description, + prefix_chars, + argument_default, + conflict_handler): + super(_ActionsContainer, self).__init__() + + self.description = description + self.argument_default = argument_default + self.prefix_chars = prefix_chars + self.conflict_handler = conflict_handler + + # set up registries + self._registries = {} + + # register actions + self.register('action', None, _StoreAction) + self.register('action', 'store', _StoreAction) + self.register('action', 'store_const', _StoreConstAction) + self.register('action', 'store_true', _StoreTrueAction) + self.register('action', 'store_false', _StoreFalseAction) + self.register('action', 'append', _AppendAction) + self.register('action', 'append_const', _AppendConstAction) + self.register('action', 'count', _CountAction) + self.register('action', 'help', _HelpAction) + self.register('action', 'version', _VersionAction) + self.register('action', 'parsers', _SubParsersAction) + + # raise an exception if the conflict handler is invalid + self._get_handler() + + # action storage + self._actions = [] + self._option_string_actions = {} + + # groups + self._action_groups = [] + self._mutually_exclusive_groups = [] + + # defaults storage + self._defaults = {} + + # determines whether an "option" looks like a negative number + self._negative_number_matcher = _re.compile(r'^-\d+|-\d*.\d+$') + + # whether or not there are any optionals that look like negative + # numbers -- uses a list so it can be shared and edited + self._has_negative_number_optionals = [] + + # ==================== + # Registration methods + # ==================== + def register(self, registry_name, value, object): + registry = self._registries.setdefault(registry_name, {}) + registry[value] = object + + def _registry_get(self, registry_name, value, default=None): + return self._registries[registry_name].get(value, default) + + # ================================== + # Namespace default settings methods + # ================================== + def set_defaults(self, **kwargs): + self._defaults.update(kwargs) + + # if these defaults match any existing arguments, replace + # the previous default on the object with the new one + for action in self._actions: + if action.dest in kwargs: + action.default = kwargs[action.dest] + + # ======================= + # Adding argument actions + # ======================= + def add_argument(self, *args, **kwargs): + """ + add_argument(dest, ..., name=value, ...) + add_argument(option_string, option_string, ..., name=value, ...) + """ + + # if no positional args are supplied or only one is supplied and + # it doesn't look like an option string, parse a positional + # argument + chars = self.prefix_chars + if not args or len(args) == 1 and args[0][0] not in chars: + kwargs = self._get_positional_kwargs(*args, **kwargs) + + # otherwise, we're adding an optional argument + else: + kwargs = self._get_optional_kwargs(*args, **kwargs) + + # if no default was supplied, use the parser-level default + if 'default' not in kwargs: + dest = kwargs['dest'] + if dest in self._defaults: + kwargs['default'] = self._defaults[dest] + elif self.argument_default is not None: + kwargs['default'] = self.argument_default + + # create the action object, and add it to the parser + action_class = self._pop_action_class(kwargs) + action = action_class(**kwargs) + return self._add_action(action) + + def add_argument_group(self, *args, **kwargs): + group = _ArgumentGroup(self, *args, **kwargs) + self._action_groups.append(group) + return group + + def add_mutually_exclusive_group(self, **kwargs): + group = _MutuallyExclusiveGroup(self, **kwargs) + self._mutually_exclusive_groups.append(group) + return group + + def _add_action(self, action): + # resolve any conflicts + self._check_conflict(action) + + # add to actions list + self._actions.append(action) + action.container = self + + # index the action by any option strings it has + for option_string in action.option_strings: + self._option_string_actions[option_string] = action + + # set the flag if any option strings look like negative numbers + for option_string in action.option_strings: + if self._negative_number_matcher.match(option_string): + if not self._has_negative_number_optionals: + self._has_negative_number_optionals.append(True) + + # return the created action + return action + + def _remove_action(self, action): + self._actions.remove(action) + + def _add_container_actions(self, container): + # collect groups by titles + title_group_map = {} + for group in self._action_groups: + if group.title in title_group_map: + msg = _('cannot merge actions - two groups are named %r') + raise ValueError(msg % (group.title)) + title_group_map[group.title] = group + + # map each action to its group + group_map = {} + for group in container._action_groups: + + # if a group with the title exists, use that, otherwise + # create a new group matching the container's group + if group.title not in title_group_map: + title_group_map[group.title] = self.add_argument_group( + title=group.title, + description=group.description, + conflict_handler=group.conflict_handler) + + # map the actions to their new group + for action in group._group_actions: + group_map[action] = title_group_map[group.title] + + # add container's mutually exclusive groups + # NOTE: if add_mutually_exclusive_group ever gains title= and + # description= then this code will need to be expanded as above + for group in container._mutually_exclusive_groups: + mutex_group = self.add_mutually_exclusive_group( + required=group.required) + + # map the actions to their new mutex group + for action in group._group_actions: + group_map[action] = mutex_group + + # add all actions to this container or their group + for action in container._actions: + group_map.get(action, self)._add_action(action) + + def _get_positional_kwargs(self, dest, **kwargs): + # make sure required is not specified + if 'required' in kwargs: + msg = _("'required' is an invalid argument for positionals") + raise TypeError(msg) + + # mark positional arguments as required if at least one is + # always required + if kwargs.get('nargs') not in [OPTIONAL, ZERO_OR_MORE]: + kwargs['required'] = True + if kwargs.get('nargs') == ZERO_OR_MORE and 'default' not in kwargs: + kwargs['required'] = True + + # return the keyword arguments with no option strings + return dict(kwargs, dest=dest, option_strings=[]) + + def _get_optional_kwargs(self, *args, **kwargs): + # determine short and long option strings + option_strings = [] + long_option_strings = [] + for option_string in args: + # error on one-or-fewer-character option strings + if len(option_string) < 2: + msg = _('invalid option string %r: ' + 'must be at least two characters long') + raise ValueError(msg % option_string) + + # error on strings that don't start with an appropriate prefix + if not option_string[0] in self.prefix_chars: + msg = _('invalid option string %r: ' + 'must start with a character %r') + tup = option_string, self.prefix_chars + raise ValueError(msg % tup) + + # error on strings that are all prefix characters + if not (_set(option_string) - _set(self.prefix_chars)): + msg = _('invalid option string %r: ' + 'must contain characters other than %r') + tup = option_string, self.prefix_chars + raise ValueError(msg % tup) + + # strings starting with two prefix characters are long options + option_strings.append(option_string) + if option_string[0] in self.prefix_chars: + if option_string[1] in self.prefix_chars: + long_option_strings.append(option_string) + + # infer destination, '--foo-bar' -> 'foo_bar' and '-x' -> 'x' + dest = kwargs.pop('dest', None) + if dest is None: + if long_option_strings: + dest_option_string = long_option_strings[0] + else: + dest_option_string = option_strings[0] + dest = dest_option_string.lstrip(self.prefix_chars) + dest = dest.replace('-', '_') + + # return the updated keyword arguments + return dict(kwargs, dest=dest, option_strings=option_strings) + + def _pop_action_class(self, kwargs, default=None): + action = kwargs.pop('action', default) + return self._registry_get('action', action, action) + + def _get_handler(self): + # determine function from conflict handler string + handler_func_name = '_handle_conflict_%s' % self.conflict_handler + try: + return getattr(self, handler_func_name) + except AttributeError: + msg = _('invalid conflict_resolution value: %r') + raise ValueError(msg % self.conflict_handler) + + def _check_conflict(self, action): + + # find all options that conflict with this option + confl_optionals = [] + for option_string in action.option_strings: + if option_string in self._option_string_actions: + confl_optional = self._option_string_actions[option_string] + confl_optionals.append((option_string, confl_optional)) + + # resolve any conflicts + if confl_optionals: + conflict_handler = self._get_handler() + conflict_handler(action, confl_optionals) + + def _handle_conflict_error(self, action, conflicting_actions): + message = _('conflicting option string(s): %s') + conflict_string = ', '.join([option_string + for option_string, action + in conflicting_actions]) + raise ArgumentError(action, message % conflict_string) + + def _handle_conflict_resolve(self, action, conflicting_actions): + + # remove all conflicting options + for option_string, action in conflicting_actions: + + # remove the conflicting option + action.option_strings.remove(option_string) + self._option_string_actions.pop(option_string, None) + + # if the option now has no option string, remove it from the + # container holding it + if not action.option_strings: + action.container._remove_action(action) + + +class _ArgumentGroup(_ActionsContainer): + + def __init__(self, container, title=None, description=None, **kwargs): + # add any missing keyword arguments by checking the container + update = kwargs.setdefault + update('conflict_handler', container.conflict_handler) + update('prefix_chars', container.prefix_chars) + update('argument_default', container.argument_default) + super_init = super(_ArgumentGroup, self).__init__ + super_init(description=description, **kwargs) + + # group attributes + self.title = title + self._group_actions = [] + + # share most attributes with the container + self._registries = container._registries + self._actions = container._actions + self._option_string_actions = container._option_string_actions + self._defaults = container._defaults + self._has_negative_number_optionals = \ + container._has_negative_number_optionals + + def _add_action(self, action): + action = super(_ArgumentGroup, self)._add_action(action) + self._group_actions.append(action) + return action + + def _remove_action(self, action): + super(_ArgumentGroup, self)._remove_action(action) + self._group_actions.remove(action) + + +class _MutuallyExclusiveGroup(_ArgumentGroup): + + def __init__(self, container, required=False): + super(_MutuallyExclusiveGroup, self).__init__(container) + self.required = required + self._container = container + + def _add_action(self, action): + if action.required: + msg = _('mutually exclusive arguments must be optional') + raise ValueError(msg) + action = self._container._add_action(action) + self._group_actions.append(action) + return action + + def _remove_action(self, action): + self._container._remove_action(action) + self._group_actions.remove(action) + + +class ArgumentParser(_AttributeHolder, _ActionsContainer): + """Object for parsing command line strings into Python objects. + + Keyword Arguments: + - prog -- The name of the program (default: sys.argv[0]) + - usage -- A usage message (default: auto-generated from arguments) + - description -- A description of what the program does + - epilog -- Text following the argument descriptions + - version -- Add a -v/--version option with the given version string + - parents -- Parsers whose arguments should be copied into this one + - formatter_class -- HelpFormatter class for printing help messages + - prefix_chars -- Characters that prefix optional arguments + - fromfile_prefix_chars -- Characters that prefix files containing + additional arguments + - argument_default -- The default value for all arguments + - conflict_handler -- String indicating how to handle conflicts + - add_help -- Add a -h/-help option + """ + + def __init__(self, + prog=None, + usage=None, + description=None, + epilog=None, + version=None, + parents=[], + formatter_class=HelpFormatter, + prefix_chars='-', + fromfile_prefix_chars=None, + argument_default=None, + conflict_handler='error', + add_help=True): + + superinit = super(ArgumentParser, self).__init__ + superinit(description=description, + prefix_chars=prefix_chars, + argument_default=argument_default, + conflict_handler=conflict_handler) + + # default setting for prog + if prog is None: + prog = _os.path.basename(_sys.argv[0]) + + self.prog = prog + self.usage = usage + self.epilog = epilog + self.version = version + self.formatter_class = formatter_class + self.fromfile_prefix_chars = fromfile_prefix_chars + self.add_help = add_help + + add_group = self.add_argument_group + self._positionals = add_group(_('positional arguments')) + self._optionals = add_group(_('optional arguments')) + self._subparsers = None + + # register types + def identity(string): + return string + self.register('type', None, identity) + + # add help and version arguments if necessary + # (using explicit default to override global argument_default) + if self.add_help: + self.add_argument( + '-h', '--help', action='help', default=SUPPRESS, + help=_('show this help message and exit')) + if self.version: + self.add_argument( + '-v', '--version', action='version', default=SUPPRESS, + help=_("show program's version number and exit")) + + # add parent arguments and defaults + for parent in parents: + self._add_container_actions(parent) + try: + defaults = parent._defaults + except AttributeError: + pass + else: + self._defaults.update(defaults) + + # ======================= + # Pretty __repr__ methods + # ======================= + def _get_kwargs(self): + names = [ + 'prog', + 'usage', + 'description', + 'version', + 'formatter_class', + 'conflict_handler', + 'add_help', + ] + return [(name, getattr(self, name)) for name in names] + + # ================================== + # Optional/Positional adding methods + # ================================== + def add_subparsers(self, **kwargs): + if self._subparsers is not None: + self.error(_('cannot have multiple subparser arguments')) + + # add the parser class to the arguments if it's not present + kwargs.setdefault('parser_class', type(self)) + + if 'title' in kwargs or 'description' in kwargs: + title = _(kwargs.pop('title', 'subcommands')) + description = _(kwargs.pop('description', None)) + self._subparsers = self.add_argument_group(title, description) + else: + self._subparsers = self._positionals + + # prog defaults to the usage message of this parser, skipping + # optional arguments and with no "usage:" prefix + if kwargs.get('prog') is None: + formatter = self._get_formatter() + positionals = self._get_positional_actions() + groups = self._mutually_exclusive_groups + formatter.add_usage(self.usage, positionals, groups, '') + kwargs['prog'] = formatter.format_help().strip() + + # create the parsers action and add it to the positionals list + parsers_class = self._pop_action_class(kwargs, 'parsers') + action = parsers_class(option_strings=[], **kwargs) + self._subparsers._add_action(action) + + # return the created parsers action + return action + + def _add_action(self, action): + if action.option_strings: + self._optionals._add_action(action) + else: + self._positionals._add_action(action) + return action + + def _get_optional_actions(self): + return [action + for action in self._actions + if action.option_strings] + + def _get_positional_actions(self): + return [action + for action in self._actions + if not action.option_strings] + + # ===================================== + # Command line argument parsing methods + # ===================================== + def parse_args(self, args=None, namespace=None): + args, argv = self.parse_known_args(args, namespace) + if argv: + msg = _('unrecognized arguments: %s') + self.error(msg % ' '.join(argv)) + return args + + def parse_known_args(self, args=None, namespace=None): + # args default to the system args + if args is None: + args = _sys.argv[1:] + + # default Namespace built from parser defaults + if namespace is None: + namespace = Namespace() + + # add any action defaults that aren't present + for action in self._actions: + if action.dest is not SUPPRESS: + if not hasattr(namespace, action.dest): + if action.default is not SUPPRESS: + default = action.default + if isinstance(action.default, _basestring): + default = self._get_value(action, default) + setattr(namespace, action.dest, default) + + # add any parser defaults that aren't present + for dest in self._defaults: + if not hasattr(namespace, dest): + setattr(namespace, dest, self._defaults[dest]) + + # parse the arguments and exit if there are any errors + try: + return self._parse_known_args(args, namespace) + except ArgumentError: + err = _sys.exc_info()[1] + self.error(str(err)) + + def _parse_known_args(self, arg_strings, namespace): + # replace arg strings that are file references + if self.fromfile_prefix_chars is not None: + arg_strings = self._read_args_from_files(arg_strings) + + # map all mutually exclusive arguments to the other arguments + # they can't occur with + action_conflicts = {} + for mutex_group in self._mutually_exclusive_groups: + group_actions = mutex_group._group_actions + for i, mutex_action in enumerate(mutex_group._group_actions): + conflicts = action_conflicts.setdefault(mutex_action, []) + conflicts.extend(group_actions[:i]) + conflicts.extend(group_actions[i + 1:]) + + # find all option indices, and determine the arg_string_pattern + # which has an 'O' if there is an option at an index, + # an 'A' if there is an argument, or a '-' if there is a '--' + option_string_indices = {} + arg_string_pattern_parts = [] + arg_strings_iter = iter(arg_strings) + for i, arg_string in enumerate(arg_strings_iter): + + # all args after -- are non-options + if arg_string == '--': + arg_string_pattern_parts.append('-') + for arg_string in arg_strings_iter: + arg_string_pattern_parts.append('A') + + # otherwise, add the arg to the arg strings + # and note the index if it was an option + else: + option_tuple = self._parse_optional(arg_string) + if option_tuple is None: + pattern = 'A' + else: + option_string_indices[i] = option_tuple + pattern = 'O' + arg_string_pattern_parts.append(pattern) + + # join the pieces together to form the pattern + arg_strings_pattern = ''.join(arg_string_pattern_parts) + + # converts arg strings to the appropriate and then takes the action + seen_actions = _set() + seen_non_default_actions = _set() + + def take_action(action, argument_strings, option_string=None): + seen_actions.add(action) + argument_values = self._get_values(action, argument_strings) + + # error if this argument is not allowed with other previously + # seen arguments, assuming that actions that use the default + # value don't really count as "present" + if argument_values is not action.default: + seen_non_default_actions.add(action) + for conflict_action in action_conflicts.get(action, []): + if conflict_action in seen_non_default_actions: + msg = _('not allowed with argument %s') + action_name = _get_action_name(conflict_action) + raise ArgumentError(action, msg % action_name) + + # take the action if we didn't receive a SUPPRESS value + # (e.g. from a default) + if argument_values is not SUPPRESS: + action(self, namespace, argument_values, option_string) + + # function to convert arg_strings into an optional action + def consume_optional(start_index): + + # get the optional identified at this index + option_tuple = option_string_indices[start_index] + action, option_string, explicit_arg = option_tuple + + # identify additional optionals in the same arg string + # (e.g. -xyz is the same as -x -y -z if no args are required) + match_argument = self._match_argument + action_tuples = [] + while True: + + # if we found no optional action, skip it + if action is None: + extras.append(arg_strings[start_index]) + return start_index + 1 + + # if there is an explicit argument, try to match the + # optional's string arguments to only this + if explicit_arg is not None: + arg_count = match_argument(action, 'A') + + # if the action is a single-dash option and takes no + # arguments, try to parse more single-dash options out + # of the tail of the option string + chars = self.prefix_chars + if arg_count == 0 and option_string[1] not in chars: + action_tuples.append((action, [], option_string)) + for char in self.prefix_chars: + option_string = char + explicit_arg[0] + explicit_arg = explicit_arg[1:] or None + optionals_map = self._option_string_actions + if option_string in optionals_map: + action = optionals_map[option_string] + break + else: + msg = _('ignored explicit argument %r') + raise ArgumentError(action, msg % explicit_arg) + + # if the action expect exactly one argument, we've + # successfully matched the option; exit the loop + elif arg_count == 1: + stop = start_index + 1 + args = [explicit_arg] + action_tuples.append((action, args, option_string)) + break + + # error if a double-dash option did not use the + # explicit argument + else: + msg = _('ignored explicit argument %r') + raise ArgumentError(action, msg % explicit_arg) + + # if there is no explicit argument, try to match the + # optional's string arguments with the following strings + # if successful, exit the loop + else: + start = start_index + 1 + selected_patterns = arg_strings_pattern[start:] + arg_count = match_argument(action, selected_patterns) + stop = start + arg_count + args = arg_strings[start:stop] + action_tuples.append((action, args, option_string)) + break + + # add the Optional to the list and return the index at which + # the Optional's string args stopped + assert action_tuples + for action, args, option_string in action_tuples: + take_action(action, args, option_string) + return stop + + # the list of Positionals left to be parsed; this is modified + # by consume_positionals() + positionals = self._get_positional_actions() + + # function to convert arg_strings into positional actions + def consume_positionals(start_index): + # match as many Positionals as possible + match_partial = self._match_arguments_partial + selected_pattern = arg_strings_pattern[start_index:] + arg_counts = match_partial(positionals, selected_pattern) + + # slice off the appropriate arg strings for each Positional + # and add the Positional and its args to the list + for action, arg_count in zip(positionals, arg_counts): + args = arg_strings[start_index: start_index + arg_count] + start_index += arg_count + take_action(action, args) + + # slice off the Positionals that we just parsed and return the + # index at which the Positionals' string args stopped + positionals[:] = positionals[len(arg_counts):] + return start_index + + # consume Positionals and Optionals alternately, until we have + # passed the last option string + extras = [] + start_index = 0 + if option_string_indices: + max_option_string_index = max(option_string_indices) + else: + max_option_string_index = -1 + while start_index <= max_option_string_index: + + # consume any Positionals preceding the next option + next_option_string_index = min([ + index + for index in option_string_indices + if index >= start_index]) + if start_index != next_option_string_index: + positionals_end_index = consume_positionals(start_index) + + # only try to parse the next optional if we didn't consume + # the option string during the positionals parsing + if positionals_end_index > start_index: + start_index = positionals_end_index + continue + else: + start_index = positionals_end_index + + # if we consumed all the positionals we could and we're not + # at the index of an option string, there were extra arguments + if start_index not in option_string_indices: + strings = arg_strings[start_index:next_option_string_index] + extras.extend(strings) + start_index = next_option_string_index + + # consume the next optional and any arguments for it + start_index = consume_optional(start_index) + + # consume any positionals following the last Optional + stop_index = consume_positionals(start_index) + + # if we didn't consume all the argument strings, there were extras + extras.extend(arg_strings[stop_index:]) + + # if we didn't use all the Positional objects, there were too few + # arg strings supplied. + if positionals: + self.error(_('too few arguments')) + + # make sure all required actions were present + for action in self._actions: + if action.required: + if action not in seen_actions: + name = _get_action_name(action) + self.error(_('argument %s is required') % name) + + # make sure all required groups had one option present + for group in self._mutually_exclusive_groups: + if group.required: + for action in group._group_actions: + if action in seen_non_default_actions: + break + + # if no actions were used, report the error + else: + names = [_get_action_name(action) + for action in group._group_actions + if action.help is not SUPPRESS] + msg = _('one of the arguments %s is required') + self.error(msg % ' '.join(names)) + + # return the updated namespace and the extra arguments + return namespace, extras + + def _read_args_from_files(self, arg_strings): + # expand arguments referencing files + new_arg_strings = [] + for arg_string in arg_strings: + + # for regular arguments, just add them back into the list + if arg_string[0] not in self.fromfile_prefix_chars: + new_arg_strings.append(arg_string) + + # replace arguments referencing files with the file content + else: + try: + args_file = open(arg_string[1:]) + try: + arg_strings = args_file.read().splitlines() + arg_strings = self._read_args_from_files(arg_strings) + new_arg_strings.extend(arg_strings) + finally: + args_file.close() + except IOError: + err = _sys.exc_info()[1] + self.error(str(err)) + + # return the modified argument list + return new_arg_strings + + def _match_argument(self, action, arg_strings_pattern): + # match the pattern for this action to the arg strings + nargs_pattern = self._get_nargs_pattern(action) + match = _re.match(nargs_pattern, arg_strings_pattern) + + # raise an exception if we weren't able to find a match + if match is None: + nargs_errors = { + None: _('expected one argument'), + OPTIONAL: _('expected at most one argument'), + ONE_OR_MORE: _('expected at least one argument'), + } + default = _('expected %s argument(s)') % action.nargs + msg = nargs_errors.get(action.nargs, default) + raise ArgumentError(action, msg) + + # return the number of arguments matched + return len(match.group(1)) + + def _match_arguments_partial(self, actions, arg_strings_pattern): + # progressively shorten the actions list by slicing off the + # final actions until we find a match + result = [] + for i in range(len(actions), 0, -1): + actions_slice = actions[:i] + pattern = ''.join([self._get_nargs_pattern(action) + for action in actions_slice]) + match = _re.match(pattern, arg_strings_pattern) + if match is not None: + result.extend([len(string) for string in match.groups()]) + break + + # return the list of arg string counts + return result + + def _parse_optional(self, arg_string): + # if it's an empty string, it was meant to be a positional + if not arg_string: + return None + + # if it doesn't start with a prefix, it was meant to be positional + if not arg_string[0] in self.prefix_chars: + return None + + # if it's just dashes, it was meant to be positional + if not arg_string.strip('-'): + return None + + # if the option string is present in the parser, return the action + if arg_string in self._option_string_actions: + action = self._option_string_actions[arg_string] + return action, arg_string, None + + # search through all possible prefixes of the option string + # and all actions in the parser for possible interpretations + option_tuples = self._get_option_tuples(arg_string) + + # if multiple actions match, the option string was ambiguous + if len(option_tuples) > 1: + options = ', '.join([option_string + for action, option_string, explicit_arg in option_tuples]) + tup = arg_string, options + self.error(_('ambiguous option: %s could match %s') % tup) + + # if exactly one action matched, this segmentation is good, + # so return the parsed action + elif len(option_tuples) == 1: + option_tuple, = option_tuples + return option_tuple + + # if it was not found as an option, but it looks like a negative + # number, it was meant to be positional + # unless there are negative-number-like options + if self._negative_number_matcher.match(arg_string): + if not self._has_negative_number_optionals: + return None + + # if it contains a space, it was meant to be a positional + if ' ' in arg_string: + return None + + # it was meant to be an optional but there is no such option + # in this parser (though it might be a valid option in a subparser) + return None, arg_string, None + + def _get_option_tuples(self, option_string): + result = [] + + # option strings starting with two prefix characters are only + # split at the '=' + chars = self.prefix_chars + if option_string[0] in chars and option_string[1] in chars: + if '=' in option_string: + option_prefix, explicit_arg = option_string.split('=', 1) + else: + option_prefix = option_string + explicit_arg = None + for option_string in self._option_string_actions: + if option_string.startswith(option_prefix): + action = self._option_string_actions[option_string] + tup = action, option_string, explicit_arg + result.append(tup) + + # single character options can be concatenated with their arguments + # but multiple character options always have to have their argument + # separate + elif option_string[0] in chars and option_string[1] not in chars: + option_prefix = option_string + explicit_arg = None + short_option_prefix = option_string[:2] + short_explicit_arg = option_string[2:] + + for option_string in self._option_string_actions: + if option_string == short_option_prefix: + action = self._option_string_actions[option_string] + tup = action, option_string, short_explicit_arg + result.append(tup) + elif option_string.startswith(option_prefix): + action = self._option_string_actions[option_string] + tup = action, option_string, explicit_arg + result.append(tup) + + # shouldn't ever get here + else: + self.error(_('unexpected option string: %s') % option_string) + + # return the collected option tuples + return result + + def _get_nargs_pattern(self, action): + # in all examples below, we have to allow for '--' args + # which are represented as '-' in the pattern + nargs = action.nargs + + # the default (None) is assumed to be a single argument + if nargs is None: + nargs_pattern = '(-*A-*)' + + # allow zero or one arguments + elif nargs == OPTIONAL: + nargs_pattern = '(-*A?-*)' + + # allow zero or more arguments + elif nargs == ZERO_OR_MORE: + nargs_pattern = '(-*[A-]*)' + + # allow one or more arguments + elif nargs == ONE_OR_MORE: + nargs_pattern = '(-*A[A-]*)' + + # allow one argument followed by any number of options or arguments + elif nargs is PARSER: + nargs_pattern = '(-*A[-AO]*)' + + # all others should be integers + else: + nargs_pattern = '(-*%s-*)' % '-*'.join('A' * nargs) + + # if this is an optional action, -- is not allowed + if action.option_strings: + nargs_pattern = nargs_pattern.replace('-*', '') + nargs_pattern = nargs_pattern.replace('-', '') + + # return the pattern + return nargs_pattern + + # ======================== + # Value conversion methods + # ======================== + def _get_values(self, action, arg_strings): + # for everything but PARSER args, strip out '--' + if action.nargs is not PARSER: + arg_strings = [s for s in arg_strings if s != '--'] + + # optional argument produces a default when not present + if not arg_strings and action.nargs == OPTIONAL: + if action.option_strings: + value = action.const + else: + value = action.default + if isinstance(value, _basestring): + value = self._get_value(action, value) + self._check_value(action, value) + + # when nargs='*' on a positional, if there were no command-line + # args, use the default if it is anything other than None + elif (not arg_strings and action.nargs == ZERO_OR_MORE and + not action.option_strings): + if action.default is not None: + value = action.default + else: + value = arg_strings + self._check_value(action, value) + + # single argument or optional argument produces a single value + elif len(arg_strings) == 1 and action.nargs in [None, OPTIONAL]: + arg_string, = arg_strings + value = self._get_value(action, arg_string) + self._check_value(action, value) + + # PARSER arguments convert all values, but check only the first + elif action.nargs is PARSER: + value = [self._get_value(action, v) for v in arg_strings] + self._check_value(action, value[0]) + + # all other types of nargs produce a list + else: + value = [self._get_value(action, v) for v in arg_strings] + for v in value: + self._check_value(action, v) + + # return the converted value + return value + + def _get_value(self, action, arg_string): + type_func = self._registry_get('type', action.type, action.type) + if not hasattr(type_func, '__call__'): + if not hasattr(type_func, '__bases__'): # classic classes + msg = _('%r is not callable') + raise ArgumentError(action, msg % type_func) + + # convert the value to the appropriate type + try: + result = type_func(arg_string) + + # TypeErrors or ValueErrors indicate errors + except (TypeError, ValueError): + name = getattr(action.type, '__name__', repr(action.type)) + msg = _('invalid %s value: %r') + raise ArgumentError(action, msg % (name, arg_string)) + + # return the converted value + return result + + def _check_value(self, action, value): + # converted value must be one of the choices (if specified) + if action.choices is not None and value not in action.choices: + tup = value, ', '.join(map(repr, action.choices)) + msg = _('invalid choice: %r (choose from %s)') % tup + raise ArgumentError(action, msg) + + # ======================= + # Help-formatting methods + # ======================= + def format_usage(self): + formatter = self._get_formatter() + formatter.add_usage(self.usage, self._actions, + self._mutually_exclusive_groups) + return formatter.format_help() + + def format_help(self): + formatter = self._get_formatter() + + # usage + formatter.add_usage(self.usage, self._actions, + self._mutually_exclusive_groups) + + # description + formatter.add_text(self.description) + + # positionals, optionals and user-defined groups + for action_group in self._action_groups: + formatter.start_section(action_group.title) + formatter.add_text(action_group.description) + formatter.add_arguments(action_group._group_actions) + formatter.end_section() + + # epilog + formatter.add_text(self.epilog) + + # determine help from format above + return formatter.format_help() + + def format_version(self): + formatter = self._get_formatter() + formatter.add_text(self.version) + return formatter.format_help() + + def _get_formatter(self): + return self.formatter_class(prog=self.prog) + + # ===================== + # Help-printing methods + # ===================== + def print_usage(self, file=None): + self._print_message(self.format_usage(), file) + + def print_help(self, file=None): + self._print_message(self.format_help(), file) + + def print_version(self, file=None): + self._print_message(self.format_version(), file) + + def _print_message(self, message, file=None): + if message: + if file is None: + file = _sys.stderr + file.write(message) + + # =============== + # Exiting methods + # =============== + def exit(self, status=0, message=None): + if message: + _sys.stderr.write(message) + _sys.exit(status) + + def error(self, message): + """error(message: string) + + Prints a usage message incorporating the message to stderr and + exits. + + If you override this in a subclass, it should not return -- it + should either exit or raise an exception. + """ + self.print_usage(_sys.stderr) + self.exit(2, _('%s: error: %s\n') % (self.prog, message)) diff --git a/IPython/external/decorator.py b/IPython/external/decorator.py new file mode 100644 index 0000000..e4b1ce7 --- /dev/null +++ b/IPython/external/decorator.py @@ -0,0 +1,254 @@ +########################## LICENCE ############################### +## +## Copyright (c) 2005, Michele Simionato +## All rights reserved. +## +## Redistributions of source code must retain the above copyright +## notice, this list of conditions and the following disclaimer. +## Redistributions in bytecode form must reproduce the above copyright +## notice, this list of conditions and the following disclaimer in +## the documentation and/or other materials provided with the +## distribution. + +## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +## HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +## INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +## BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +## OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +## ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +## TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +## USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +## DAMAGE. + +""" +Decorator module, see http://pypi.python.org/pypi/decorator +for the documentation. +""" + +__all__ = ["decorator", "FunctionMaker", "partial", + "deprecated", "getinfo", "new_wrapper"] + +import os, sys, re, inspect, string, warnings +try: + from functools import partial +except ImportError: # for Python version < 2.5 + class partial(object): + "A simple replacement of functools.partial" + def __init__(self, func, *args, **kw): + self.func = func + self.args = args + self.keywords = kw + def __call__(self, *otherargs, **otherkw): + kw = self.keywords.copy() + kw.update(otherkw) + return self.func(*(self.args + otherargs), **kw) + +DEF = re.compile('\s*def\s*([_\w][_\w\d]*)\s*\(') + +# basic functionality +class FunctionMaker(object): + """ + An object with the ability to create functions with a given signature. + It has attributes name, doc, module, signature, defaults, dict and + methods update and make. + """ + def __init__(self, func=None, name=None, signature=None, + defaults=None, doc=None, module=None, funcdict=None): + if func: + # func can be a class or a callable, but not an instance method + self.name = func.__name__ + if self.name == '': # small hack for lambda functions + self.name = '_lambda_' + self.doc = func.__doc__ + self.module = func.__module__ + if inspect.isfunction(func): + argspec = inspect.getargspec(func) + self.args, self.varargs, self.keywords, self.defaults = argspec + for i, arg in enumerate(self.args): + setattr(self, 'arg%d' % i, arg) + self.signature = inspect.formatargspec( + formatvalue=lambda val: "", *argspec)[1:-1] + self.dict = func.__dict__.copy() + if name: + self.name = name + if signature is not None: + self.signature = signature + if defaults: + self.defaults = defaults + if doc: + self.doc = doc + if module: + self.module = module + if funcdict: + self.dict = funcdict + # check existence required attributes + assert hasattr(self, 'name') + if not hasattr(self, 'signature'): + raise TypeError('You are decorating a non function: %s' % func) + + def update(self, func, **kw): + "Update the signature of func with the data in self" + func.__name__ = self.name + func.__doc__ = getattr(self, 'doc', None) + func.__dict__ = getattr(self, 'dict', {}) + func.func_defaults = getattr(self, 'defaults', ()) + callermodule = sys._getframe(3).f_globals.get('__name__', '?') + func.__module__ = getattr(self, 'module', callermodule) + func.__dict__.update(kw) + + def make(self, src_templ, evaldict=None, addsource=False, **attrs): + "Make a new function from a given template and update the signature" + src = src_templ % vars(self) # expand name and signature + evaldict = evaldict or {} + mo = DEF.match(src) + if mo is None: + raise SyntaxError('not a valid function template\n%s' % src) + name = mo.group(1) # extract the function name + reserved_names = set([name] + [ + arg.strip(' *') for arg in self.signature.split(',')]) + for n, v in evaldict.iteritems(): + if n in reserved_names: + raise NameError('%s is overridden in\n%s' % (n, src)) + if not src.endswith('\n'): # add a newline just for safety + src += '\n' + try: + code = compile(src, '', 'single') + exec code in evaldict + except: + print >> sys.stderr, 'Error in generated code:' + print >> sys.stderr, src + raise + func = evaldict[name] + if addsource: + attrs['__source__'] = src + self.update(func, **attrs) + return func + + @classmethod + def create(cls, obj, body, evaldict, defaults=None, + doc=None, module=None, addsource=True,**attrs): + """ + Create a function from the strings name, signature and body. + evaldict is the evaluation dictionary. If addsource is true an attribute + __source__ is added to the result. The attributes attrs are added, + if any. + """ + if isinstance(obj, str): # "name(signature)" + name, rest = obj.strip().split('(', 1) + signature = rest[:-1] #strip a right parens + func = None + else: # a function + name = None + signature = None + func = obj + fun = cls(func, name, signature, defaults, doc, module) + ibody = '\n'.join(' ' + line for line in body.splitlines()) + return fun.make('def %(name)s(%(signature)s):\n' + ibody, + evaldict, addsource, **attrs) + +def decorator(caller, func=None): + """ + decorator(caller) converts a caller function into a decorator; + decorator(caller, func) decorates a function using a caller. + """ + if func is not None: # returns a decorated function + return FunctionMaker.create( + func, "return _call_(_func_, %(signature)s)", + dict(_call_=caller, _func_=func), undecorated=func) + else: # returns a decorator + if isinstance(caller, partial): + return partial(decorator, caller) + # otherwise assume caller is a function + f = inspect.getargspec(caller)[0][0] # first arg + return FunctionMaker.create( + '%s(%s)' % (caller.__name__, f), + 'return decorator(_call_, %s)' % f, + dict(_call_=caller, decorator=decorator), undecorated=caller, + doc=caller.__doc__, module=caller.__module__) + +###################### deprecated functionality ######################### + +@decorator +def deprecated(func, *args, **kw): + "A decorator for deprecated functions" + warnings.warn( + ('Calling the deprecated function %r\n' + 'Downgrade to decorator 2.3 if you want to use this functionality') + % func.__name__, DeprecationWarning, stacklevel=3) + return func(*args, **kw) + +@deprecated +def getinfo(func): + """ + Returns an info dictionary containing: + - name (the name of the function : str) + - argnames (the names of the arguments : list) + - defaults (the values of the default arguments : tuple) + - signature (the signature : str) + - doc (the docstring : str) + - module (the module name : str) + - dict (the function __dict__ : str) + + >>> def f(self, x=1, y=2, *args, **kw): pass + + >>> info = getinfo(f) + + >>> info["name"] + 'f' + >>> info["argnames"] + ['self', 'x', 'y', 'args', 'kw'] + + >>> info["defaults"] + (1, 2) + + >>> info["signature"] + 'self, x, y, *args, **kw' + """ + assert inspect.ismethod(func) or inspect.isfunction(func) + regargs, varargs, varkwargs, defaults = inspect.getargspec(func) + argnames = list(regargs) + if varargs: + argnames.append(varargs) + if varkwargs: + argnames.append(varkwargs) + signature = inspect.formatargspec(regargs, varargs, varkwargs, defaults, + formatvalue=lambda value: "")[1:-1] + return dict(name=func.__name__, argnames=argnames, signature=signature, + defaults = func.func_defaults, doc=func.__doc__, + module=func.__module__, dict=func.__dict__, + globals=func.func_globals, closure=func.func_closure) + +@deprecated +def update_wrapper(wrapper, model, infodict=None): + "A replacement for functools.update_wrapper" + infodict = infodict or getinfo(model) + wrapper.__name__ = infodict['name'] + wrapper.__doc__ = infodict['doc'] + wrapper.__module__ = infodict['module'] + wrapper.__dict__.update(infodict['dict']) + wrapper.func_defaults = infodict['defaults'] + wrapper.undecorated = model + return wrapper + +@deprecated +def new_wrapper(wrapper, model): + """ + An improvement over functools.update_wrapper. The wrapper is a generic + callable object. It works by generating a copy of the wrapper with the + right signature and by updating the copy, not the original. + Moreovoer, 'model' can be a dictionary with keys 'name', 'doc', 'module', + 'dict', 'defaults'. + """ + if isinstance(model, dict): + infodict = model + else: # assume model is a function + infodict = getinfo(model) + assert not '_wrapper_' in infodict["argnames"], ( + '"_wrapper_" is a reserved argument name!') + src = "lambda %(signature)s: _wrapper_(%(signature)s)" % infodict + funcopy = eval(src, dict(_wrapper_=wrapper)) + return update_wrapper(funcopy, model, infodict) + diff --git a/IPython/external/mglob.py b/IPython/external/mglob.py index ed3679f..1212c05 100755 --- a/IPython/external/mglob.py +++ b/IPython/external/mglob.py @@ -213,7 +213,7 @@ def main(): print "\n".join(expand(sys.argv[1:])), def mglob_f(self, arg): - from IPython.genutils import SList + from IPython.utils.genutils import SList if arg.strip(): return SList(expand(arg)) print "Please specify pattern!" @@ -222,7 +222,7 @@ def mglob_f(self, arg): def init_ipython(ip): """ register %mglob for IPython """ mglob_f.__doc__ = globsyntax - ip.expose_magic("mglob",mglob_f) + ip.define_magic("mglob",mglob_f) # test() if __name__ == "__main__": diff --git a/IPython/external/pyparsing.py b/IPython/external/pyparsing.py new file mode 100644 index 0000000..91b106a --- /dev/null +++ b/IPython/external/pyparsing.py @@ -0,0 +1,3707 @@ +# module pyparsing.py +# +# Copyright (c) 2003-2009 Paul T. McGuire +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +#from __future__ import generators + +__doc__ = \ +""" +pyparsing module - Classes and methods to define and execute parsing grammars + +The pyparsing module is an alternative approach to creating and executing simple grammars, +vs. the traditional lex/yacc approach, or the use of regular expressions. With pyparsing, you +don't need to learn a new syntax for defining grammars or matching expressions - the parsing module +provides a library of classes that you use to construct the grammar directly in Python. + +Here is a program to parse "Hello, World!" (or any greeting of the form ", !"):: + + from pyparsing import Word, alphas + + # define grammar of a greeting + greet = Word( alphas ) + "," + Word( alphas ) + "!" + + hello = "Hello, World!" + print hello, "->", greet.parseString( hello ) + +The program outputs the following:: + + Hello, World! -> ['Hello', ',', 'World', '!'] + +The Python representation of the grammar is quite readable, owing to the self-explanatory +class names, and the use of '+', '|' and '^' operators. + +The parsed results returned from parseString() can be accessed as a nested list, a dictionary, or an +object with named attributes. + +The pyparsing module handles some of the problems that are typically vexing when writing text parsers: + - extra or missing whitespace (the above program will also handle "Hello,World!", "Hello , World !", etc.) + - quoted strings + - embedded comments +""" + +__version__ = "1.5.2" +__versionTime__ = "17 February 2009 19:45" +__author__ = "Paul McGuire " + +import string +from weakref import ref as wkref +import copy +import sys +import warnings +import re +import sre_constants +#~ sys.stderr.write( "testing pyparsing module, version %s, %s\n" % (__version__,__versionTime__ ) ) + +__all__ = [ +'And', 'CaselessKeyword', 'CaselessLiteral', 'CharsNotIn', 'Combine', 'Dict', 'Each', 'Empty', +'FollowedBy', 'Forward', 'GoToColumn', 'Group', 'Keyword', 'LineEnd', 'LineStart', 'Literal', +'MatchFirst', 'NoMatch', 'NotAny', 'OneOrMore', 'OnlyOnce', 'Optional', 'Or', +'ParseBaseException', 'ParseElementEnhance', 'ParseException', 'ParseExpression', 'ParseFatalException', +'ParseResults', 'ParseSyntaxException', 'ParserElement', 'QuotedString', 'RecursiveGrammarException', +'Regex', 'SkipTo', 'StringEnd', 'StringStart', 'Suppress', 'Token', 'TokenConverter', 'Upcase', +'White', 'Word', 'WordEnd', 'WordStart', 'ZeroOrMore', +'alphanums', 'alphas', 'alphas8bit', 'anyCloseTag', 'anyOpenTag', 'cStyleComment', 'col', +'commaSeparatedList', 'commonHTMLEntity', 'countedArray', 'cppStyleComment', 'dblQuotedString', +'dblSlashComment', 'delimitedList', 'dictOf', 'downcaseTokens', 'empty', 'getTokensEndLoc', 'hexnums', +'htmlComment', 'javaStyleComment', 'keepOriginalText', 'line', 'lineEnd', 'lineStart', 'lineno', +'makeHTMLTags', 'makeXMLTags', 'matchOnlyAtCol', 'matchPreviousExpr', 'matchPreviousLiteral', +'nestedExpr', 'nullDebugAction', 'nums', 'oneOf', 'opAssoc', 'operatorPrecedence', 'printables', +'punc8bit', 'pythonStyleComment', 'quotedString', 'removeQuotes', 'replaceHTMLEntity', +'replaceWith', 'restOfLine', 'sglQuotedString', 'srange', 'stringEnd', +'stringStart', 'traceParseAction', 'unicodeString', 'upcaseTokens', 'withAttribute', +'indentedBlock', 'originalTextFor', +] + + +""" +Detect if we are running version 3.X and make appropriate changes +Robert A. Clark +""" +if sys.version_info[0] > 2: + _PY3K = True + _MAX_INT = sys.maxsize + basestring = str +else: + _PY3K = False + _MAX_INT = sys.maxint + +if not _PY3K: + def _ustr(obj): + """Drop-in replacement for str(obj) that tries to be Unicode friendly. It first tries + str(obj). If that fails with a UnicodeEncodeError, then it tries unicode(obj). It + then < returns the unicode object | encodes it with the default encoding | ... >. + """ + if isinstance(obj,unicode): + return obj + + try: + # If this works, then _ustr(obj) has the same behaviour as str(obj), so + # it won't break any existing code. + return str(obj) + + except UnicodeEncodeError: + # The Python docs (http://docs.python.org/ref/customization.html#l2h-182) + # state that "The return value must be a string object". However, does a + # unicode object (being a subclass of basestring) count as a "string + # object"? + # If so, then return a unicode object: + return unicode(obj) + # Else encode it... but how? There are many choices... :) + # Replace unprintables with escape codes? + #return unicode(obj).encode(sys.getdefaultencoding(), 'backslashreplace_errors') + # Replace unprintables with question marks? + #return unicode(obj).encode(sys.getdefaultencoding(), 'replace') + # ... +else: + _ustr = str + unichr = chr + +if not _PY3K: + def _str2dict(strg): + return dict( [(c,0) for c in strg] ) +else: + _str2dict = set + +def _xml_escape(data): + """Escape &, <, >, ", ', etc. in a string of data.""" + + # ampersand must be replaced first + from_symbols = '&><"\'' + to_symbols = ['&'+s+';' for s in "amp gt lt quot apos".split()] + for from_,to_ in zip(from_symbols, to_symbols): + data = data.replace(from_, to_) + return data + +class _Constants(object): + pass + +if not _PY3K: + alphas = string.lowercase + string.uppercase +else: + alphas = string.ascii_lowercase + string.ascii_uppercase +nums = string.digits +hexnums = nums + "ABCDEFabcdef" +alphanums = alphas + nums +_bslash = chr(92) +printables = "".join( [ c for c in string.printable if c not in string.whitespace ] ) + +class ParseBaseException(Exception): + """base exception class for all parsing runtime exceptions""" + # Performance tuning: we construct a *lot* of these, so keep this + # constructor as small and fast as possible + def __init__( self, pstr, loc=0, msg=None, elem=None ): + self.loc = loc + if msg is None: + self.msg = pstr + self.pstr = "" + else: + self.msg = msg + self.pstr = pstr + self.parserElement = elem + + def __getattr__( self, aname ): + """supported attributes by name are: + - lineno - returns the line number of the exception text + - col - returns the column number of the exception text + - line - returns the line containing the exception text + """ + if( aname == "lineno" ): + return lineno( self.loc, self.pstr ) + elif( aname in ("col", "column") ): + return col( self.loc, self.pstr ) + elif( aname == "line" ): + return line( self.loc, self.pstr ) + else: + raise AttributeError(aname) + + def __str__( self ): + return "%s (at char %d), (line:%d, col:%d)" % \ + ( self.msg, self.loc, self.lineno, self.column ) + def __repr__( self ): + return _ustr(self) + def markInputline( self, markerString = ">!<" ): + """Extracts the exception line from the input string, and marks + the location of the exception with a special symbol. + """ + line_str = self.line + line_column = self.column - 1 + if markerString: + line_str = "".join( [line_str[:line_column], + markerString, line_str[line_column:]]) + return line_str.strip() + def __dir__(self): + return "loc msg pstr parserElement lineno col line " \ + "markInputLine __str__ __repr__".split() + +class ParseException(ParseBaseException): + """exception thrown when parse expressions don't match class; + supported attributes by name are: + - lineno - returns the line number of the exception text + - col - returns the column number of the exception text + - line - returns the line containing the exception text + """ + pass + +class ParseFatalException(ParseBaseException): + """user-throwable exception thrown when inconsistent parse content + is found; stops all parsing immediately""" + pass + +class ParseSyntaxException(ParseFatalException): + """just like ParseFatalException, but thrown internally when an + ErrorStop indicates that parsing is to stop immediately because + an unbacktrackable syntax error has been found""" + def __init__(self, pe): + super(ParseSyntaxException, self).__init__( + pe.pstr, pe.loc, pe.msg, pe.parserElement) + +#~ class ReparseException(ParseBaseException): + #~ """Experimental class - parse actions can raise this exception to cause + #~ pyparsing to reparse the input string: + #~ - with a modified input string, and/or + #~ - with a modified start location + #~ Set the values of the ReparseException in the constructor, and raise the + #~ exception in a parse action to cause pyparsing to use the new string/location. + #~ Setting the values as None causes no change to be made. + #~ """ + #~ def __init_( self, newstring, restartLoc ): + #~ self.newParseText = newstring + #~ self.reparseLoc = restartLoc + +class RecursiveGrammarException(Exception): + """exception thrown by validate() if the grammar could be improperly recursive""" + def __init__( self, parseElementList ): + self.parseElementTrace = parseElementList + + def __str__( self ): + return "RecursiveGrammarException: %s" % self.parseElementTrace + +class _ParseResultsWithOffset(object): + def __init__(self,p1,p2): + self.tup = (p1,p2) + def __getitem__(self,i): + return self.tup[i] + def __repr__(self): + return repr(self.tup) + def setOffset(self,i): + self.tup = (self.tup[0],i) + +class ParseResults(object): + """Structured parse results, to provide multiple means of access to the parsed data: + - as a list (len(results)) + - by list index (results[0], results[1], etc.) + - by attribute (results.) + """ + __slots__ = ( "__toklist", "__tokdict", "__doinit", "__name", "__parent", "__accumNames", "__weakref__" ) + def __new__(cls, toklist, name=None, asList=True, modal=True ): + if isinstance(toklist, cls): + return toklist + retobj = object.__new__(cls) + retobj.__doinit = True + return retobj + + # Performance tuning: we construct a *lot* of these, so keep this + # constructor as small and fast as possible + def __init__( self, toklist, name=None, asList=True, modal=True ): + if self.__doinit: + self.__doinit = False + self.__name = None + self.__parent = None + self.__accumNames = {} + if isinstance(toklist, list): + self.__toklist = toklist[:] + else: + self.__toklist = [toklist] + self.__tokdict = dict() + + if name: + if not modal: + self.__accumNames[name] = 0 + if isinstance(name,int): + name = _ustr(name) # will always return a str, but use _ustr for consistency + self.__name = name + if not toklist in (None,'',[]): + if isinstance(toklist,basestring): + toklist = [ toklist ] + if asList: + if isinstance(toklist,ParseResults): + self[name] = _ParseResultsWithOffset(toklist.copy(),0) + else: + self[name] = _ParseResultsWithOffset(ParseResults(toklist[0]),0) + self[name].__name = name + else: + try: + self[name] = toklist[0] + except (KeyError,TypeError,IndexError): + self[name] = toklist + + def __getitem__( self, i ): + if isinstance( i, (int,slice) ): + return self.__toklist[i] + else: + if i not in self.__accumNames: + return self.__tokdict[i][-1][0] + else: + return ParseResults([ v[0] for v in self.__tokdict[i] ]) + + def __setitem__( self, k, v ): + if isinstance(v,_ParseResultsWithOffset): + self.__tokdict[k] = self.__tokdict.get(k,list()) + [v] + sub = v[0] + elif isinstance(k,int): + self.__toklist[k] = v + sub = v + else: + self.__tokdict[k] = self.__tokdict.get(k,list()) + [_ParseResultsWithOffset(v,0)] + sub = v + if isinstance(sub,ParseResults): + sub.__parent = wkref(self) + + def __delitem__( self, i ): + if isinstance(i,(int,slice)): + mylen = len( self.__toklist ) + del self.__toklist[i] + + # convert int to slice + if isinstance(i, int): + if i < 0: + i += mylen + i = slice(i, i+1) + # get removed indices + removed = list(range(*i.indices(mylen))) + removed.reverse() + # fixup indices in token dictionary + for name in self.__tokdict: + occurrences = self.__tokdict[name] + for j in removed: + for k, (value, position) in enumerate(occurrences): + occurrences[k] = _ParseResultsWithOffset(value, position - (position > j)) + else: + del self.__tokdict[i] + + def __contains__( self, k ): + return k in self.__tokdict + + def __len__( self ): return len( self.__toklist ) + def __bool__(self): return len( self.__toklist ) > 0 + __nonzero__ = __bool__ + def __iter__( self ): return iter( self.__toklist ) + def __reversed__( self ): return iter( reversed(self.__toklist) ) + def keys( self ): + """Returns all named result keys.""" + return self.__tokdict.keys() + + def pop( self, index=-1 ): + """Removes and returns item at specified index (default=last). + Will work with either numeric indices or dict-key indicies.""" + ret = self[index] + del self[index] + return ret + + def get(self, key, defaultValue=None): + """Returns named result matching the given key, or if there is no + such name, then returns the given defaultValue or None if no + defaultValue is specified.""" + if key in self: + return self[key] + else: + return defaultValue + + def insert( self, index, insStr ): + self.__toklist.insert(index, insStr) + # fixup indices in token dictionary + for name in self.__tokdict: + occurrences = self.__tokdict[name] + for k, (value, position) in enumerate(occurrences): + occurrences[k] = _ParseResultsWithOffset(value, position + (position > index)) + + def items( self ): + """Returns all named result keys and values as a list of tuples.""" + return [(k,self[k]) for k in self.__tokdict] + + def values( self ): + """Returns all named result values.""" + return [ v[-1][0] for v in self.__tokdict.values() ] + + def __getattr__( self, name ): + if name not in self.__slots__: + if name in self.__tokdict: + if name not in self.__accumNames: + return self.__tokdict[name][-1][0] + else: + return ParseResults([ v[0] for v in self.__tokdict[name] ]) + else: + return "" + return None + + def __add__( self, other ): + ret = self.copy() + ret += other + return ret + + def __iadd__( self, other ): + if other.__tokdict: + offset = len(self.__toklist) + addoffset = ( lambda a: (a<0 and offset) or (a+offset) ) + otheritems = other.__tokdict.items() + otherdictitems = [(k, _ParseResultsWithOffset(v[0],addoffset(v[1])) ) + for (k,vlist) in otheritems for v in vlist] + for k,v in otherdictitems: + self[k] = v + if isinstance(v[0],ParseResults): + v[0].__parent = wkref(self) + + self.__toklist += other.__toklist + self.__accumNames.update( other.__accumNames ) + del other + return self + + def __repr__( self ): + return "(%s, %s)" % ( repr( self.__toklist ), repr( self.__tokdict ) ) + + def __str__( self ): + out = "[" + sep = "" + for i in self.__toklist: + if isinstance(i, ParseResults): + out += sep + _ustr(i) + else: + out += sep + repr(i) + sep = ", " + out += "]" + return out + + def _asStringList( self, sep='' ): + out = [] + for item in self.__toklist: + if out and sep: + out.append(sep) + if isinstance( item, ParseResults ): + out += item._asStringList() + else: + out.append( _ustr(item) ) + return out + + def asList( self ): + """Returns the parse results as a nested list of matching tokens, all converted to strings.""" + out = [] + for res in self.__toklist: + if isinstance(res,ParseResults): + out.append( res.asList() ) + else: + out.append( res ) + return out + + def asDict( self ): + """Returns the named parse results as dictionary.""" + return dict( self.items() ) + + def copy( self ): + """Returns a new copy of a ParseResults object.""" + ret = ParseResults( self.__toklist ) + ret.__tokdict = self.__tokdict.copy() + ret.__parent = self.__parent + ret.__accumNames.update( self.__accumNames ) + ret.__name = self.__name + return ret + + def asXML( self, doctag=None, namedItemsOnly=False, indent="", formatted=True ): + """Returns the parse results as XML. Tags are created for tokens and lists that have defined results names.""" + nl = "\n" + out = [] + namedItems = dict( [ (v[1],k) for (k,vlist) in self.__tokdict.items() + for v in vlist ] ) + nextLevelIndent = indent + " " + + # collapse out indents if formatting is not desired + if not formatted: + indent = "" + nextLevelIndent = "" + nl = "" + + selfTag = None + if doctag is not None: + selfTag = doctag + else: + if self.__name: + selfTag = self.__name + + if not selfTag: + if namedItemsOnly: + return "" + else: + selfTag = "ITEM" + + out += [ nl, indent, "<", selfTag, ">" ] + + worklist = self.__toklist + for i,res in enumerate(worklist): + if isinstance(res,ParseResults): + if i in namedItems: + out += [ res.asXML(namedItems[i], + namedItemsOnly and doctag is None, + nextLevelIndent, + formatted)] + else: + out += [ res.asXML(None, + namedItemsOnly and doctag is None, + nextLevelIndent, + formatted)] + else: + # individual token, see if there is a name for it + resTag = None + if i in namedItems: + resTag = namedItems[i] + if not resTag: + if namedItemsOnly: + continue + else: + resTag = "ITEM" + xmlBodyText = _xml_escape(_ustr(res)) + out += [ nl, nextLevelIndent, "<", resTag, ">", + xmlBodyText, + "" ] + + out += [ nl, indent, "" ] + return "".join(out) + + def __lookup(self,sub): + for k,vlist in self.__tokdict.items(): + for v,loc in vlist: + if sub is v: + return k + return None + + def getName(self): + """Returns the results name for this token expression.""" + if self.__name: + return self.__name + elif self.__parent: + par = self.__parent() + if par: + return par.__lookup(self) + else: + return None + elif (len(self) == 1 and + len(self.__tokdict) == 1 and + self.__tokdict.values()[0][0][1] in (0,-1)): + return self.__tokdict.keys()[0] + else: + return None + + def dump(self,indent='',depth=0): + """Diagnostic method for listing out the contents of a ParseResults. + Accepts an optional indent argument so that this string can be embedded + in a nested display of other data.""" + out = [] + out.append( indent+_ustr(self.asList()) ) + keys = self.items() + keys.sort() + for k,v in keys: + if out: + out.append('\n') + out.append( "%s%s- %s: " % (indent,(' '*depth), k) ) + if isinstance(v,ParseResults): + if v.keys(): + #~ out.append('\n') + out.append( v.dump(indent,depth+1) ) + #~ out.append('\n') + else: + out.append(_ustr(v)) + else: + out.append(_ustr(v)) + #~ out.append('\n') + return "".join(out) + + # add support for pickle protocol + def __getstate__(self): + return ( self.__toklist, + ( self.__tokdict.copy(), + self.__parent is not None and self.__parent() or None, + self.__accumNames, + self.__name ) ) + + def __setstate__(self,state): + self.__toklist = state[0] + self.__tokdict, \ + par, \ + inAccumNames, \ + self.__name = state[1] + self.__accumNames = {} + self.__accumNames.update(inAccumNames) + if par is not None: + self.__parent = wkref(par) + else: + self.__parent = None + + def __dir__(self): + return dir(super(ParseResults,self)) + self.keys() + +def col (loc,strg): + """Returns current column within a string, counting newlines as line separators. + The first column is number 1. + + Note: the default parsing behavior is to expand tabs in the input string + before starting the parsing process. See L{I{ParserElement.parseString}} for more information + on parsing strings containing s, and suggested methods to maintain a + consistent view of the parsed string, the parse location, and line and column + positions within the parsed string. + """ + return (loc} for more information + on parsing strings containing s, and suggested methods to maintain a + consistent view of the parsed string, the parse location, and line and column + positions within the parsed string. + """ + return strg.count("\n",0,loc) + 1 + +def line( loc, strg ): + """Returns the line of text containing loc within a string, counting newlines as line separators. + """ + lastCR = strg.rfind("\n", 0, loc) + nextCR = strg.find("\n", loc) + if nextCR > 0: + return strg[lastCR+1:nextCR] + else: + return strg[lastCR+1:] + +def _defaultStartDebugAction( instring, loc, expr ): + print ("Match " + _ustr(expr) + " at loc " + _ustr(loc) + "(%d,%d)" % ( lineno(loc,instring), col(loc,instring) )) + +def _defaultSuccessDebugAction( instring, startloc, endloc, expr, toks ): + print ("Matched " + _ustr(expr) + " -> " + str(toks.asList())) + +def _defaultExceptionDebugAction( instring, loc, expr, exc ): + print ("Exception raised:" + _ustr(exc)) + +def nullDebugAction(*args): + """'Do-nothing' debug action, to suppress debugging output during parsing.""" + pass + +class ParserElement(object): + """Abstract base level parser element class.""" + DEFAULT_WHITE_CHARS = " \n\t\r" + + def setDefaultWhitespaceChars( chars ): + """Overrides the default whitespace chars + """ + ParserElement.DEFAULT_WHITE_CHARS = chars + setDefaultWhitespaceChars = staticmethod(setDefaultWhitespaceChars) + + def __init__( self, savelist=False ): + self.parseAction = list() + self.failAction = None + #~ self.name = "" # don't define self.name, let subclasses try/except upcall + self.strRepr = None + self.resultsName = None + self.saveAsList = savelist + self.skipWhitespace = True + self.whiteChars = ParserElement.DEFAULT_WHITE_CHARS + self.copyDefaultWhiteChars = True + self.mayReturnEmpty = False # used when checking for left-recursion + self.keepTabs = False + self.ignoreExprs = list() + self.debug = False + self.streamlined = False + self.mayIndexError = True # used to optimize exception handling for subclasses that don't advance parse index + self.errmsg = "" + self.modalResults = True # used to mark results names as modal (report only last) or cumulative (list all) + self.debugActions = ( None, None, None ) #custom debug actions + self.re = None + self.callPreparse = True # used to avoid redundant calls to preParse + self.callDuringTry = False + + def copy( self ): + """Make a copy of this ParserElement. Useful for defining different parse actions + for the same parsing pattern, using copies of the original parse element.""" + cpy = copy.copy( self ) + cpy.parseAction = self.parseAction[:] + cpy.ignoreExprs = self.ignoreExprs[:] + if self.copyDefaultWhiteChars: + cpy.whiteChars = ParserElement.DEFAULT_WHITE_CHARS + return cpy + + def setName( self, name ): + """Define name for this expression, for use in debugging.""" + self.name = name + self.errmsg = "Expected " + self.name + if hasattr(self,"exception"): + self.exception.msg = self.errmsg + return self + + def setResultsName( self, name, listAllMatches=False ): + """Define name for referencing matching tokens as a nested attribute + of the returned parse results. + NOTE: this returns a *copy* of the original ParserElement object; + this is so that the client can define a basic element, such as an + integer, and reference it in multiple places with different names. + """ + newself = self.copy() + newself.resultsName = name + newself.modalResults = not listAllMatches + return newself + + def setBreak(self,breakFlag = True): + """Method to invoke the Python pdb debugger when this element is + about to be parsed. Set breakFlag to True to enable, False to + disable. + """ + if breakFlag: + _parseMethod = self._parse + def breaker(instring, loc, doActions=True, callPreParse=True): + import pdb + pdb.set_trace() + return _parseMethod( instring, loc, doActions, callPreParse ) + breaker._originalParseMethod = _parseMethod + self._parse = breaker + else: + if hasattr(self._parse,"_originalParseMethod"): + self._parse = self._parse._originalParseMethod + return self + + def _normalizeParseActionArgs( f ): + """Internal method used to decorate parse actions that take fewer than 3 arguments, + so that all parse actions can be called as f(s,l,t).""" + STAR_ARGS = 4 + + try: + restore = None + if isinstance(f,type): + restore = f + f = f.__init__ + if not _PY3K: + codeObj = f.func_code + else: + codeObj = f.code + if codeObj.co_flags & STAR_ARGS: + return f + numargs = codeObj.co_argcount + if not _PY3K: + if hasattr(f,"im_self"): + numargs -= 1 + else: + if hasattr(f,"__self__"): + numargs -= 1 + if restore: + f = restore + except AttributeError: + try: + if not _PY3K: + call_im_func_code = f.__call__.im_func.func_code + else: + call_im_func_code = f.__code__ + + # not a function, must be a callable object, get info from the + # im_func binding of its bound __call__ method + if call_im_func_code.co_flags & STAR_ARGS: + return f + numargs = call_im_func_code.co_argcount + if not _PY3K: + if hasattr(f.__call__,"im_self"): + numargs -= 1 + else: + if hasattr(f.__call__,"__self__"): + numargs -= 0 + except AttributeError: + if not _PY3K: + call_func_code = f.__call__.func_code + else: + call_func_code = f.__call__.__code__ + # not a bound method, get info directly from __call__ method + if call_func_code.co_flags & STAR_ARGS: + return f + numargs = call_func_code.co_argcount + if not _PY3K: + if hasattr(f.__call__,"im_self"): + numargs -= 1 + else: + if hasattr(f.__call__,"__self__"): + numargs -= 1 + + + #~ print ("adding function %s with %d args" % (f.func_name,numargs)) + if numargs == 3: + return f + else: + if numargs > 3: + def tmp(s,l,t): + return f(f.__call__.__self__, s,l,t) + if numargs == 2: + def tmp(s,l,t): + return f(l,t) + elif numargs == 1: + def tmp(s,l,t): + return f(t) + else: #~ numargs == 0: + def tmp(s,l,t): + return f() + try: + tmp.__name__ = f.__name__ + except (AttributeError,TypeError): + # no need for special handling if attribute doesnt exist + pass + try: + tmp.__doc__ = f.__doc__ + except (AttributeError,TypeError): + # no need for special handling if attribute doesnt exist + pass + try: + tmp.__dict__.update(f.__dict__) + except (AttributeError,TypeError): + # no need for special handling if attribute doesnt exist + pass + return tmp + _normalizeParseActionArgs = staticmethod(_normalizeParseActionArgs) + + def setParseAction( self, *fns, **kwargs ): + """Define action to perform when successfully matching parse element definition. + Parse action fn is a callable method with 0-3 arguments, called as fn(s,loc,toks), + fn(loc,toks), fn(toks), or just fn(), where: + - s = the original string being parsed (see note below) + - loc = the location of the matching substring + - toks = a list of the matched tokens, packaged as a ParseResults object + If the functions in fns modify the tokens, they can return them as the return + value from fn, and the modified list of tokens will replace the original. + Otherwise, fn does not need to return any value. + + Note: the default parsing behavior is to expand tabs in the input string + before starting the parsing process. See L{I{parseString}} for more information + on parsing strings containing s, and suggested methods to maintain a + consistent view of the parsed string, the parse location, and line and column + positions within the parsed string. + """ + self.parseAction = list(map(self._normalizeParseActionArgs, list(fns))) + self.callDuringTry = ("callDuringTry" in kwargs and kwargs["callDuringTry"]) + return self + + def addParseAction( self, *fns, **kwargs ): + """Add parse action to expression's list of parse actions. See L{I{setParseAction}}.""" + self.parseAction += list(map(self._normalizeParseActionArgs, list(fns))) + self.callDuringTry = self.callDuringTry or ("callDuringTry" in kwargs and kwargs["callDuringTry"]) + return self + + def setFailAction( self, fn ): + """Define action to perform if parsing fails at this expression. + Fail acton fn is a callable function that takes the arguments + fn(s,loc,expr,err) where: + - s = string being parsed + - loc = location where expression match was attempted and failed + - expr = the parse expression that failed + - err = the exception thrown + The function returns no value. It may throw ParseFatalException + if it is desired to stop parsing immediately.""" + self.failAction = fn + return self + + def _skipIgnorables( self, instring, loc ): + exprsFound = True + while exprsFound: + exprsFound = False + for e in self.ignoreExprs: + try: + while 1: + loc,dummy = e._parse( instring, loc ) + exprsFound = True + except ParseException: + pass + return loc + + def preParse( self, instring, loc ): + if self.ignoreExprs: + loc = self._skipIgnorables( instring, loc ) + + if self.skipWhitespace: + wt = self.whiteChars + instrlen = len(instring) + while loc < instrlen and instring[loc] in wt: + loc += 1 + + return loc + + def parseImpl( self, instring, loc, doActions=True ): + return loc, [] + + def postParse( self, instring, loc, tokenlist ): + return tokenlist + + #~ @profile + def _parseNoCache( self, instring, loc, doActions=True, callPreParse=True ): + debugging = ( self.debug ) #and doActions ) + + if debugging or self.failAction: + #~ print ("Match",self,"at loc",loc,"(%d,%d)" % ( lineno(loc,instring), col(loc,instring) )) + if (self.debugActions[0] ): + self.debugActions[0]( instring, loc, self ) + if callPreParse and self.callPreparse: + preloc = self.preParse( instring, loc ) + else: + preloc = loc + tokensStart = loc + try: + try: + loc,tokens = self.parseImpl( instring, preloc, doActions ) + except IndexError: + raise ParseException( instring, len(instring), self.errmsg, self ) + except ParseBaseException, err: + #~ print ("Exception raised:", err) + if self.debugActions[2]: + self.debugActions[2]( instring, tokensStart, self, err ) + if self.failAction: + self.failAction( instring, tokensStart, self, err ) + raise + else: + if callPreParse and self.callPreparse: + preloc = self.preParse( instring, loc ) + else: + preloc = loc + tokensStart = loc + if self.mayIndexError or loc >= len(instring): + try: + loc,tokens = self.parseImpl( instring, preloc, doActions ) + except IndexError: + raise ParseException( instring, len(instring), self.errmsg, self ) + else: + loc,tokens = self.parseImpl( instring, preloc, doActions ) + + tokens = self.postParse( instring, loc, tokens ) + + retTokens = ParseResults( tokens, self.resultsName, asList=self.saveAsList, modal=self.modalResults ) + if self.parseAction and (doActions or self.callDuringTry): + if debugging: + try: + for fn in self.parseAction: + tokens = fn( instring, tokensStart, retTokens ) + if tokens is not None: + retTokens = ParseResults( tokens, + self.resultsName, + asList=self.saveAsList and isinstance(tokens,(ParseResults,list)), + modal=self.modalResults ) + except ParseBaseException, err: + #~ print "Exception raised in user parse action:", err + if (self.debugActions[2] ): + self.debugActions[2]( instring, tokensStart, self, err ) + raise + else: + for fn in self.parseAction: + tokens = fn( instring, tokensStart, retTokens ) + if tokens is not None: + retTokens = ParseResults( tokens, + self.resultsName, + asList=self.saveAsList and isinstance(tokens,(ParseResults,list)), + modal=self.modalResults ) + + if debugging: + #~ print ("Matched",self,"->",retTokens.asList()) + if (self.debugActions[1] ): + self.debugActions[1]( instring, tokensStart, loc, self, retTokens ) + + return loc, retTokens + + def tryParse( self, instring, loc ): + try: + return self._parse( instring, loc, doActions=False )[0] + except ParseFatalException: + raise ParseException( instring, loc, self.errmsg, self) + + # this method gets repeatedly called during backtracking with the same arguments - + # we can cache these arguments and save ourselves the trouble of re-parsing the contained expression + def _parseCache( self, instring, loc, doActions=True, callPreParse=True ): + lookup = (self,instring,loc,callPreParse,doActions) + if lookup in ParserElement._exprArgCache: + value = ParserElement._exprArgCache[ lookup ] + if isinstance(value,Exception): + raise value + return value + else: + try: + value = self._parseNoCache( instring, loc, doActions, callPreParse ) + ParserElement._exprArgCache[ lookup ] = (value[0],value[1].copy()) + return value + except ParseBaseException, pe: + ParserElement._exprArgCache[ lookup ] = pe + raise + + _parse = _parseNoCache + + # argument cache for optimizing repeated calls when backtracking through recursive expressions + _exprArgCache = {} + def resetCache(): + ParserElement._exprArgCache.clear() + resetCache = staticmethod(resetCache) + + _packratEnabled = False + def enablePackrat(): + """Enables "packrat" parsing, which adds memoizing to the parsing logic. + Repeated parse attempts at the same string location (which happens + often in many complex grammars) can immediately return a cached value, + instead of re-executing parsing/validating code. Memoizing is done of + both valid results and parsing exceptions. + + This speedup may break existing programs that use parse actions that + have side-effects. For this reason, packrat parsing is disabled when + you first import pyparsing. To activate the packrat feature, your + program must call the class method ParserElement.enablePackrat(). If + your program uses psyco to "compile as you go", you must call + enablePackrat before calling psyco.full(). If you do not do this, + Python will crash. For best results, call enablePackrat() immediately + after importing pyparsing. + """ + if not ParserElement._packratEnabled: + ParserElement._packratEnabled = True + ParserElement._parse = ParserElement._parseCache + enablePackrat = staticmethod(enablePackrat) + + def parseString( self, instring, parseAll=False ): + """Execute the parse expression with the given string. + This is the main interface to the client code, once the complete + expression has been built. + + If you want the grammar to require that the entire input string be + successfully parsed, then set parseAll to True (equivalent to ending + the grammar with StringEnd()). + + Note: parseString implicitly calls expandtabs() on the input string, + in order to report proper column numbers in parse actions. + If the input string contains tabs and + the grammar uses parse actions that use the loc argument to index into the + string being parsed, you can ensure you have a consistent view of the input + string by: + - calling parseWithTabs on your grammar before calling parseString + (see L{I{parseWithTabs}}) + - define your parse action using the full (s,loc,toks) signature, and + reference the input string using the parse action's s argument + - explictly expand the tabs in your input string before calling + parseString + """ + ParserElement.resetCache() + if not self.streamlined: + self.streamline() + #~ self.saveAsList = True + for e in self.ignoreExprs: + e.streamline() + if not self.keepTabs: + instring = instring.expandtabs() + try: + loc, tokens = self._parse( instring, 0 ) + if parseAll: + loc = self.preParse( instring, loc ) + StringEnd()._parse( instring, loc ) + except ParseBaseException, exc: + # catch and re-raise exception from here, clears out pyparsing internal stack trace + raise exc + else: + return tokens + + def scanString( self, instring, maxMatches=_MAX_INT ): + """Scan the input string for expression matches. Each match will return the + matching tokens, start location, and end location. May be called with optional + maxMatches argument, to clip scanning after 'n' matches are found. + + Note that the start and end locations are reported relative to the string + being parsed. See L{I{parseString}} for more information on parsing + strings with embedded tabs.""" + if not self.streamlined: + self.streamline() + for e in self.ignoreExprs: + e.streamline() + + if not self.keepTabs: + instring = _ustr(instring).expandtabs() + instrlen = len(instring) + loc = 0 + preparseFn = self.preParse + parseFn = self._parse + ParserElement.resetCache() + matches = 0 + try: + while loc <= instrlen and matches < maxMatches: + try: + preloc = preparseFn( instring, loc ) + nextLoc,tokens = parseFn( instring, preloc, callPreParse=False ) + except ParseException: + loc = preloc+1 + else: + matches += 1 + yield tokens, preloc, nextLoc + loc = nextLoc + except ParseBaseException, pe: + raise pe + + def transformString( self, instring ): + """Extension to scanString, to modify matching text with modified tokens that may + be returned from a parse action. To use transformString, define a grammar and + attach a parse action to it that modifies the returned token list. + Invoking transformString() on a target string will then scan for matches, + and replace the matched text patterns according to the logic in the parse + action. transformString() returns the resulting transformed string.""" + out = [] + lastE = 0 + # force preservation of s, to minimize unwanted transformation of string, and to + # keep string locs straight between transformString and scanString + self.keepTabs = True + try: + for t,s,e in self.scanString( instring ): + out.append( instring[lastE:s] ) + if t: + if isinstance(t,ParseResults): + out += t.asList() + elif isinstance(t,list): + out += t + else: + out.append(t) + lastE = e + out.append(instring[lastE:]) + return "".join(map(_ustr,out)) + except ParseBaseException, pe: + raise pe + + def searchString( self, instring, maxMatches=_MAX_INT ): + """Another extension to scanString, simplifying the access to the tokens found + to match the given parse expression. May be called with optional + maxMatches argument, to clip searching after 'n' matches are found. + """ + try: + return ParseResults([ t for t,s,e in self.scanString( instring, maxMatches ) ]) + except ParseBaseException, pe: + raise pe + + def __add__(self, other ): + """Implementation of + operator - returns And""" + if isinstance( other, basestring ): + other = Literal( other ) + if not isinstance( other, ParserElement ): + warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), + SyntaxWarning, stacklevel=2) + return None + return And( [ self, other ] ) + + def __radd__(self, other ): + """Implementation of + operator when left operand is not a ParserElement""" + if isinstance( other, basestring ): + other = Literal( other ) + if not isinstance( other, ParserElement ): + warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), + SyntaxWarning, stacklevel=2) + return None + return other + self + + def __sub__(self, other): + """Implementation of - operator, returns And with error stop""" + if isinstance( other, basestring ): + other = Literal( other ) + if not isinstance( other, ParserElement ): + warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), + SyntaxWarning, stacklevel=2) + return None + return And( [ self, And._ErrorStop(), other ] ) + + def __rsub__(self, other ): + """Implementation of - operator when left operand is not a ParserElement""" + if isinstance( other, basestring ): + other = Literal( other ) + if not isinstance( other, ParserElement ): + warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), + SyntaxWarning, stacklevel=2) + return None + return other - self + + def __mul__(self,other): + if isinstance(other,int): + minElements, optElements = other,0 + elif isinstance(other,tuple): + other = (other + (None, None))[:2] + if other[0] is None: + other = (0, other[1]) + if isinstance(other[0],int) and other[1] is None: + if other[0] == 0: + return ZeroOrMore(self) + if other[0] == 1: + return OneOrMore(self) + else: + return self*other[0] + ZeroOrMore(self) + elif isinstance(other[0],int) and isinstance(other[1],int): + minElements, optElements = other + optElements -= minElements + else: + raise TypeError("cannot multiply 'ParserElement' and ('%s','%s') objects", type(other[0]),type(other[1])) + else: + raise TypeError("cannot multiply 'ParserElement' and '%s' objects", type(other)) + + if minElements < 0: + raise ValueError("cannot multiply ParserElement by negative value") + if optElements < 0: + raise ValueError("second tuple value must be greater or equal to first tuple value") + if minElements == optElements == 0: + raise ValueError("cannot multiply ParserElement by 0 or (0,0)") + + if (optElements): + def makeOptionalList(n): + if n>1: + return Optional(self + makeOptionalList(n-1)) + else: + return Optional(self) + if minElements: + if minElements == 1: + ret = self + makeOptionalList(optElements) + else: + ret = And([self]*minElements) + makeOptionalList(optElements) + else: + ret = makeOptionalList(optElements) + else: + if minElements == 1: + ret = self + else: + ret = And([self]*minElements) + return ret + + def __rmul__(self, other): + return self.__mul__(other) + + def __or__(self, other ): + """Implementation of | operator - returns MatchFirst""" + if isinstance( other, basestring ): + other = Literal( other ) + if not isinstance( other, ParserElement ): + warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), + SyntaxWarning, stacklevel=2) + return None + return MatchFirst( [ self, other ] ) + + def __ror__(self, other ): + """Implementation of | operator when left operand is not a ParserElement""" + if isinstance( other, basestring ): + other = Literal( other ) + if not isinstance( other, ParserElement ): + warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), + SyntaxWarning, stacklevel=2) + return None + return other | self + + def __xor__(self, other ): + """Implementation of ^ operator - returns Or""" + if isinstance( other, basestring ): + other = Literal( other ) + if not isinstance( other, ParserElement ): + warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), + SyntaxWarning, stacklevel=2) + return None + return Or( [ self, other ] ) + + def __rxor__(self, other ): + """Implementation of ^ operator when left operand is not a ParserElement""" + if isinstance( other, basestring ): + other = Literal( other ) + if not isinstance( other, ParserElement ): + warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), + SyntaxWarning, stacklevel=2) + return None + return other ^ self + + def __and__(self, other ): + """Implementation of & operator - returns Each""" + if isinstance( other, basestring ): + other = Literal( other ) + if not isinstance( other, ParserElement ): + warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), + SyntaxWarning, stacklevel=2) + return None + return Each( [ self, other ] ) + + def __rand__(self, other ): + """Implementation of & operator when left operand is not a ParserElement""" + if isinstance( other, basestring ): + other = Literal( other ) + if not isinstance( other, ParserElement ): + warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), + SyntaxWarning, stacklevel=2) + return None + return other & self + + def __invert__( self ): + """Implementation of ~ operator - returns NotAny""" + return NotAny( self ) + + def __call__(self, name): + """Shortcut for setResultsName, with listAllMatches=default:: + userdata = Word(alphas).setResultsName("name") + Word(nums+"-").setResultsName("socsecno") + could be written as:: + userdata = Word(alphas)("name") + Word(nums+"-")("socsecno") + """ + return self.setResultsName(name) + + def suppress( self ): + """Suppresses the output of this ParserElement; useful to keep punctuation from + cluttering up returned output. + """ + return Suppress( self ) + + def leaveWhitespace( self ): + """Disables the skipping of whitespace before matching the characters in the + ParserElement's defined pattern. This is normally only used internally by + the pyparsing module, but may be needed in some whitespace-sensitive grammars. + """ + self.skipWhitespace = False + return self + + def setWhitespaceChars( self, chars ): + """Overrides the default whitespace chars + """ + self.skipWhitespace = True + self.whiteChars = chars + self.copyDefaultWhiteChars = False + return self + + def parseWithTabs( self ): + """Overrides default behavior to expand s to spaces before parsing the input string. + Must be called before parseString when the input grammar contains elements that + match characters.""" + self.keepTabs = True + return self + + def ignore( self, other ): + """Define expression to be ignored (e.g., comments) while doing pattern + matching; may be called repeatedly, to define multiple comment or other + ignorable patterns. + """ + if isinstance( other, Suppress ): + if other not in self.ignoreExprs: + self.ignoreExprs.append( other ) + else: + self.ignoreExprs.append( Suppress( other ) ) + return self + + def setDebugActions( self, startAction, successAction, exceptionAction ): + """Enable display of debugging messages while doing pattern matching.""" + self.debugActions = (startAction or _defaultStartDebugAction, + successAction or _defaultSuccessDebugAction, + exceptionAction or _defaultExceptionDebugAction) + self.debug = True + return self + + def setDebug( self, flag=True ): + """Enable display of debugging messages while doing pattern matching. + Set flag to True to enable, False to disable.""" + if flag: + self.setDebugActions( _defaultStartDebugAction, _defaultSuccessDebugAction, _defaultExceptionDebugAction ) + else: + self.debug = False + return self + + def __str__( self ): + return self.name + + def __repr__( self ): + return _ustr(self) + + def streamline( self ): + self.streamlined = True + self.strRepr = None + return self + + def checkRecursion( self, parseElementList ): + pass + + def validate( self, validateTrace=[] ): + """Check defined expressions for valid structure, check for infinite recursive definitions.""" + self.checkRecursion( [] ) + + def parseFile( self, file_or_filename, parseAll=False ): + """Execute the parse expression on the given file or filename. + If a filename is specified (instead of a file object), + the entire file is opened, read, and closed before parsing. + """ + try: + file_contents = file_or_filename.read() + except AttributeError: + f = open(file_or_filename, "rb") + file_contents = f.read() + f.close() + try: + return self.parseString(file_contents, parseAll) + except ParseBaseException, exc: + # catch and re-raise exception from here, clears out pyparsing internal stack trace + raise exc + + def getException(self): + return ParseException("",0,self.errmsg,self) + + def __getattr__(self,aname): + if aname == "myException": + self.myException = ret = self.getException(); + return ret; + else: + raise AttributeError("no such attribute " + aname) + + def __eq__(self,other): + if isinstance(other, ParserElement): + return self is other or self.__dict__ == other.__dict__ + elif isinstance(other, basestring): + try: + self.parseString(_ustr(other), parseAll=True) + return True + except ParseBaseException: + return False + else: + return super(ParserElement,self)==other + + def __ne__(self,other): + return not (self == other) + + def __hash__(self): + return hash(id(self)) + + def __req__(self,other): + return self == other + + def __rne__(self,other): + return not (self == other) + + +class Token(ParserElement): + """Abstract ParserElement subclass, for defining atomic matching patterns.""" + def __init__( self ): + super(Token,self).__init__( savelist=False ) + #self.myException = ParseException("",0,"",self) + + def setName(self, name): + s = super(Token,self).setName(name) + self.errmsg = "Expected " + self.name + #s.myException.msg = self.errmsg + return s + + +class Empty(Token): + """An empty token, will always match.""" + def __init__( self ): + super(Empty,self).__init__() + self.name = "Empty" + self.mayReturnEmpty = True + self.mayIndexError = False + + +class NoMatch(Token): + """A token that will never match.""" + def __init__( self ): + super(NoMatch,self).__init__() + self.name = "NoMatch" + self.mayReturnEmpty = True + self.mayIndexError = False + self.errmsg = "Unmatchable token" + #self.myException.msg = self.errmsg + + def parseImpl( self, instring, loc, doActions=True ): + exc = self.myException + exc.loc = loc + exc.pstr = instring + raise exc + + +class Literal(Token): + """Token to exactly match a specified string.""" + def __init__( self, matchString ): + super(Literal,self).__init__() + self.match = matchString + self.matchLen = len(matchString) + try: + self.firstMatchChar = matchString[0] + except IndexError: + warnings.warn("null string passed to Literal; use Empty() instead", + SyntaxWarning, stacklevel=2) + self.__class__ = Empty + self.name = '"%s"' % _ustr(self.match) + self.errmsg = "Expected " + self.name + self.mayReturnEmpty = False + #self.myException.msg = self.errmsg + self.mayIndexError = False + + # Performance tuning: this routine gets called a *lot* + # if this is a single character match string and the first character matches, + # short-circuit as quickly as possible, and avoid calling startswith + #~ @profile + def parseImpl( self, instring, loc, doActions=True ): + if (instring[loc] == self.firstMatchChar and + (self.matchLen==1 or instring.startswith(self.match,loc)) ): + return loc+self.matchLen, self.match + #~ raise ParseException( instring, loc, self.errmsg ) + exc = self.myException + exc.loc = loc + exc.pstr = instring + raise exc +_L = Literal + +class Keyword(Token): + """Token to exactly match a specified string as a keyword, that is, it must be + immediately followed by a non-keyword character. Compare with Literal:: + Literal("if") will match the leading 'if' in 'ifAndOnlyIf'. + Keyword("if") will not; it will only match the leading 'if in 'if x=1', or 'if(y==2)' + Accepts two optional constructor arguments in addition to the keyword string: + identChars is a string of characters that would be valid identifier characters, + defaulting to all alphanumerics + "_" and "$"; caseless allows case-insensitive + matching, default is False. + """ + DEFAULT_KEYWORD_CHARS = alphanums+"_$" + + def __init__( self, matchString, identChars=DEFAULT_KEYWORD_CHARS, caseless=False ): + super(Keyword,self).__init__() + self.match = matchString + self.matchLen = len(matchString) + try: + self.firstMatchChar = matchString[0] + except IndexError: + warnings.warn("null string passed to Keyword; use Empty() instead", + SyntaxWarning, stacklevel=2) + self.name = '"%s"' % self.match + self.errmsg = "Expected " + self.name + self.mayReturnEmpty = False + #self.myException.msg = self.errmsg + self.mayIndexError = False + self.caseless = caseless + if caseless: + self.caselessmatch = matchString.upper() + identChars = identChars.upper() + self.identChars = _str2dict(identChars) + + def parseImpl( self, instring, loc, doActions=True ): + if self.caseless: + if ( (instring[ loc:loc+self.matchLen ].upper() == self.caselessmatch) and + (loc >= len(instring)-self.matchLen or instring[loc+self.matchLen].upper() not in self.identChars) and + (loc == 0 or instring[loc-1].upper() not in self.identChars) ): + return loc+self.matchLen, self.match + else: + if (instring[loc] == self.firstMatchChar and + (self.matchLen==1 or instring.startswith(self.match,loc)) and + (loc >= len(instring)-self.matchLen or instring[loc+self.matchLen] not in self.identChars) and + (loc == 0 or instring[loc-1] not in self.identChars) ): + return loc+self.matchLen, self.match + #~ raise ParseException( instring, loc, self.errmsg ) + exc = self.myException + exc.loc = loc + exc.pstr = instring + raise exc + + def copy(self): + c = super(Keyword,self).copy() + c.identChars = Keyword.DEFAULT_KEYWORD_CHARS + return c + + def setDefaultKeywordChars( chars ): + """Overrides the default Keyword chars + """ + Keyword.DEFAULT_KEYWORD_CHARS = chars + setDefaultKeywordChars = staticmethod(setDefaultKeywordChars) + +class CaselessLiteral(Literal): + """Token to match a specified string, ignoring case of letters. + Note: the matched results will always be in the case of the given + match string, NOT the case of the input text. + """ + def __init__( self, matchString ): + super(CaselessLiteral,self).__init__( matchString.upper() ) + # Preserve the defining literal. + self.returnString = matchString + self.name = "'%s'" % self.returnString + self.errmsg = "Expected " + self.name + #self.myException.msg = self.errmsg + + def parseImpl( self, instring, loc, doActions=True ): + if instring[ loc:loc+self.matchLen ].upper() == self.match: + return loc+self.matchLen, self.returnString + #~ raise ParseException( instring, loc, self.errmsg ) + exc = self.myException + exc.loc = loc + exc.pstr = instring + raise exc + +class CaselessKeyword(Keyword): + def __init__( self, matchString, identChars=Keyword.DEFAULT_KEYWORD_CHARS ): + super(CaselessKeyword,self).__init__( matchString, identChars, caseless=True ) + + def parseImpl( self, instring, loc, doActions=True ): + if ( (instring[ loc:loc+self.matchLen ].upper() == self.caselessmatch) and + (loc >= len(instring)-self.matchLen or instring[loc+self.matchLen].upper() not in self.identChars) ): + return loc+self.matchLen, self.match + #~ raise ParseException( instring, loc, self.errmsg ) + exc = self.myException + exc.loc = loc + exc.pstr = instring + raise exc + +class Word(Token): + """Token for matching words composed of allowed character sets. + Defined with string containing all allowed initial characters, + an optional string containing allowed body characters (if omitted, + defaults to the initial character set), and an optional minimum, + maximum, and/or exact length. The default value for min is 1 (a + minimum value < 1 is not valid); the default values for max and exact + are 0, meaning no maximum or exact length restriction. + """ + def __init__( self, initChars, bodyChars=None, min=1, max=0, exact=0, asKeyword=False ): + super(Word,self).__init__() + self.initCharsOrig = initChars + self.initChars = _str2dict(initChars) + if bodyChars : + self.bodyCharsOrig = bodyChars + self.bodyChars = _str2dict(bodyChars) + else: + self.bodyCharsOrig = initChars + self.bodyChars = _str2dict(initChars) + + self.maxSpecified = max > 0 + + if min < 1: + raise ValueError("cannot specify a minimum length < 1; use Optional(Word()) if zero-length word is permitted") + + self.minLen = min + + if max > 0: + self.maxLen = max + else: + self.maxLen = _MAX_INT + + if exact > 0: + self.maxLen = exact + self.minLen = exact + + self.name = _ustr(self) + self.errmsg = "Expected " + self.name + #self.myException.msg = self.errmsg + self.mayIndexError = False + self.asKeyword = asKeyword + + if ' ' not in self.initCharsOrig+self.bodyCharsOrig and (min==1 and max==0 and exact==0): + if self.bodyCharsOrig == self.initCharsOrig: + self.reString = "[%s]+" % _escapeRegexRangeChars(self.initCharsOrig) + elif len(self.bodyCharsOrig) == 1: + self.reString = "%s[%s]*" % \ + (re.escape(self.initCharsOrig), + _escapeRegexRangeChars(self.bodyCharsOrig),) + else: + self.reString = "[%s][%s]*" % \ + (_escapeRegexRangeChars(self.initCharsOrig), + _escapeRegexRangeChars(self.bodyCharsOrig),) + if self.asKeyword: + self.reString = r"\b"+self.reString+r"\b" + try: + self.re = re.compile( self.reString ) + except: + self.re = None + + def parseImpl( self, instring, loc, doActions=True ): + if self.re: + result = self.re.match(instring,loc) + if not result: + exc = self.myException + exc.loc = loc + exc.pstr = instring + raise exc + + loc = result.end() + return loc,result.group() + + if not(instring[ loc ] in self.initChars): + #~ raise ParseException( instring, loc, self.errmsg ) + exc = self.myException + exc.loc = loc + exc.pstr = instring + raise exc + start = loc + loc += 1 + instrlen = len(instring) + bodychars = self.bodyChars + maxloc = start + self.maxLen + maxloc = min( maxloc, instrlen ) + while loc < maxloc and instring[loc] in bodychars: + loc += 1 + + throwException = False + if loc - start < self.minLen: + throwException = True + if self.maxSpecified and loc < instrlen and instring[loc] in bodychars: + throwException = True + if self.asKeyword: + if (start>0 and instring[start-1] in bodychars) or (loc4: + return s[:4]+"..." + else: + return s + + if ( self.initCharsOrig != self.bodyCharsOrig ): + self.strRepr = "W:(%s,%s)" % ( charsAsStr(self.initCharsOrig), charsAsStr(self.bodyCharsOrig) ) + else: + self.strRepr = "W:(%s)" % charsAsStr(self.initCharsOrig) + + return self.strRepr + + +class Regex(Token): + """Token for matching strings that match a given regular expression. + Defined with string specifying the regular expression in a form recognized by the inbuilt Python re module. + """ + def __init__( self, pattern, flags=0): + """The parameters pattern and flags are passed to the re.compile() function as-is. See the Python re module for an explanation of the acceptable patterns and flags.""" + super(Regex,self).__init__() + + if len(pattern) == 0: + warnings.warn("null string passed to Regex; use Empty() instead", + SyntaxWarning, stacklevel=2) + + self.pattern = pattern + self.flags = flags + + try: + self.re = re.compile(self.pattern, self.flags) + self.reString = self.pattern + except sre_constants.error: + warnings.warn("invalid pattern (%s) passed to Regex" % pattern, + SyntaxWarning, stacklevel=2) + raise + + self.name = _ustr(self) + self.errmsg = "Expected " + self.name + #self.myException.msg = self.errmsg + self.mayIndexError = False + self.mayReturnEmpty = True + + def parseImpl( self, instring, loc, doActions=True ): + result = self.re.match(instring,loc) + if not result: + exc = self.myException + exc.loc = loc + exc.pstr = instring + raise exc + + loc = result.end() + d = result.groupdict() + ret = ParseResults(result.group()) + if d: + for k in d: + ret[k] = d[k] + return loc,ret + + def __str__( self ): + try: + return super(Regex,self).__str__() + except: + pass + + if self.strRepr is None: + self.strRepr = "Re:(%s)" % repr(self.pattern) + + return self.strRepr + + +class QuotedString(Token): + """Token for matching strings that are delimited by quoting characters. + """ + def __init__( self, quoteChar, escChar=None, escQuote=None, multiline=False, unquoteResults=True, endQuoteChar=None): + """ + Defined with the following parameters: + - quoteChar - string of one or more characters defining the quote delimiting string + - escChar - character to escape quotes, typically backslash (default=None) + - escQuote - special quote sequence to escape an embedded quote string (such as SQL's "" to escape an embedded ") (default=None) + - multiline - boolean indicating whether quotes can span multiple lines (default=False) + - unquoteResults - boolean indicating whether the matched text should be unquoted (default=True) + - endQuoteChar - string of one or more characters defining the end of the quote delimited string (default=None => same as quoteChar) + """ + super(QuotedString,self).__init__() + + # remove white space from quote chars - wont work anyway + quoteChar = quoteChar.strip() + if len(quoteChar) == 0: + warnings.warn("quoteChar cannot be the empty string",SyntaxWarning,stacklevel=2) + raise SyntaxError() + + if endQuoteChar is None: + endQuoteChar = quoteChar + else: + endQuoteChar = endQuoteChar.strip() + if len(endQuoteChar) == 0: + warnings.warn("endQuoteChar cannot be the empty string",SyntaxWarning,stacklevel=2) + raise SyntaxError() + + self.quoteChar = quoteChar + self.quoteCharLen = len(quoteChar) + self.firstQuoteChar = quoteChar[0] + self.endQuoteChar = endQuoteChar + self.endQuoteCharLen = len(endQuoteChar) + self.escChar = escChar + self.escQuote = escQuote + self.unquoteResults = unquoteResults + + if multiline: + self.flags = re.MULTILINE | re.DOTALL + self.pattern = r'%s(?:[^%s%s]' % \ + ( re.escape(self.quoteChar), + _escapeRegexRangeChars(self.endQuoteChar[0]), + (escChar is not None and _escapeRegexRangeChars(escChar) or '') ) + else: + self.flags = 0 + self.pattern = r'%s(?:[^%s\n\r%s]' % \ + ( re.escape(self.quoteChar), + _escapeRegexRangeChars(self.endQuoteChar[0]), + (escChar is not None and _escapeRegexRangeChars(escChar) or '') ) + if len(self.endQuoteChar) > 1: + self.pattern += ( + '|(?:' + ')|(?:'.join(["%s[^%s]" % (re.escape(self.endQuoteChar[:i]), + _escapeRegexRangeChars(self.endQuoteChar[i])) + for i in range(len(self.endQuoteChar)-1,0,-1)]) + ')' + ) + if escQuote: + self.pattern += (r'|(?:%s)' % re.escape(escQuote)) + if escChar: + self.pattern += (r'|(?:%s.)' % re.escape(escChar)) + self.escCharReplacePattern = re.escape(self.escChar)+"(.)" + self.pattern += (r')*%s' % re.escape(self.endQuoteChar)) + + try: + self.re = re.compile(self.pattern, self.flags) + self.reString = self.pattern + except sre_constants.error: + warnings.warn("invalid pattern (%s) passed to Regex" % self.pattern, + SyntaxWarning, stacklevel=2) + raise + + self.name = _ustr(self) + self.errmsg = "Expected " + self.name + #self.myException.msg = self.errmsg + self.mayIndexError = False + self.mayReturnEmpty = True + + def parseImpl( self, instring, loc, doActions=True ): + result = instring[loc] == self.firstQuoteChar and self.re.match(instring,loc) or None + if not result: + exc = self.myException + exc.loc = loc + exc.pstr = instring + raise exc + + loc = result.end() + ret = result.group() + + if self.unquoteResults: + + # strip off quotes + ret = ret[self.quoteCharLen:-self.endQuoteCharLen] + + if isinstance(ret,basestring): + # replace escaped characters + if self.escChar: + ret = re.sub(self.escCharReplacePattern,"\g<1>",ret) + + # replace escaped quotes + if self.escQuote: + ret = ret.replace(self.escQuote, self.endQuoteChar) + + return loc, ret + + def __str__( self ): + try: + return super(QuotedString,self).__str__() + except: + pass + + if self.strRepr is None: + self.strRepr = "quoted string, starting with %s ending with %s" % (self.quoteChar, self.endQuoteChar) + + return self.strRepr + + +class CharsNotIn(Token): + """Token for matching words composed of characters *not* in a given set. + Defined with string containing all disallowed characters, and an optional + minimum, maximum, and/or exact length. The default value for min is 1 (a + minimum value < 1 is not valid); the default values for max and exact + are 0, meaning no maximum or exact length restriction. + """ + def __init__( self, notChars, min=1, max=0, exact=0 ): + super(CharsNotIn,self).__init__() + self.skipWhitespace = False + self.notChars = notChars + + if min < 1: + raise ValueError("cannot specify a minimum length < 1; use Optional(CharsNotIn()) if zero-length char group is permitted") + + self.minLen = min + + if max > 0: + self.maxLen = max + else: + self.maxLen = _MAX_INT + + if exact > 0: + self.maxLen = exact + self.minLen = exact + + self.name = _ustr(self) + self.errmsg = "Expected " + self.name + self.mayReturnEmpty = ( self.minLen == 0 ) + #self.myException.msg = self.errmsg + self.mayIndexError = False + + def parseImpl( self, instring, loc, doActions=True ): + if instring[loc] in self.notChars: + #~ raise ParseException( instring, loc, self.errmsg ) + exc = self.myException + exc.loc = loc + exc.pstr = instring + raise exc + + start = loc + loc += 1 + notchars = self.notChars + maxlen = min( start+self.maxLen, len(instring) ) + while loc < maxlen and \ + (instring[loc] not in notchars): + loc += 1 + + if loc - start < self.minLen: + #~ raise ParseException( instring, loc, self.errmsg ) + exc = self.myException + exc.loc = loc + exc.pstr = instring + raise exc + + return loc, instring[start:loc] + + def __str__( self ): + try: + return super(CharsNotIn, self).__str__() + except: + pass + + if self.strRepr is None: + if len(self.notChars) > 4: + self.strRepr = "!W:(%s...)" % self.notChars[:4] + else: + self.strRepr = "!W:(%s)" % self.notChars + + return self.strRepr + +class White(Token): + """Special matching class for matching whitespace. Normally, whitespace is ignored + by pyparsing grammars. This class is included when some whitespace structures + are significant. Define with a string containing the whitespace characters to be + matched; default is " \\t\\r\\n". Also takes optional min, max, and exact arguments, + as defined for the Word class.""" + whiteStrs = { + " " : "", + "\t": "", + "\n": "", + "\r": "", + "\f": "", + } + def __init__(self, ws=" \t\r\n", min=1, max=0, exact=0): + super(White,self).__init__() + self.matchWhite = ws + self.setWhitespaceChars( "".join([c for c in self.whiteChars if c not in self.matchWhite]) ) + #~ self.leaveWhitespace() + self.name = ("".join([White.whiteStrs[c] for c in self.matchWhite])) + self.mayReturnEmpty = True + self.errmsg = "Expected " + self.name + #self.myException.msg = self.errmsg + + self.minLen = min + + if max > 0: + self.maxLen = max + else: + self.maxLen = _MAX_INT + + if exact > 0: + self.maxLen = exact + self.minLen = exact + + def parseImpl( self, instring, loc, doActions=True ): + if not(instring[ loc ] in self.matchWhite): + #~ raise ParseException( instring, loc, self.errmsg ) + exc = self.myException + exc.loc = loc + exc.pstr = instring + raise exc + start = loc + loc += 1 + maxloc = start + self.maxLen + maxloc = min( maxloc, len(instring) ) + while loc < maxloc and instring[loc] in self.matchWhite: + loc += 1 + + if loc - start < self.minLen: + #~ raise ParseException( instring, loc, self.errmsg ) + exc = self.myException + exc.loc = loc + exc.pstr = instring + raise exc + + return loc, instring[start:loc] + + +class _PositionToken(Token): + def __init__( self ): + super(_PositionToken,self).__init__() + self.name=self.__class__.__name__ + self.mayReturnEmpty = True + self.mayIndexError = False + +class GoToColumn(_PositionToken): + """Token to advance to a specific column of input text; useful for tabular report scraping.""" + def __init__( self, colno ): + super(GoToColumn,self).__init__() + self.col = colno + + def preParse( self, instring, loc ): + if col(loc,instring) != self.col: + instrlen = len(instring) + if self.ignoreExprs: + loc = self._skipIgnorables( instring, loc ) + while loc < instrlen and instring[loc].isspace() and col( loc, instring ) != self.col : + loc += 1 + return loc + + def parseImpl( self, instring, loc, doActions=True ): + thiscol = col( loc, instring ) + if thiscol > self.col: + raise ParseException( instring, loc, "Text not in expected column", self ) + newloc = loc + self.col - thiscol + ret = instring[ loc: newloc ] + return newloc, ret + +class LineStart(_PositionToken): + """Matches if current position is at the beginning of a line within the parse string""" + def __init__( self ): + super(LineStart,self).__init__() + self.setWhitespaceChars( ParserElement.DEFAULT_WHITE_CHARS.replace("\n","") ) + self.errmsg = "Expected start of line" + #self.myException.msg = self.errmsg + + def preParse( self, instring, loc ): + preloc = super(LineStart,self).preParse(instring,loc) + if instring[preloc] == "\n": + loc += 1 + return loc + + def parseImpl( self, instring, loc, doActions=True ): + if not( loc==0 or + (loc == self.preParse( instring, 0 )) or + (instring[loc-1] == "\n") ): #col(loc, instring) != 1: + #~ raise ParseException( instring, loc, "Expected start of line" ) + exc = self.myException + exc.loc = loc + exc.pstr = instring + raise exc + return loc, [] + +class LineEnd(_PositionToken): + """Matches if current position is at the end of a line within the parse string""" + def __init__( self ): + super(LineEnd,self).__init__() + self.setWhitespaceChars( ParserElement.DEFAULT_WHITE_CHARS.replace("\n","") ) + self.errmsg = "Expected end of line" + #self.myException.msg = self.errmsg + + def parseImpl( self, instring, loc, doActions=True ): + if loc len(instring): + return loc, [] + else: + exc = self.myException + exc.loc = loc + exc.pstr = instring + raise exc + +class WordStart(_PositionToken): + """Matches if the current position is at the beginning of a Word, and + is not preceded by any character in a given set of wordChars + (default=printables). To emulate the \b behavior of regular expressions, + use WordStart(alphanums). WordStart will also match at the beginning of + the string being parsed, or at the beginning of a line. + """ + def __init__(self, wordChars = printables): + super(WordStart,self).__init__() + self.wordChars = _str2dict(wordChars) + self.errmsg = "Not at the start of a word" + + def parseImpl(self, instring, loc, doActions=True ): + if loc != 0: + if (instring[loc-1] in self.wordChars or + instring[loc] not in self.wordChars): + exc = self.myException + exc.loc = loc + exc.pstr = instring + raise exc + return loc, [] + +class WordEnd(_PositionToken): + """Matches if the current position is at the end of a Word, and + is not followed by any character in a given set of wordChars + (default=printables). To emulate the \b behavior of regular expressions, + use WordEnd(alphanums). WordEnd will also match at the end of + the string being parsed, or at the end of a line. + """ + def __init__(self, wordChars = printables): + super(WordEnd,self).__init__() + self.wordChars = _str2dict(wordChars) + self.skipWhitespace = False + self.errmsg = "Not at the end of a word" + + def parseImpl(self, instring, loc, doActions=True ): + instrlen = len(instring) + if instrlen>0 and loc maxExcLoc: + maxException = err + maxExcLoc = err.loc + except IndexError: + if len(instring) > maxExcLoc: + maxException = ParseException(instring,len(instring),e.errmsg,self) + maxExcLoc = len(instring) + else: + if loc2 > maxMatchLoc: + maxMatchLoc = loc2 + maxMatchExp = e + + if maxMatchLoc < 0: + if maxException is not None: + raise maxException + else: + raise ParseException(instring, loc, "no defined alternatives to match", self) + + return maxMatchExp._parse( instring, loc, doActions ) + + def __ixor__(self, other ): + if isinstance( other, basestring ): + other = Literal( other ) + return self.append( other ) #Or( [ self, other ] ) + + def __str__( self ): + if hasattr(self,"name"): + return self.name + + if self.strRepr is None: + self.strRepr = "{" + " ^ ".join( [ _ustr(e) for e in self.exprs ] ) + "}" + + return self.strRepr + + def checkRecursion( self, parseElementList ): + subRecCheckList = parseElementList[:] + [ self ] + for e in self.exprs: + e.checkRecursion( subRecCheckList ) + + +class MatchFirst(ParseExpression): + """Requires that at least one ParseExpression is found. + If two expressions match, the first one listed is the one that will match. + May be constructed using the '|' operator. + """ + def __init__( self, exprs, savelist = False ): + super(MatchFirst,self).__init__(exprs, savelist) + if exprs: + self.mayReturnEmpty = False + for e in self.exprs: + if e.mayReturnEmpty: + self.mayReturnEmpty = True + break + else: + self.mayReturnEmpty = True + + def parseImpl( self, instring, loc, doActions=True ): + maxExcLoc = -1 + maxException = None + for e in self.exprs: + try: + ret = e._parse( instring, loc, doActions ) + return ret + except ParseException, err: + if err.loc > maxExcLoc: + maxException = err + maxExcLoc = err.loc + except IndexError: + if len(instring) > maxExcLoc: + maxException = ParseException(instring,len(instring),e.errmsg,self) + maxExcLoc = len(instring) + + # only got here if no expression matched, raise exception for match that made it the furthest + else: + if maxException is not None: + raise maxException + else: + raise ParseException(instring, loc, "no defined alternatives to match", self) + + def __ior__(self, other ): + if isinstance( other, basestring ): + other = Literal( other ) + return self.append( other ) #MatchFirst( [ self, other ] ) + + def __str__( self ): + if hasattr(self,"name"): + return self.name + + if self.strRepr is None: + self.strRepr = "{" + " | ".join( [ _ustr(e) for e in self.exprs ] ) + "}" + + return self.strRepr + + def checkRecursion( self, parseElementList ): + subRecCheckList = parseElementList[:] + [ self ] + for e in self.exprs: + e.checkRecursion( subRecCheckList ) + + +class Each(ParseExpression): + """Requires all given ParseExpressions to be found, but in any order. + Expressions may be separated by whitespace. + May be constructed using the '&' operator. + """ + def __init__( self, exprs, savelist = True ): + super(Each,self).__init__(exprs, savelist) + self.mayReturnEmpty = True + for e in self.exprs: + if not e.mayReturnEmpty: + self.mayReturnEmpty = False + break + self.skipWhitespace = True + self.initExprGroups = True + + def parseImpl( self, instring, loc, doActions=True ): + if self.initExprGroups: + self.optionals = [ e.expr for e in self.exprs if isinstance(e,Optional) ] + self.multioptionals = [ e.expr for e in self.exprs if isinstance(e,ZeroOrMore) ] + self.multirequired = [ e.expr for e in self.exprs if isinstance(e,OneOrMore) ] + self.required = [ e for e in self.exprs if not isinstance(e,(Optional,ZeroOrMore,OneOrMore)) ] + self.required += self.multirequired + self.initExprGroups = False + tmpLoc = loc + tmpReqd = self.required[:] + tmpOpt = self.optionals[:] + matchOrder = [] + + keepMatching = True + while keepMatching: + tmpExprs = tmpReqd + tmpOpt + self.multioptionals + self.multirequired + failed = [] + for e in tmpExprs: + try: + tmpLoc = e.tryParse( instring, tmpLoc ) + except ParseException: + failed.append(e) + else: + matchOrder.append(e) + if e in tmpReqd: + tmpReqd.remove(e) + elif e in tmpOpt: + tmpOpt.remove(e) + if len(failed) == len(tmpExprs): + keepMatching = False + + if tmpReqd: + missing = ", ".join( [ _ustr(e) for e in tmpReqd ] ) + raise ParseException(instring,loc,"Missing one or more required elements (%s)" % missing ) + + # add any unmatched Optionals, in case they have default values defined + matchOrder += list(e for e in self.exprs if isinstance(e,Optional) and e.expr in tmpOpt) + + resultlist = [] + for e in matchOrder: + loc,results = e._parse(instring,loc,doActions) + resultlist.append(results) + + finalResults = ParseResults([]) + for r in resultlist: + dups = {} + for k in r.keys(): + if k in finalResults.keys(): + tmp = ParseResults(finalResults[k]) + tmp += ParseResults(r[k]) + dups[k] = tmp + finalResults += ParseResults(r) + for k,v in dups.items(): + finalResults[k] = v + return loc, finalResults + + def __str__( self ): + if hasattr(self,"name"): + return self.name + + if self.strRepr is None: + self.strRepr = "{" + " & ".join( [ _ustr(e) for e in self.exprs ] ) + "}" + + return self.strRepr + + def checkRecursion( self, parseElementList ): + subRecCheckList = parseElementList[:] + [ self ] + for e in self.exprs: + e.checkRecursion( subRecCheckList ) + + +class ParseElementEnhance(ParserElement): + """Abstract subclass of ParserElement, for combining and post-processing parsed tokens.""" + def __init__( self, expr, savelist=False ): + super(ParseElementEnhance,self).__init__(savelist) + if isinstance( expr, basestring ): + expr = Literal(expr) + self.expr = expr + self.strRepr = None + if expr is not None: + self.mayIndexError = expr.mayIndexError + self.mayReturnEmpty = expr.mayReturnEmpty + self.setWhitespaceChars( expr.whiteChars ) + self.skipWhitespace = expr.skipWhitespace + self.saveAsList = expr.saveAsList + self.callPreparse = expr.callPreparse + self.ignoreExprs.extend(expr.ignoreExprs) + + def parseImpl( self, instring, loc, doActions=True ): + if self.expr is not None: + return self.expr._parse( instring, loc, doActions, callPreParse=False ) + else: + raise ParseException("",loc,self.errmsg,self) + + def leaveWhitespace( self ): + self.skipWhitespace = False + self.expr = self.expr.copy() + if self.expr is not None: + self.expr.leaveWhitespace() + return self + + def ignore( self, other ): + if isinstance( other, Suppress ): + if other not in self.ignoreExprs: + super( ParseElementEnhance, self).ignore( other ) + if self.expr is not None: + self.expr.ignore( self.ignoreExprs[-1] ) + else: + super( ParseElementEnhance, self).ignore( other ) + if self.expr is not None: + self.expr.ignore( self.ignoreExprs[-1] ) + return self + + def streamline( self ): + super(ParseElementEnhance,self).streamline() + if self.expr is not None: + self.expr.streamline() + return self + + def checkRecursion( self, parseElementList ): + if self in parseElementList: + raise RecursiveGrammarException( parseElementList+[self] ) + subRecCheckList = parseElementList[:] + [ self ] + if self.expr is not None: + self.expr.checkRecursion( subRecCheckList ) + + def validate( self, validateTrace=[] ): + tmp = validateTrace[:]+[self] + if self.expr is not None: + self.expr.validate(tmp) + self.checkRecursion( [] ) + + def __str__( self ): + try: + return super(ParseElementEnhance,self).__str__() + except: + pass + + if self.strRepr is None and self.expr is not None: + self.strRepr = "%s:(%s)" % ( self.__class__.__name__, _ustr(self.expr) ) + return self.strRepr + + +class FollowedBy(ParseElementEnhance): + """Lookahead matching of the given parse expression. FollowedBy + does *not* advance the parsing position within the input string, it only + verifies that the specified parse expression matches at the current + position. FollowedBy always returns a null token list.""" + def __init__( self, expr ): + super(FollowedBy,self).__init__(expr) + self.mayReturnEmpty = True + + def parseImpl( self, instring, loc, doActions=True ): + self.expr.tryParse( instring, loc ) + return loc, [] + + +class NotAny(ParseElementEnhance): + """Lookahead to disallow matching with the given parse expression. NotAny + does *not* advance the parsing position within the input string, it only + verifies that the specified parse expression does *not* match at the current + position. Also, NotAny does *not* skip over leading whitespace. NotAny + always returns a null token list. May be constructed using the '~' operator.""" + def __init__( self, expr ): + super(NotAny,self).__init__(expr) + #~ self.leaveWhitespace() + self.skipWhitespace = False # do NOT use self.leaveWhitespace(), don't want to propagate to exprs + self.mayReturnEmpty = True + self.errmsg = "Found unwanted token, "+_ustr(self.expr) + #self.myException = ParseException("",0,self.errmsg,self) + + def parseImpl( self, instring, loc, doActions=True ): + try: + self.expr.tryParse( instring, loc ) + except (ParseException,IndexError): + pass + else: + #~ raise ParseException(instring, loc, self.errmsg ) + exc = self.myException + exc.loc = loc + exc.pstr = instring + raise exc + return loc, [] + + def __str__( self ): + if hasattr(self,"name"): + return self.name + + if self.strRepr is None: + self.strRepr = "~{" + _ustr(self.expr) + "}" + + return self.strRepr + + +class ZeroOrMore(ParseElementEnhance): + """Optional repetition of zero or more of the given expression.""" + def __init__( self, expr ): + super(ZeroOrMore,self).__init__(expr) + self.mayReturnEmpty = True + + def parseImpl( self, instring, loc, doActions=True ): + tokens = [] + try: + loc, tokens = self.expr._parse( instring, loc, doActions, callPreParse=False ) + hasIgnoreExprs = ( len(self.ignoreExprs) > 0 ) + while 1: + if hasIgnoreExprs: + preloc = self._skipIgnorables( instring, loc ) + else: + preloc = loc + loc, tmptokens = self.expr._parse( instring, preloc, doActions ) + if tmptokens or tmptokens.keys(): + tokens += tmptokens + except (ParseException,IndexError): + pass + + return loc, tokens + + def __str__( self ): + if hasattr(self,"name"): + return self.name + + if self.strRepr is None: + self.strRepr = "[" + _ustr(self.expr) + "]..." + + return self.strRepr + + def setResultsName( self, name, listAllMatches=False ): + ret = super(ZeroOrMore,self).setResultsName(name,listAllMatches) + ret.saveAsList = True + return ret + + +class OneOrMore(ParseElementEnhance): + """Repetition of one or more of the given expression.""" + def parseImpl( self, instring, loc, doActions=True ): + # must be at least one + loc, tokens = self.expr._parse( instring, loc, doActions, callPreParse=False ) + try: + hasIgnoreExprs = ( len(self.ignoreExprs) > 0 ) + while 1: + if hasIgnoreExprs: + preloc = self._skipIgnorables( instring, loc ) + else: + preloc = loc + loc, tmptokens = self.expr._parse( instring, preloc, doActions ) + if tmptokens or tmptokens.keys(): + tokens += tmptokens + except (ParseException,IndexError): + pass + + return loc, tokens + + def __str__( self ): + if hasattr(self,"name"): + return self.name + + if self.strRepr is None: + self.strRepr = "{" + _ustr(self.expr) + "}..." + + return self.strRepr + + def setResultsName( self, name, listAllMatches=False ): + ret = super(OneOrMore,self).setResultsName(name,listAllMatches) + ret.saveAsList = True + return ret + +class _NullToken(object): + def __bool__(self): + return False + __nonzero__ = __bool__ + def __str__(self): + return "" + +_optionalNotMatched = _NullToken() +class Optional(ParseElementEnhance): + """Optional matching of the given expression. + A default return string can also be specified, if the optional expression + is not found. + """ + def __init__( self, exprs, default=_optionalNotMatched ): + super(Optional,self).__init__( exprs, savelist=False ) + self.defaultValue = default + self.mayReturnEmpty = True + + def parseImpl( self, instring, loc, doActions=True ): + try: + loc, tokens = self.expr._parse( instring, loc, doActions, callPreParse=False ) + except (ParseException,IndexError): + if self.defaultValue is not _optionalNotMatched: + if self.expr.resultsName: + tokens = ParseResults([ self.defaultValue ]) + tokens[self.expr.resultsName] = self.defaultValue + else: + tokens = [ self.defaultValue ] + else: + tokens = [] + return loc, tokens + + def __str__( self ): + if hasattr(self,"name"): + return self.name + + if self.strRepr is None: + self.strRepr = "[" + _ustr(self.expr) + "]" + + return self.strRepr + + +class SkipTo(ParseElementEnhance): + """Token for skipping over all undefined text until the matched expression is found. + If include is set to true, the matched expression is also parsed (the skipped text + and matched expression are returned as a 2-element list). The ignore + argument is used to define grammars (typically quoted strings and comments) that + might contain false matches. + """ + def __init__( self, other, include=False, ignore=None, failOn=None ): + super( SkipTo, self ).__init__( other ) + self.ignoreExpr = ignore + self.mayReturnEmpty = True + self.mayIndexError = False + self.includeMatch = include + self.asList = False + if failOn is not None and isinstance(failOn, basestring): + self.failOn = Literal(failOn) + else: + self.failOn = failOn + self.errmsg = "No match found for "+_ustr(self.expr) + #self.myException = ParseException("",0,self.errmsg,self) + + def parseImpl( self, instring, loc, doActions=True ): + startLoc = loc + instrlen = len(instring) + expr = self.expr + failParse = False + while loc <= instrlen: + try: + if self.failOn: + try: + self.failOn.tryParse(instring, loc) + except ParseBaseException: + pass + else: + failParse = True + raise ParseException(instring, loc, "Found expression " + str(self.failOn)) + failParse = False + if self.ignoreExpr is not None: + while 1: + try: + loc = self.ignoreExpr.tryParse(instring,loc) + print "found ignoreExpr, advance to", loc + except ParseBaseException: + break + expr._parse( instring, loc, doActions=False, callPreParse=False ) + skipText = instring[startLoc:loc] + if self.includeMatch: + loc,mat = expr._parse(instring,loc,doActions,callPreParse=False) + if mat: + skipRes = ParseResults( skipText ) + skipRes += mat + return loc, [ skipRes ] + else: + return loc, [ skipText ] + else: + return loc, [ skipText ] + except (ParseException,IndexError): + if failParse: + raise + else: + loc += 1 + exc = self.myException + exc.loc = loc + exc.pstr = instring + raise exc + +class Forward(ParseElementEnhance): + """Forward declaration of an expression to be defined later - + used for recursive grammars, such as algebraic infix notation. + When the expression is known, it is assigned to the Forward variable using the '<<' operator. + + Note: take care when assigning to Forward not to overlook precedence of operators. + Specifically, '|' has a lower precedence than '<<', so that:: + fwdExpr << a | b | c + will actually be evaluated as:: + (fwdExpr << a) | b | c + thereby leaving b and c out as parseable alternatives. It is recommended that you + explicitly group the values inserted into the Forward:: + fwdExpr << (a | b | c) + """ + def __init__( self, other=None ): + super(Forward,self).__init__( other, savelist=False ) + + def __lshift__( self, other ): + if isinstance( other, basestring ): + other = Literal(other) + self.expr = other + self.mayReturnEmpty = other.mayReturnEmpty + self.strRepr = None + self.mayIndexError = self.expr.mayIndexError + self.mayReturnEmpty = self.expr.mayReturnEmpty + self.setWhitespaceChars( self.expr.whiteChars ) + self.skipWhitespace = self.expr.skipWhitespace + self.saveAsList = self.expr.saveAsList + self.ignoreExprs.extend(self.expr.ignoreExprs) + return None + + def leaveWhitespace( self ): + self.skipWhitespace = False + return self + + def streamline( self ): + if not self.streamlined: + self.streamlined = True + if self.expr is not None: + self.expr.streamline() + return self + + def validate( self, validateTrace=[] ): + if self not in validateTrace: + tmp = validateTrace[:]+[self] + if self.expr is not None: + self.expr.validate(tmp) + self.checkRecursion([]) + + def __str__( self ): + if hasattr(self,"name"): + return self.name + + self._revertClass = self.__class__ + self.__class__ = _ForwardNoRecurse + try: + if self.expr is not None: + retString = _ustr(self.expr) + else: + retString = "None" + finally: + self.__class__ = self._revertClass + return self.__class__.__name__ + ": " + retString + + def copy(self): + if self.expr is not None: + return super(Forward,self).copy() + else: + ret = Forward() + ret << self + return ret + +class _ForwardNoRecurse(Forward): + def __str__( self ): + return "..." + +class TokenConverter(ParseElementEnhance): + """Abstract subclass of ParseExpression, for converting parsed results.""" + def __init__( self, expr, savelist=False ): + super(TokenConverter,self).__init__( expr )#, savelist ) + self.saveAsList = False + +class Upcase(TokenConverter): + """Converter to upper case all matching tokens.""" + def __init__(self, *args): + super(Upcase,self).__init__(*args) + warnings.warn("Upcase class is deprecated, use upcaseTokens parse action instead", + DeprecationWarning,stacklevel=2) + + def postParse( self, instring, loc, tokenlist ): + return list(map( string.upper, tokenlist )) + + +class Combine(TokenConverter): + """Converter to concatenate all matching tokens to a single string. + By default, the matching patterns must also be contiguous in the input string; + this can be disabled by specifying 'adjacent=False' in the constructor. + """ + def __init__( self, expr, joinString="", adjacent=True ): + super(Combine,self).__init__( expr ) + # suppress whitespace-stripping in contained parse expressions, but re-enable it on the Combine itself + if adjacent: + self.leaveWhitespace() + self.adjacent = adjacent + self.skipWhitespace = True + self.joinString = joinString + + def ignore( self, other ): + if self.adjacent: + ParserElement.ignore(self, other) + else: + super( Combine, self).ignore( other ) + return self + + def postParse( self, instring, loc, tokenlist ): + retToks = tokenlist.copy() + del retToks[:] + retToks += ParseResults([ "".join(tokenlist._asStringList(self.joinString)) ], modal=self.modalResults) + + if self.resultsName and len(retToks.keys())>0: + return [ retToks ] + else: + return retToks + +class Group(TokenConverter): + """Converter to return the matched tokens as a list - useful for returning tokens of ZeroOrMore and OneOrMore expressions.""" + def __init__( self, expr ): + super(Group,self).__init__( expr ) + self.saveAsList = True + + def postParse( self, instring, loc, tokenlist ): + return [ tokenlist ] + +class Dict(TokenConverter): + """Converter to return a repetitive expression as a list, but also as a dictionary. + Each element can also be referenced using the first token in the expression as its key. + Useful for tabular report scraping when the first column can be used as a item key. + """ + def __init__( self, exprs ): + super(Dict,self).__init__( exprs ) + self.saveAsList = True + + def postParse( self, instring, loc, tokenlist ): + for i,tok in enumerate(tokenlist): + if len(tok) == 0: + continue + ikey = tok[0] + if isinstance(ikey,int): + ikey = _ustr(tok[0]).strip() + if len(tok)==1: + tokenlist[ikey] = _ParseResultsWithOffset("",i) + elif len(tok)==2 and not isinstance(tok[1],ParseResults): + tokenlist[ikey] = _ParseResultsWithOffset(tok[1],i) + else: + dictvalue = tok.copy() #ParseResults(i) + del dictvalue[0] + if len(dictvalue)!= 1 or (isinstance(dictvalue,ParseResults) and dictvalue.keys()): + tokenlist[ikey] = _ParseResultsWithOffset(dictvalue,i) + else: + tokenlist[ikey] = _ParseResultsWithOffset(dictvalue[0],i) + + if self.resultsName: + return [ tokenlist ] + else: + return tokenlist + + +class Suppress(TokenConverter): + """Converter for ignoring the results of a parsed expression.""" + def postParse( self, instring, loc, tokenlist ): + return [] + + def suppress( self ): + return self + + +class OnlyOnce(object): + """Wrapper for parse actions, to ensure they are only called once.""" + def __init__(self, methodCall): + self.callable = ParserElement._normalizeParseActionArgs(methodCall) + self.called = False + def __call__(self,s,l,t): + if not self.called: + results = self.callable(s,l,t) + self.called = True + return results + raise ParseException(s,l,"") + def reset(self): + self.called = False + +def traceParseAction(f): + """Decorator for debugging parse actions.""" + f = ParserElement._normalizeParseActionArgs(f) + def z(*paArgs): + thisFunc = f.func_name + s,l,t = paArgs[-3:] + if len(paArgs)>3: + thisFunc = paArgs[0].__class__.__name__ + '.' + thisFunc + sys.stderr.write( ">>entering %s(line: '%s', %d, %s)\n" % (thisFunc,line(l,s),l,t) ) + try: + ret = f(*paArgs) + except Exception, exc: + sys.stderr.write( "<", "|".join( [ _escapeRegexChars(sym) for sym in symbols] )) + try: + if len(symbols)==len("".join(symbols)): + return Regex( "[%s]" % "".join( [ _escapeRegexRangeChars(sym) for sym in symbols] ) ) + else: + return Regex( "|".join( [ re.escape(sym) for sym in symbols] ) ) + except: + warnings.warn("Exception creating Regex for oneOf, building MatchFirst", + SyntaxWarning, stacklevel=2) + + + # last resort, just use MatchFirst + return MatchFirst( [ parseElementClass(sym) for sym in symbols ] ) + +def dictOf( key, value ): + """Helper to easily and clearly define a dictionary by specifying the respective patterns + for the key and value. Takes care of defining the Dict, ZeroOrMore, and Group tokens + in the proper order. The key pattern can include delimiting markers or punctuation, + as long as they are suppressed, thereby leaving the significant key text. The value + pattern can include named results, so that the Dict results can include named token + fields. + """ + return Dict( ZeroOrMore( Group ( key + value ) ) ) + +def originalTextFor(expr, asString=True): + """Helper to return the original, untokenized text for a given expression. Useful to + restore the parsed fields of an HTML start tag into the raw tag text itself, or to + revert separate tokens with intervening whitespace back to the original matching + input text. Simpler to use than the parse action keepOriginalText, and does not + require the inspect module to chase up the call stack. By default, returns a + string containing the original parsed text. + + If the optional asString argument is passed as False, then the return value is a + ParseResults containing any results names that were originally matched, and a + single token containing the original matched text from the input string. So if + the expression passed to originalTextFor contains expressions with defined + results names, you must set asString to False if you want to preserve those + results name values.""" + locMarker = Empty().setParseAction(lambda s,loc,t: loc) + matchExpr = locMarker("_original_start") + expr + locMarker("_original_end") + if asString: + extractText = lambda s,l,t: s[t._original_start:t._original_end] + else: + def extractText(s,l,t): + del t[:] + t.insert(0, s[t._original_start:t._original_end]) + del t["_original_start"] + del t["_original_end"] + matchExpr.setParseAction(extractText) + return matchExpr + +# convenience constants for positional expressions +empty = Empty().setName("empty") +lineStart = LineStart().setName("lineStart") +lineEnd = LineEnd().setName("lineEnd") +stringStart = StringStart().setName("stringStart") +stringEnd = StringEnd().setName("stringEnd") + +_escapedPunc = Word( _bslash, r"\[]-*.$+^?()~ ", exact=2 ).setParseAction(lambda s,l,t:t[0][1]) +_printables_less_backslash = "".join([ c for c in printables if c not in r"\]" ]) +_escapedHexChar = Combine( Suppress(_bslash + "0x") + Word(hexnums) ).setParseAction(lambda s,l,t:unichr(int(t[0],16))) +_escapedOctChar = Combine( Suppress(_bslash) + Word("0","01234567") ).setParseAction(lambda s,l,t:unichr(int(t[0],8))) +_singleChar = _escapedPunc | _escapedHexChar | _escapedOctChar | Word(_printables_less_backslash,exact=1) +_charRange = Group(_singleChar + Suppress("-") + _singleChar) +_reBracketExpr = Literal("[") + Optional("^").setResultsName("negate") + Group( OneOrMore( _charRange | _singleChar ) ).setResultsName("body") + "]" + +_expanded = lambda p: (isinstance(p,ParseResults) and ''.join([ unichr(c) for c in range(ord(p[0]),ord(p[1])+1) ]) or p) + +def srange(s): + r"""Helper to easily define string ranges for use in Word construction. Borrows + syntax from regexp '[]' string range definitions:: + srange("[0-9]") -> "0123456789" + srange("[a-z]") -> "abcdefghijklmnopqrstuvwxyz" + srange("[a-z$_]") -> "abcdefghijklmnopqrstuvwxyz$_" + The input string must be enclosed in []'s, and the returned string is the expanded + character set joined into a single string. + The values enclosed in the []'s may be:: + a single character + an escaped character with a leading backslash (such as \- or \]) + an escaped hex character with a leading '\0x' (\0x21, which is a '!' character) + an escaped octal character with a leading '\0' (\041, which is a '!' character) + a range of any of the above, separated by a dash ('a-z', etc.) + any combination of the above ('aeiouy', 'a-zA-Z0-9_$', etc.) + """ + try: + return "".join([_expanded(part) for part in _reBracketExpr.parseString(s).body]) + except: + return "" + +def matchOnlyAtCol(n): + """Helper method for defining parse actions that require matching at a specific + column in the input text. + """ + def verifyCol(strg,locn,toks): + if col(locn,strg) != n: + raise ParseException(strg,locn,"matched token not at column %d" % n) + return verifyCol + +def replaceWith(replStr): + """Helper method for common parse actions that simply return a literal value. Especially + useful when used with transformString(). + """ + def _replFunc(*args): + return [replStr] + return _replFunc + +def removeQuotes(s,l,t): + """Helper parse action for removing quotation marks from parsed quoted strings. + To use, add this parse action to quoted string using:: + quotedString.setParseAction( removeQuotes ) + """ + return t[0][1:-1] + +def upcaseTokens(s,l,t): + """Helper parse action to convert tokens to upper case.""" + return [ tt.upper() for tt in map(_ustr,t) ] + +def downcaseTokens(s,l,t): + """Helper parse action to convert tokens to lower case.""" + return [ tt.lower() for tt in map(_ustr,t) ] + +def keepOriginalText(s,startLoc,t): + """Helper parse action to preserve original parsed text, + overriding any nested parse actions.""" + try: + endloc = getTokensEndLoc() + except ParseException: + raise ParseFatalException("incorrect usage of keepOriginalText - may only be called as a parse action") + del t[:] + t += ParseResults(s[startLoc:endloc]) + return t + +def getTokensEndLoc(): + """Method to be called from within a parse action to determine the end + location of the parsed tokens.""" + import inspect + fstack = inspect.stack() + try: + # search up the stack (through intervening argument normalizers) for correct calling routine + for f in fstack[2:]: + if f[3] == "_parseNoCache": + endloc = f[0].f_locals["loc"] + return endloc + else: + raise ParseFatalException("incorrect usage of getTokensEndLoc - may only be called from within a parse action") + finally: + del fstack + +def _makeTags(tagStr, xml): + """Internal helper to construct opening and closing tag expressions, given a tag name""" + if isinstance(tagStr,basestring): + resname = tagStr + tagStr = Keyword(tagStr, caseless=not xml) + else: + resname = tagStr.name + + tagAttrName = Word(alphas,alphanums+"_-:") + if (xml): + tagAttrValue = dblQuotedString.copy().setParseAction( removeQuotes ) + openTag = Suppress("<") + tagStr + \ + Dict(ZeroOrMore(Group( tagAttrName + Suppress("=") + tagAttrValue ))) + \ + Optional("/",default=[False]).setResultsName("empty").setParseAction(lambda s,l,t:t[0]=='/') + Suppress(">") + else: + printablesLessRAbrack = "".join( [ c for c in printables if c not in ">" ] ) + tagAttrValue = quotedString.copy().setParseAction( removeQuotes ) | Word(printablesLessRAbrack) + openTag = Suppress("<") + tagStr + \ + Dict(ZeroOrMore(Group( tagAttrName.setParseAction(downcaseTokens) + \ + Optional( Suppress("=") + tagAttrValue ) ))) + \ + Optional("/",default=[False]).setResultsName("empty").setParseAction(lambda s,l,t:t[0]=='/') + Suppress(">") + closeTag = Combine(_L("") + + openTag = openTag.setResultsName("start"+"".join(resname.replace(":"," ").title().split())).setName("<%s>" % tagStr) + closeTag = closeTag.setResultsName("end"+"".join(resname.replace(":"," ").title().split())).setName("" % tagStr) + + return openTag, closeTag + +def makeHTMLTags(tagStr): + """Helper to construct opening and closing tag expressions for HTML, given a tag name""" + return _makeTags( tagStr, False ) + +def makeXMLTags(tagStr): + """Helper to construct opening and closing tag expressions for XML, given a tag name""" + return _makeTags( tagStr, True ) + +def withAttribute(*args,**attrDict): + """Helper to create a validating parse action to be used with start tags created + with makeXMLTags or makeHTMLTags. Use withAttribute to qualify a starting tag + with a required attribute value, to avoid false matches on common tags such as + or
. + + Call withAttribute with a series of attribute names and values. Specify the list + of filter attributes names and values as: + - keyword arguments, as in (class="Customer",align="right"), or + - a list of name-value tuples, as in ( ("ns1:class", "Customer"), ("ns2:align","right") ) + For attribute names with a namespace prefix, you must use the second form. Attribute + names are matched insensitive to upper/lower case. + + To verify that the attribute exists, but without specifying a value, pass + withAttribute.ANY_VALUE as the value. + """ + if args: + attrs = args[:] + else: + attrs = attrDict.items() + attrs = [(k,v) for k,v in attrs] + def pa(s,l,tokens): + for attrName,attrValue in attrs: + if attrName not in tokens: + raise ParseException(s,l,"no matching attribute " + attrName) + if attrValue != withAttribute.ANY_VALUE and tokens[attrName] != attrValue: + raise ParseException(s,l,"attribute '%s' has value '%s', must be '%s'" % + (attrName, tokens[attrName], attrValue)) + return pa +withAttribute.ANY_VALUE = object() + +opAssoc = _Constants() +opAssoc.LEFT = object() +opAssoc.RIGHT = object() + +def operatorPrecedence( baseExpr, opList ): + """Helper method for constructing grammars of expressions made up of + operators working in a precedence hierarchy. Operators may be unary or + binary, left- or right-associative. Parse actions can also be attached + to operator expressions. + + Parameters: + - baseExpr - expression representing the most basic element for the nested + - opList - list of tuples, one for each operator precedence level in the + expression grammar; each tuple is of the form + (opExpr, numTerms, rightLeftAssoc, parseAction), where: + - opExpr is the pyparsing expression for the operator; + may also be a string, which will be converted to a Literal; + if numTerms is 3, opExpr is a tuple of two expressions, for the + two operators separating the 3 terms + - numTerms is the number of terms for this operator (must + be 1, 2, or 3) + - rightLeftAssoc is the indicator whether the operator is + right or left associative, using the pyparsing-defined + constants opAssoc.RIGHT and opAssoc.LEFT. + - parseAction is the parse action to be associated with + expressions matching this operator expression (the + parse action tuple member may be omitted) + """ + ret = Forward() + lastExpr = baseExpr | ( Suppress('(') + ret + Suppress(')') ) + for i,operDef in enumerate(opList): + opExpr,arity,rightLeftAssoc,pa = (operDef + (None,))[:4] + if arity == 3: + if opExpr is None or len(opExpr) != 2: + raise ValueError("if numterms=3, opExpr must be a tuple or list of two expressions") + opExpr1, opExpr2 = opExpr + thisExpr = Forward()#.setName("expr%d" % i) + if rightLeftAssoc == opAssoc.LEFT: + if arity == 1: + matchExpr = FollowedBy(lastExpr + opExpr) + Group( lastExpr + OneOrMore( opExpr ) ) + elif arity == 2: + if opExpr is not None: + matchExpr = FollowedBy(lastExpr + opExpr + lastExpr) + Group( lastExpr + OneOrMore( opExpr + lastExpr ) ) + else: + matchExpr = FollowedBy(lastExpr+lastExpr) + Group( lastExpr + OneOrMore(lastExpr) ) + elif arity == 3: + matchExpr = FollowedBy(lastExpr + opExpr1 + lastExpr + opExpr2 + lastExpr) + \ + Group( lastExpr + opExpr1 + lastExpr + opExpr2 + lastExpr ) + else: + raise ValueError("operator must be unary (1), binary (2), or ternary (3)") + elif rightLeftAssoc == opAssoc.RIGHT: + if arity == 1: + # try to avoid LR with this extra test + if not isinstance(opExpr, Optional): + opExpr = Optional(opExpr) + matchExpr = FollowedBy(opExpr.expr + thisExpr) + Group( opExpr + thisExpr ) + elif arity == 2: + if opExpr is not None: + matchExpr = FollowedBy(lastExpr + opExpr + thisExpr) + Group( lastExpr + OneOrMore( opExpr + thisExpr ) ) + else: + matchExpr = FollowedBy(lastExpr + thisExpr) + Group( lastExpr + OneOrMore( thisExpr ) ) + elif arity == 3: + matchExpr = FollowedBy(lastExpr + opExpr1 + thisExpr + opExpr2 + thisExpr) + \ + Group( lastExpr + opExpr1 + thisExpr + opExpr2 + thisExpr ) + else: + raise ValueError("operator must be unary (1), binary (2), or ternary (3)") + else: + raise ValueError("operator must indicate right or left associativity") + if pa: + matchExpr.setParseAction( pa ) + thisExpr << ( matchExpr | lastExpr ) + lastExpr = thisExpr + ret << lastExpr + return ret + +dblQuotedString = Regex(r'"(?:[^"\n\r\\]|(?:"")|(?:\\x[0-9a-fA-F]+)|(?:\\.))*"').setName("string enclosed in double quotes") +sglQuotedString = Regex(r"'(?:[^'\n\r\\]|(?:'')|(?:\\x[0-9a-fA-F]+)|(?:\\.))*'").setName("string enclosed in single quotes") +quotedString = Regex(r'''(?:"(?:[^"\n\r\\]|(?:"")|(?:\\x[0-9a-fA-F]+)|(?:\\.))*")|(?:'(?:[^'\n\r\\]|(?:'')|(?:\\x[0-9a-fA-F]+)|(?:\\.))*')''').setName("quotedString using single or double quotes") +unicodeString = Combine(_L('u') + quotedString.copy()) + +def nestedExpr(opener="(", closer=")", content=None, ignoreExpr=quotedString): + """Helper method for defining nested lists enclosed in opening and closing + delimiters ("(" and ")" are the default). + + Parameters: + - opener - opening character for a nested list (default="("); can also be a pyparsing expression + - closer - closing character for a nested list (default=")"); can also be a pyparsing expression + - content - expression for items within the nested lists (default=None) + - ignoreExpr - expression for ignoring opening and closing delimiters (default=quotedString) + + If an expression is not provided for the content argument, the nested + expression will capture all whitespace-delimited content between delimiters + as a list of separate values. + + Use the ignoreExpr argument to define expressions that may contain + opening or closing characters that should not be treated as opening + or closing characters for nesting, such as quotedString or a comment + expression. Specify multiple expressions using an Or or MatchFirst. + The default is quotedString, but if no expressions are to be ignored, + then pass None for this argument. + """ + if opener == closer: + raise ValueError("opening and closing strings cannot be the same") + if content is None: + if isinstance(opener,basestring) and isinstance(closer,basestring): + if len(opener) == 1 and len(closer)==1: + if ignoreExpr is not None: + content = (Combine(OneOrMore(~ignoreExpr + + CharsNotIn(opener+closer+ParserElement.DEFAULT_WHITE_CHARS,exact=1)) + ).setParseAction(lambda t:t[0].strip())) + else: + content = (empty+CharsNotIn(opener+closer+ParserElement.DEFAULT_WHITE_CHARS + ).setParseAction(lambda t:t[0].strip())) + else: + if ignoreExpr is not None: + content = (Combine(OneOrMore(~ignoreExpr + + ~Literal(opener) + ~Literal(closer) + + CharsNotIn(ParserElement.DEFAULT_WHITE_CHARS,exact=1)) + ).setParseAction(lambda t:t[0].strip())) + else: + content = (Combine(OneOrMore(~Literal(opener) + ~Literal(closer) + + CharsNotIn(ParserElement.DEFAULT_WHITE_CHARS,exact=1)) + ).setParseAction(lambda t:t[0].strip())) + else: + raise ValueError("opening and closing arguments must be strings if no content expression is given") + ret = Forward() + if ignoreExpr is not None: + ret << Group( Suppress(opener) + ZeroOrMore( ignoreExpr | ret | content ) + Suppress(closer) ) + else: + ret << Group( Suppress(opener) + ZeroOrMore( ret | content ) + Suppress(closer) ) + return ret + +def indentedBlock(blockStatementExpr, indentStack, indent=True): + """Helper method for defining space-delimited indentation blocks, such as + those used to define block statements in Python source code. + + Parameters: + - blockStatementExpr - expression defining syntax of statement that + is repeated within the indented block + - indentStack - list created by caller to manage indentation stack + (multiple statementWithIndentedBlock expressions within a single grammar + should share a common indentStack) + - indent - boolean indicating whether block must be indented beyond the + the current level; set to False for block of left-most statements + (default=True) + + A valid block must contain at least one blockStatement. + """ + def checkPeerIndent(s,l,t): + if l >= len(s): return + curCol = col(l,s) + if curCol != indentStack[-1]: + if curCol > indentStack[-1]: + raise ParseFatalException(s,l,"illegal nesting") + raise ParseException(s,l,"not a peer entry") + + def checkSubIndent(s,l,t): + curCol = col(l,s) + if curCol > indentStack[-1]: + indentStack.append( curCol ) + else: + raise ParseException(s,l,"not a subentry") + + def checkUnindent(s,l,t): + if l >= len(s): return + curCol = col(l,s) + if not(indentStack and curCol < indentStack[-1] and curCol <= indentStack[-2]): + raise ParseException(s,l,"not an unindent") + indentStack.pop() + + NL = OneOrMore(LineEnd().setWhitespaceChars("\t ").suppress()) + INDENT = Empty() + Empty().setParseAction(checkSubIndent) + PEER = Empty().setParseAction(checkPeerIndent) + UNDENT = Empty().setParseAction(checkUnindent) + if indent: + smExpr = Group( Optional(NL) + + FollowedBy(blockStatementExpr) + + INDENT + (OneOrMore( PEER + Group(blockStatementExpr) + Optional(NL) )) + UNDENT) + else: + smExpr = Group( Optional(NL) + + (OneOrMore( PEER + Group(blockStatementExpr) + Optional(NL) )) ) + blockStatementExpr.ignore(_bslash + LineEnd()) + return smExpr + +alphas8bit = srange(r"[\0xc0-\0xd6\0xd8-\0xf6\0xf8-\0xff]") +punc8bit = srange(r"[\0xa1-\0xbf\0xd7\0xf7]") + +anyOpenTag,anyCloseTag = makeHTMLTags(Word(alphas,alphanums+"_:")) +commonHTMLEntity = Combine(_L("&") + oneOf("gt lt amp nbsp quot").setResultsName("entity") +";").streamline() +_htmlEntityMap = dict(zip("gt lt amp nbsp quot".split(),'><& "')) +replaceHTMLEntity = lambda t : t.entity in _htmlEntityMap and _htmlEntityMap[t.entity] or None + +# it's easy to get these comment structures wrong - they're very common, so may as well make them available +cStyleComment = Regex(r"/\*(?:[^*]*\*+)+?/").setName("C style comment") + +htmlComment = Regex(r"") +restOfLine = Regex(r".*").leaveWhitespace() +dblSlashComment = Regex(r"\/\/(\\\n|.)*").setName("// comment") +cppStyleComment = Regex(r"/(?:\*(?:[^*]*\*+)+?/|/[^\n]*(?:\n[^\n]*)*?(?:(?" + str(tokenlist)) + print ("tokens = " + str(tokens)) + print ("tokens.columns = " + str(tokens.columns)) + print ("tokens.tables = " + str(tokens.tables)) + print (tokens.asXML("SQL",True)) + except ParseBaseException,err: + print (teststring + "->") + print (err.line) + print (" "*(err.column-1) + "^") + print (err) + print() + + selectToken = CaselessLiteral( "select" ) + fromToken = CaselessLiteral( "from" ) + + ident = Word( alphas, alphanums + "_$" ) + columnName = delimitedList( ident, ".", combine=True ).setParseAction( upcaseTokens ) + columnNameList = Group( delimitedList( columnName ) )#.setName("columns") + tableName = delimitedList( ident, ".", combine=True ).setParseAction( upcaseTokens ) + tableNameList = Group( delimitedList( tableName ) )#.setName("tables") + simpleSQL = ( selectToken + \ + ( '*' | columnNameList ).setResultsName( "columns" ) + \ + fromToken + \ + tableNameList.setResultsName( "tables" ) ) + + test( "SELECT * from XYZZY, ABC" ) + test( "select * from SYS.XYZZY" ) + test( "Select A from Sys.dual" ) + test( "Select AA,BB,CC from Sys.dual" ) + test( "Select A, B, C from Sys.dual" ) + test( "Select A, B, C from Sys.dual" ) + test( "Xelect A, B, C from Sys.dual" ) + test( "Select A, B, C frox Sys.dual" ) + test( "Select" ) + test( "Select ^^^ frox Sys.dual" ) + test( "Select A, B, C from Sys.dual, Table2 " ) diff --git a/IPython/frontend/asyncfrontendbase.py b/IPython/frontend/asyncfrontendbase.py index cc7ada6..1171a83 100644 --- a/IPython/frontend/asyncfrontendbase.py +++ b/IPython/frontend/asyncfrontendbase.py @@ -3,6 +3,9 @@ Base front end class for all async frontends. """ __docformat__ = "restructuredtext en" +# Tell nose to skip this module +__test__ = {} + #------------------------------------------------------------------------------- # Copyright (C) 2008 The IPython Development Team # @@ -10,20 +13,25 @@ __docformat__ = "restructuredtext en" # the file COPYING, distributed as part of this software. #------------------------------------------------------------------------------- - #------------------------------------------------------------------------------- # Imports #------------------------------------------------------------------------------- +# Third-party +from twisted.python.failure import Failure +from zope.interface import implements, classProvides + +# From IPython from IPython.external import guid -from zope.interface import Interface, Attribute, implements, classProvides -from twisted.python.failure import Failure -from IPython.frontend.frontendbase import ( - FrontEndBase, IFrontEnd, IFrontEndFactory) +from IPython.frontend.frontendbase import (FrontEndBase, IFrontEnd, + IFrontEndFactory) from IPython.kernel.core.history import FrontEndHistory from IPython.kernel.engineservice import IEngineCore +#----------------------------------------------------------------------------- +# Classes and functions +#----------------------------------------------------------------------------- class AsyncFrontEndBase(FrontEndBase): """ @@ -41,8 +49,7 @@ class AsyncFrontEndBase(FrontEndBase): self.history = FrontEndHistory(input_cache=['']) else: self.history = history - - + def execute(self, block, blockID=None): """Execute the block and return the deferred result. @@ -73,5 +80,3 @@ class AsyncFrontEndBase(FrontEndBase): errback=self.render_error) return d - - diff --git a/IPython/frontend/cocoa/plugin/setup.py b/IPython/frontend/cocoa/plugin/setup.py index 8393085..11fdd25 100644 --- a/IPython/frontend/cocoa/plugin/setup.py +++ b/IPython/frontend/cocoa/plugin/setup.py @@ -19,17 +19,17 @@ __docformat__ = "restructuredtext en" from setuptools import setup infoPlist = dict( - CFBundleDevelopmentRegion='English', - CFBundleIdentifier='org.scipy.ipython.cocoa_frontend', - NSPrincipalClass='IPythonCocoaController', + CFBundleDevelopmentRegion='English', + CFBundleIdentifier='org.scipy.ipython.cocoa_frontend', + NSPrincipalClass='IPythonCocoaController', ) setup( - plugin=['IPythonCocoaFrontendLoader.py'], + plugin=['IPythonCocoaFrontendLoader.py'], setup_requires=['py2app'], - options=dict(py2app=dict( - plist=infoPlist, - site_packages=True, - excludes=['IPython','twisted','PyObjCTools'] - )), + options=dict(py2app=dict( + plist=infoPlist, + site_packages=True, + excludes=['IPython','twisted','PyObjCTools'] + )), ) \ No newline at end of file diff --git a/IPython/frontend/cocoa/tests/_trial_temp/test.log b/IPython/frontend/cocoa/tests/_trial_temp/test.log deleted file mode 100644 index 1fdde15..0000000 --- a/IPython/frontend/cocoa/tests/_trial_temp/test.log +++ /dev/null @@ -1,27 +0,0 @@ -2008-03-14 00:06:49-0700 [-] Log opened. -2008-03-14 00:06:49-0700 [-] --> ipython1.frontend.cocoa.tests.test_cocoa_frontend.TestIPythonCocoaControler.testControllerCompletesToken <-- -2008-03-14 00:06:49-0700 [-] Unhandled error in Deferred: -2008-03-14 00:06:49-0700 [-] Unhandled Error - Traceback (most recent call last): - File "/Library/Python/2.5/site-packages/Twisted-2.5.0_rUnknown-py2.5-macosx-10.3-i386.egg/twisted/internet/utils.py", line 144, in runWithWarningsSuppressed - result = f(*a, **kw) - File "/Users/barry/Desktop/ipython1-cocoa/ipython1/frontend/cocoa/tests/test_cocoa_frontend.py", line 94, in testControllerCompletesToken - self.controller.executeRequest([code]).addCallback(testCompletes) - File "/Library/Python/2.5/site-packages/Twisted-2.5.0_rUnknown-py2.5-macosx-10.3-i386.egg/twisted/internet/defer.py", line 196, in addCallback - callbackKeywords=kw) - File "/Library/Python/2.5/site-packages/Twisted-2.5.0_rUnknown-py2.5-macosx-10.3-i386.egg/twisted/internet/defer.py", line 187, in addCallbacks - self._runCallbacks() - --- --- - File "/Library/Python/2.5/site-packages/Twisted-2.5.0_rUnknown-py2.5-macosx-10.3-i386.egg/twisted/internet/defer.py", line 325, in _runCallbacks - self.result = callback(self.result, *args, **kw) - File "/Users/barry/Desktop/ipython1-cocoa/ipython1/frontend/cocoa/tests/test_cocoa_frontend.py", line 89, in testCompletes - self.assert_("longNameVariable" in result) - File "/Library/Python/2.5/site-packages/Twisted-2.5.0_rUnknown-py2.5-macosx-10.3-i386.egg/twisted/trial/unittest.py", line 136, in failUnless - raise self.failureException(msg) - twisted.trial.unittest.FailTest: None - -2008-03-14 00:06:49-0700 [-] --> ipython1.frontend.cocoa.tests.test_cocoa_frontend.TestIPythonCocoaControler.testControllerExecutesCode <-- -2008-03-14 00:06:49-0700 [-] --> ipython1.frontend.cocoa.tests.test_cocoa_frontend.TestIPythonCocoaControler.testControllerInstantiatesIEngineInteractive <-- -2008-03-14 00:06:49-0700 [-] --> ipython1.frontend.cocoa.tests.test_cocoa_frontend.TestIPythonCocoaControler.testControllerMirrorsUserNSWithValuesAsStrings <-- -2008-03-14 00:06:49-0700 [-] --> ipython1.frontend.cocoa.tests.test_cocoa_frontend.TestIPythonCocoaControler.testControllerRaisesCompilerErrorForIllegalCode <-- -2008-03-14 00:06:49-0700 [-] --> ipython1.frontend.cocoa.tests.test_cocoa_frontend.TestIPythonCocoaControler.testControllerReturnsNoneForIncompleteCode <-- diff --git a/IPython/frontend/linefrontendbase.py b/IPython/frontend/linefrontendbase.py index 1cb1ad6..18e0ba8 100644 --- a/IPython/frontend/linefrontendbase.py +++ b/IPython/frontend/linefrontendbase.py @@ -97,8 +97,8 @@ class LineFrontEndBase(FrontEndBase): ---------- line : string - Result - ------ + Returns + ------- The replacement for the line and the list of possible completions. """ completions = self.shell.complete(line) diff --git a/IPython/frontend/prefilterfrontend.py b/IPython/frontend/prefilterfrontend.py index ceb21c2..257669d 100644 --- a/IPython/frontend/prefilterfrontend.py +++ b/IPython/frontend/prefilterfrontend.py @@ -27,13 +27,12 @@ import os import re import __builtin__ -from IPython.ipmaker import make_IPython -from IPython.ipapi import IPApi +from IPython.core.ipmaker import make_IPython from IPython.kernel.core.redirector_output_trap import RedirectorOutputTrap from IPython.kernel.core.sync_traceback_trap import SyncTracebackTrap -from IPython.genutils import Term +from IPython.utils.genutils import Term from linefrontendbase import LineFrontEndBase, common_prefix @@ -65,8 +64,8 @@ class PrefilterFrontEnd(LineFrontEndBase): debug = False def __init__(self, ipython0=None, argv=None, *args, **kwargs): - """ Parameters: - ----------- + """ Parameters + ---------- ipython0: an optional ipython0 instance to use for command prefiltering and completion. @@ -80,7 +79,7 @@ class PrefilterFrontEnd(LineFrontEndBase): # on exceptions (https://bugs.launchpad.net/bugs/337105) # XXX: This is horrible: module-leve monkey patching -> side # effects. - from IPython import iplib + from IPython.core import iplib iplib.InteractiveShell.isthreaded = True LineFrontEndBase.__init__(self, *args, **kwargs) @@ -112,7 +111,7 @@ class PrefilterFrontEnd(LineFrontEndBase): self.ipython0.set_hook('show_in_pager', lambda s, string: self.write("\n" + string)) self.ipython0.write = self.write - self._ip = _ip = IPApi(self.ipython0) + self._ip = _ip = self.ipython0 # Make sure the raw system call doesn't get called, as we don't # have a stdin accessible. self._ip.system = self.system_call diff --git a/IPython/frontend/tests/test_asyncfrontendbase.py b/IPython/frontend/tests/test_asyncfrontendbase.py index fb497c8..b52659b 100644 --- a/IPython/frontend/tests/test_asyncfrontendbase.py +++ b/IPython/frontend/tests/test_asyncfrontendbase.py @@ -3,6 +3,9 @@ """This file contains unittests for the asyncfrontendbase module.""" __docformat__ = "restructuredtext en" + +# Tell nose to skip this module +__test__ = {} #--------------------------------------------------------------------------- # Copyright (C) 2008 The IPython Development Team @@ -10,20 +13,21 @@ __docformat__ = "restructuredtext en" # Distributed under the terms of the BSD License. The full license is in # the file COPYING, distributed as part of this software. #--------------------------------------------------------------------------- - + #--------------------------------------------------------------------------- # Imports #--------------------------------------------------------------------------- -# Tell nose to skip this module -__test__ = {} - from twisted.trial import unittest + from IPython.frontend.asyncfrontendbase import AsyncFrontEndBase from IPython.frontend import frontendbase from IPython.kernel.engineservice import EngineService from IPython.testing.parametric import Parametric, parametric +#----------------------------------------------------------------------------- +# Classes and functions +#----------------------------------------------------------------------------- class FrontEndCallbackChecker(AsyncFrontEndBase): """FrontEndBase subclass for checking callbacks""" @@ -106,4 +110,3 @@ class TestAsyncFrontendBase(unittest.TestCase): def test_history_returns_none_at_startup(self): self.assert_(self.fb.get_history_previous("")==None) self.assert_(self.fb.get_history_next()==None) - diff --git a/IPython/frontend/tests/test_prefilterfrontend.py b/IPython/frontend/tests/test_prefilterfrontend.py index a636029..653b3e6 100644 --- a/IPython/frontend/tests/test_prefilterfrontend.py +++ b/IPython/frontend/tests/test_prefilterfrontend.py @@ -20,7 +20,7 @@ import sys from nose.tools import assert_equal from IPython.frontend.prefilterfrontend import PrefilterFrontEnd -from IPython.ipapi import get as get_ipython0 +from IPython.core.ipapi import get as get_ipython0 from IPython.testing.plugin.ipdoctest import default_argv @@ -61,8 +61,8 @@ def isolate_ipython0(func): if ip0 is None: return func() # We have a real ipython running... - user_ns = ip0.IP.user_ns - user_global_ns = ip0.IP.user_global_ns + user_ns = ip0.user_ns + user_global_ns = ip0.user_global_ns # Previously the isolation was attempted with a deep copy of the user # dicts, but we found cases where this didn't work correctly. I'm not @@ -86,7 +86,7 @@ def isolate_ipython0(func): for k in new_globals: del user_global_ns[k] # Undo the hack at creation of PrefilterFrontEnd - from IPython import iplib + from IPython.core import iplib iplib.InteractiveShell.isthreaded = False return out diff --git a/IPython/frontend/wx/console_widget.py b/IPython/frontend/wx/console_widget.py index 8c7967f..b3b7245 100644 --- a/IPython/frontend/wx/console_widget.py +++ b/IPython/frontend/wx/console_widget.py @@ -105,8 +105,8 @@ ANSI_STYLES = {'0;30': [0, 'BLACK'], '0;31': [1, 'RED'], [13, 'MEDIUM VIOLET RED'], '1;36': [14, 'LIGHT STEEL BLUE'], '1;37': [15, 'YELLOW']} -# XXX: Maybe one day we should factor this code with ColorANSI. Right now -# ColorANSI is hard to reuse and makes our code more complex. +# XXX: Maybe one day we should factor this code with coloransi. Right now +# coloransi is hard to reuse and makes our code more complex. #we define platform specific fonts if wx.Platform == '__WXMSW__': diff --git a/IPython/frontend/wx/ipythonx.py b/IPython/frontend/wx/ipythonx.py index af7d322..d95d709 100644 --- a/IPython/frontend/wx/ipythonx.py +++ b/IPython/frontend/wx/ipythonx.py @@ -6,11 +6,10 @@ ipython. try: import wx except ImportError, e: - e.message = """%s + e.args[0] = """%s ________________________________________________________________________________ You need wxPython to run this application. -""" % e.message - e.args = (e.message, ) + e.args[1:] +""" % e.args[0] raise e from wx_frontend import WxController diff --git a/IPython/generics.py b/IPython/generics.py deleted file mode 100644 index b38b978..0000000 --- a/IPython/generics.py +++ /dev/null @@ -1,54 +0,0 @@ -''' 'Generic' functions for extending IPython. - -See http://cheeseshop.python.org/pypi/simplegeneric. - -Here is an example from genutils.py: - - def print_lsstring(arg): - "Prettier (non-repr-like) and more informative printer for LSString" - print "LSString (.p, .n, .l, .s available). Value:" - print arg - - print_lsstring = result_display.when_type(LSString)(print_lsstring) - -(Yes, the nasty syntax is for python 2.3 compatibility. Your own extensions -can use the niftier decorator syntax introduced in Python 2.4). -''' - -from IPython.ipapi import TryNext -from IPython.external.simplegeneric import generic - -def result_display(result): - """ print the result of computation """ - raise TryNext - -result_display = generic(result_display) - -def inspect_object(obj): - """ Called when you do obj? """ - raise TryNext -inspect_object = generic(inspect_object) - -def complete_object(obj, prev_completions): - """ Custom completer dispatching for python objects - - obj is the object itself. - prev_completions is the list of attributes discovered so far. - - This should return the list of attributes in obj. If you only wish to - add to the attributes already discovered normally, return - own_attrs + prev_completions. - """ - - raise TryNext -complete_object = generic(complete_object) - -#import os -#def my_demo_complete_object(obj, prev_completions): -# """ Demo completer that adds 'foobar' to the completions suggested -# for any object that has attribute (path), e.g. 'os'""" -# if hasattr(obj,'path'): -# return prev_completions + ['foobar'] -# raise TryNext -# -#my_demo_complete_object = complete_object.when_type(type(os))(my_demo_complete_object) diff --git a/IPython/gui/wx/ipshell_nonblocking.py b/IPython/gui/wx/ipshell_nonblocking.py index def47c5..d4194d6 100644 --- a/IPython/gui/wx/ipshell_nonblocking.py +++ b/IPython/gui/wx/ipshell_nonblocking.py @@ -25,6 +25,8 @@ from thread_ex import ThreadEx try: import IPython + from IPython.utils import genutils + from IPython.core import iplib except Exception,e: print "Error importing IPython (%s)" % str(e) raise Exception, e @@ -44,7 +46,7 @@ class _Helper(object): def __call__(self, *args, **kwds): class DummyWriter(object): - '''Dumy class to handle help output''' + '''Dumy class to handle help output''' def __init__(self, pager): self._pager = pager @@ -143,22 +145,22 @@ class NonBlockingIPShell(object): #only one instance can be instanciated else tehre will be #cin/cout/cerr clash... if cin: - IPython.genutils.Term.cin = cin + genutils.Term.cin = cin if cout: - IPython.genutils.Term.cout = cout + genutils.Term.cout = cout if cerr: - IPython.genutils.Term.cerr = cerr + genutils.Term.cerr = cerr excepthook = sys.excepthook #Hack to save sys.displayhook, because ipython seems to overwrite it... self.sys_displayhook_ori = sys.displayhook - self._IP = IPython.Shell.make_IPython( + self._IP = IPython.shell.make_IPython( argv,user_ns=user_ns, user_global_ns=user_global_ns, embedded=True, - shell_class=IPython.Shell.InteractiveShell) + shell_class=IPython.shell.InteractiveShell) #we save ipython0 displayhook and we restore sys.displayhook self.displayhook = sys.displayhook @@ -176,18 +178,18 @@ class NonBlockingIPShell(object): self._IP.set_hook('shell_hook', self._shell) #we replace the ipython default input command caller by our method - IPython.iplib.raw_input_original = self._raw_input_original + iplib.raw_input_original = self._raw_input_original #we replace the ipython default exit command by our method self._IP.exit = ask_exit_handler #we replace the help command self._IP.user_ns['help'] = _Helper(self._pager_help) #we disable cpase magic... until we found a way to use it properly. - #import IPython.ipapi - ip = IPython.ipapi.get() + from IPython.core import ipapi + ip = ipapi.get() def bypass_magic(self, arg): print '%this magic is currently disabled.' - ip.expose_magic('cpaste', bypass_magic) + ip.define_magic('cpaste', bypass_magic) import __builtin__ __builtin__.raw_input = self._raw_input @@ -468,7 +470,7 @@ class NonBlockingIPShell(object): ''' orig_stdout = sys.stdout - sys.stdout = IPython.Shell.Term.cout + sys.stdout = IPython.shell.Term.cout #self.sys_displayhook_ori = sys.displayhook #sys.displayhook = self.displayhook @@ -490,9 +492,9 @@ class NonBlockingIPShell(object): self._IP.showtraceback() else: self._IP.write(str(self._IP.outputcache.prompt_out).strip()) - self._iter_more = self._IP.push(line) + self._iter_more = self._IP.push_line(line) if (self._IP.SyntaxTB.last_syntax_error and \ - self._IP.rc.autoedit_syntax): + self._IP.autoedit_syntax): self._IP.edit_syntax_error() if self._iter_more: self._prompt = str(self._IP.outputcache.prompt2).strip() diff --git a/IPython/gui/wx/ipython_view.py b/IPython/gui/wx/ipython_view.py old mode 100755 new mode 100644 index 2319b28..2fc44c3 --- a/IPython/gui/wx/ipython_view.py +++ b/IPython/gui/wx/ipython_view.py @@ -76,12 +76,12 @@ class WxNonBlockingIPShell(NonBlockingIPShell): """ A replacement from python's raw_input. """ self.answer = None - if(self._threading == True): - wx.CallAfter(self._yesNoBox, prompt) - while self.answer is None: - time.sleep(.1) + if(self._threading == True): + wx.CallAfter(self._yesNoBox, prompt) + while self.answer is None: + time.sleep(.1) else: - self._yesNoBox(prompt) + self._yesNoBox(prompt) return self.answer def _yesNoBox(self, prompt): diff --git a/IPython/gui/wx/wxIPython.py b/IPython/gui/wx/wxIPython.py index b724e53..b5158b7 100644 --- a/IPython/gui/wx/wxIPython.py +++ b/IPython/gui/wx/wxIPython.py @@ -19,7 +19,7 @@ except ImportError: is_sync_frontend_ok = False #used to create options.conf file in user directory -from IPython.ipapi import get +from IPython.core.ipapi import get __version__ = 0.91 __author__ = "Laurent Dufrechou" @@ -109,7 +109,7 @@ class MyFrame(wx.Frame): def optionSave(self, name, value): ip = get() - path = ip.IP.rc.ipythondir + path = ip.ipython_dir opt = open(path + '/options.conf','w') try: @@ -126,7 +126,7 @@ class MyFrame(wx.Frame): def optionLoad(self): try: ip = get() - path = ip.IP.rc.ipythondir + path = ip.ipython_dir opt = open(path + '/options.conf','r') lines = opt.readlines() opt.close() diff --git a/IPython/ipapi.py b/IPython/ipapi.py deleted file mode 100644 index da6bcf4..0000000 --- a/IPython/ipapi.py +++ /dev/null @@ -1,685 +0,0 @@ -"""IPython customization API - -Your one-stop module for configuring & extending ipython - -The API will probably break when ipython 1.0 is released, but so -will the other configuration method (rc files). - -All names prefixed by underscores are for internal use, not part -of the public api. - -Below is an example that you can just put to a module and import from ipython. - -A good practice is to install the config script below as e.g. - -~/.ipython/my_private_conf.py - -And do - -import_mod my_private_conf - -in ~/.ipython/ipythonrc - -That way the module is imported at startup and you can have all your -personal configuration (as opposed to boilerplate ipythonrc-PROFILENAME -stuff) in there. - -import IPython.ipapi -ip = IPython.ipapi.get() - -def ankka_f(self, arg): - print 'Ankka',self,'says uppercase:',arg.upper() - -ip.expose_magic('ankka',ankka_f) - -ip.magic('alias sayhi echo "Testing, hi ok"') -ip.magic('alias helloworld echo "Hello world"') -ip.system('pwd') - -ip.ex('import re') -ip.ex(''' -def funcci(a,b): - print a+b -print funcci(3,4) -''') -ip.ex('funcci(348,9)') - -def jed_editor(self,filename, linenum=None): - print 'Calling my own editor, jed ... via hook!' - import os - if linenum is None: linenum = 0 - os.system('jed +%d %s' % (linenum, filename)) - print 'exiting jed' - -ip.set_hook('editor',jed_editor) - -o = ip.options -o.autocall = 2 # FULL autocall mode - -print 'done!' -""" - -#----------------------------------------------------------------------------- -# Modules and globals - -# stdlib imports -import __builtin__ -import sys - -# contains the most recently instantiated IPApi -_RECENT_IP = None - -#----------------------------------------------------------------------------- -# Code begins - -class TryNext(Exception): - """Try next hook exception. - - Raise this in your hook function to indicate that the next hook handler - should be used to handle the operation. If you pass arguments to the - constructor those arguments will be used by the next hook instead of the - original ones. - """ - - def __init__(self, *args, **kwargs): - self.args = args - self.kwargs = kwargs - - -class UsageError(Exception): - """ Error in magic function arguments, etc. - - Something that probably won't warrant a full traceback, but should - nevertheless interrupt a macro / batch file. - """ - - -class IPyAutocall: - """ Instances of this class are always autocalled - - This happens regardless of 'autocall' variable state. Use this to - develop macro-like mechanisms. - """ - - def set_ip(self,ip): - """ Will be used to set _ip point to current ipython instance b/f call - - Override this method if you don't want this to happen. - - """ - self._ip = ip - - -class IPythonNotRunning: - """Dummy do-nothing class. - - Instances of this class return a dummy attribute on all accesses, which - can be called and warns. This makes it easier to write scripts which use - the ipapi.get() object for informational purposes to operate both with and - without ipython. Obviously code which uses the ipython object for - computations will not work, but this allows a wider range of code to - transparently work whether ipython is being used or not.""" - - def __init__(self,warn=True): - if warn: - self.dummy = self._dummy_warn - else: - self.dummy = self._dummy_silent - - def __str__(self): - return "" - - __repr__ = __str__ - - def __getattr__(self,name): - return self.dummy - - def _dummy_warn(self,*args,**kw): - """Dummy function, which doesn't do anything but warn.""" - - print ("IPython is not running, this is a dummy no-op function") - - def _dummy_silent(self,*args,**kw): - """Dummy function, which doesn't do anything and emits no warnings.""" - pass - - -def get(allow_dummy=False,dummy_warn=True): - """Get an IPApi object. - - If allow_dummy is true, returns an instance of IPythonNotRunning - instead of None if not running under IPython. - - If dummy_warn is false, the dummy instance will be completely silent. - - Running this should be the first thing you do when writing extensions that - can be imported as normal modules. You can then direct all the - configuration operations against the returned object. - """ - global _RECENT_IP - if allow_dummy and not _RECENT_IP: - _RECENT_IP = IPythonNotRunning(dummy_warn) - return _RECENT_IP - - -class IPApi(object): - """ The actual API class for configuring IPython - - You should do all of the IPython configuration by getting an IPApi object - with IPython.ipapi.get() and using the attributes and methods of the - returned object.""" - - def __init__(self,ip): - - global _RECENT_IP - - # All attributes exposed here are considered to be the public API of - # IPython. As needs dictate, some of these may be wrapped as - # properties. - - self.magic = ip.ipmagic - - self.system = ip.system - - self.set_hook = ip.set_hook - - self.set_custom_exc = ip.set_custom_exc - - self.user_ns = ip.user_ns - - self.set_crash_handler = ip.set_crash_handler - - # Session-specific data store, which can be used to store - # data that should persist through the ipython session. - self.meta = ip.meta - - # The ipython instance provided - self.IP = ip - - self.extensions = {} - - self.dbg = DebugTools(self) - - _RECENT_IP = self - - # Use a property for some things which are added to the instance very - # late. I don't have time right now to disentangle the initialization - # order issues, so a property lets us delay item extraction while - # providing a normal attribute API. - def get_db(self): - """A handle to persistent dict-like database (a PickleShareDB object)""" - return self.IP.db - - db = property(get_db,None,None,get_db.__doc__) - - def get_options(self): - """All configurable variables.""" - - # catch typos by disabling new attribute creation. If new attr creation - # is in fact wanted (e.g. when exposing new options), do - # allow_new_attr(True) for the received rc struct. - - self.IP.rc.allow_new_attr(False) - return self.IP.rc - - options = property(get_options,None,None,get_options.__doc__) - - def expose_magic(self,magicname, func): - """Expose own function as magic function for ipython - - def foo_impl(self,parameter_s=''): - 'My very own magic!. (Use docstrings, IPython reads them).' - print 'Magic function. Passed parameter is between < >:' - print '<%s>' % parameter_s - print 'The self object is:',self - - ipapi.expose_magic('foo',foo_impl) - """ - - import new - im = new.instancemethod(func,self.IP, self.IP.__class__) - old = getattr(self.IP, "magic_" + magicname, None) - if old: - self.dbg.debug_stack("Magic redefinition '%s', old %s" % - (magicname,old) ) - - setattr(self.IP, "magic_" + magicname, im) - - def ex(self,cmd): - """ Execute a normal python statement in user namespace """ - exec cmd in self.user_ns - - def ev(self,expr): - """ Evaluate python expression expr in user namespace - - Returns the result of evaluation""" - return eval(expr,self.user_ns) - - def runlines(self,lines): - """ Run the specified lines in interpreter, honoring ipython directives. - - This allows %magic and !shell escape notations. - - Takes either all lines in one string or list of lines. - """ - - def cleanup_ipy_script(script): - """ Make a script safe for _ip.runlines() - - - Removes empty lines Suffixes all indented blocks that end with - - unindented lines with empty lines - """ - - res = [] - lines = script.splitlines() - - level = 0 - for l in lines: - lstripped = l.lstrip() - stripped = l.strip() - if not stripped: - continue - newlevel = len(l) - len(lstripped) - def is_secondary_block_start(s): - if not s.endswith(':'): - return False - if (s.startswith('elif') or - s.startswith('else') or - s.startswith('except') or - s.startswith('finally')): - return True - - if level > 0 and newlevel == 0 and \ - not is_secondary_block_start(stripped): - # add empty line - res.append('') - - res.append(l) - level = newlevel - return '\n'.join(res) + '\n' - - if isinstance(lines,basestring): - script = lines - else: - script = '\n'.join(lines) - clean=cleanup_ipy_script(script) - # print "_ip.runlines() script:\n",clean # dbg - self.IP.runlines(clean) - - def to_user_ns(self,vars, interactive = True): - """Inject a group of variables into the IPython user namespace. - - Inputs: - - - vars: string with variable names separated by whitespace, or a - dict with name/value pairs. - - - interactive: if True (default), the var will be listed with - %whos et. al. - - This utility routine is meant to ease interactive debugging work, - where you want to easily propagate some internal variable in your code - up to the interactive namespace for further exploration. - - When you run code via %run, globals in your script become visible at - the interactive prompt, but this doesn't happen for locals inside your - own functions and methods. Yet when debugging, it is common to want - to explore some internal variables further at the interactive propmt. - - Examples: - - To use this, you first must obtain a handle on the ipython object as - indicated above, via: - - import IPython.ipapi - ip = IPython.ipapi.get() - - Once this is done, inside a routine foo() where you want to expose - variables x and y, you do the following: - - def foo(): - ... - x = your_computation() - y = something_else() - - # This pushes x and y to the interactive prompt immediately, even - # if this routine crashes on the next line after: - ip.to_user_ns('x y') - ... - - # To expose *ALL* the local variables from the function, use: - ip.to_user_ns(locals()) - - ... - # return - - - If you need to rename variables, the dict input makes it easy. For - example, this call exposes variables 'foo' as 'x' and 'bar' as 'y' - in IPython user namespace: - - ip.to_user_ns(dict(x=foo,y=bar)) - """ - - # print 'vars given:',vars # dbg - - # We need a dict of name/value pairs to do namespace updates. - if isinstance(vars,dict): - # If a dict was given, no need to change anything. - vdict = vars - elif isinstance(vars,basestring): - # If a string with names was given, get the caller's frame to - # evaluate the given names in - cf = sys._getframe(1) - vdict = {} - for name in vars.split(): - try: - vdict[name] = eval(name,cf.f_globals,cf.f_locals) - except: - print ('could not get var. %s from %s' % - (name,cf.f_code.co_name)) - else: - raise ValueError('vars must be a string or a dict') - - # Propagate variables to user namespace - self.user_ns.update(vdict) - - # And configure interactive visibility - config_ns = self.IP.user_config_ns - if interactive: - for name,val in vdict.iteritems(): - config_ns.pop(name,None) - else: - for name,val in vdict.iteritems(): - config_ns[name] = val - - def expand_alias(self,line): - """ Expand an alias in the command line - - Returns the provided command line, possibly with the first word - (command) translated according to alias expansion rules. - - [ipython]|16> _ip.expand_aliases("np myfile.txt") - <16> 'q:/opt/np/notepad++.exe myfile.txt' - """ - - pre,fn,rest = self.IP.split_user_input(line) - res = pre + self.IP.expand_aliases(fn,rest) - return res - - def itpl(self, s, depth = 1): - """ Expand Itpl format string s. - - Only callable from command line (i.e. prefilter results); - If you use in your scripts, you need to use a bigger depth! - """ - return self.IP.var_expand(s, depth) - - def defalias(self, name, cmd): - """ Define a new alias - - _ip.defalias('bb','bldmake bldfiles') - - Creates a new alias named 'bb' in ipython user namespace - """ - - self.dbg.check_hotname(name) - - if name in self.IP.alias_table: - self.dbg.debug_stack("Alias redefinition: '%s' => '%s' (old '%s')" - % (name, cmd, self.IP.alias_table[name])) - - if callable(cmd): - self.IP.alias_table[name] = cmd - import IPython.shadowns - setattr(IPython.shadowns, name,cmd) - return - - if isinstance(cmd,basestring): - nargs = cmd.count('%s') - if nargs>0 and cmd.find('%l')>=0: - raise Exception('The %s and %l specifiers are mutually ' - 'exclusive in alias definitions.') - - self.IP.alias_table[name] = (nargs,cmd) - return - - # just put it in - it's probably (0,'foo') - self.IP.alias_table[name] = cmd - - def defmacro(self, *args): - """ Define a new macro - - 2 forms of calling: - - mac = _ip.defmacro('print "hello"\nprint "world"') - - (doesn't put the created macro on user namespace) - - _ip.defmacro('build', 'bldmake bldfiles\nabld build winscw udeb') - - (creates a macro named 'build' in user namespace) - """ - - import IPython.macro - - if len(args) == 1: - return IPython.macro.Macro(args[0]) - elif len(args) == 2: - self.user_ns[args[0]] = IPython.macro.Macro(args[1]) - else: - return Exception("_ip.defmacro must be called with 1 or 2 arguments") - - def set_next_input(self, s): - """ Sets the 'default' input string for the next command line. - - Requires readline. - - Example: - - [D:\ipython]|1> _ip.set_next_input("Hello Word") - [D:\ipython]|2> Hello Word_ # cursor is here - """ - - self.IP.rl_next_input = s - - def load(self, mod): - """ Load an extension. - - Some modules should (or must) be 'load()':ed, rather than just imported. - - Loading will do: - - - run init_ipython(ip) - - run ipython_firstrun(ip) - """ - - if mod in self.extensions: - # just to make sure we don't init it twice - # note that if you 'load' a module that has already been - # imported, init_ipython gets run anyway - - return self.extensions[mod] - __import__(mod) - m = sys.modules[mod] - if hasattr(m,'init_ipython'): - m.init_ipython(self) - - if hasattr(m,'ipython_firstrun'): - already_loaded = self.db.get('firstrun_done', set()) - if mod not in already_loaded: - m.ipython_firstrun(self) - already_loaded.add(mod) - self.db['firstrun_done'] = already_loaded - - self.extensions[mod] = m - return m - - -class DebugTools: - """ Used for debugging mishaps in api usage - - So far, tracing redefinitions is supported. - """ - - def __init__(self, ip): - self.ip = ip - self.debugmode = False - self.hotnames = set() - - def hotname(self, name_to_catch): - self.hotnames.add(name_to_catch) - - def debug_stack(self, msg = None): - if not self.debugmode: - return - - import traceback - if msg is not None: - print '====== %s ========' % msg - traceback.print_stack() - - def check_hotname(self,name): - if name in self.hotnames: - self.debug_stack( "HotName '%s' caught" % name) - - -def launch_new_instance(user_ns = None,shellclass = None): - """ Make and start a new ipython instance. - - This can be called even without having an already initialized - ipython session running. - - This is also used as the egg entry point for the 'ipython' script. - - """ - ses = make_session(user_ns,shellclass) - ses.mainloop() - - -def make_user_ns(user_ns = None): - """Return a valid user interactive namespace. - - This builds a dict with the minimal information needed to operate as a - valid IPython user namespace, which you can pass to the various embedding - classes in ipython. - - This API is currently deprecated. Use ipapi.make_user_namespaces() instead - to make both the local and global namespace objects simultaneously. - - :Parameters: - user_ns : dict-like, optional - The current user namespace. The items in this namespace should be - included in the output. If None, an appropriate blank namespace - should be created. - - :Returns: - A dictionary-like object to be used as the local namespace of the - interpreter. - """ - - raise NotImplementedError - - -def make_user_global_ns(ns = None): - """Return a valid user global namespace. - - Similar to make_user_ns(), but global namespaces are really only needed in - embedded applications, where there is a distinction between the user's - interactive namespace and the global one where ipython is running. - - This API is currently deprecated. Use ipapi.make_user_namespaces() instead - to make both the local and global namespace objects simultaneously. - - :Parameters: - ns : dict, optional - The current user global namespace. The items in this namespace - should be included in the output. If None, an appropriate blank - namespace should be created. - - :Returns: - A true dict to be used as the global namespace of the interpreter. - """ - - raise NotImplementedError - -# Record the true objects in order to be able to test if the user has overridden -# these API functions. -_make_user_ns = make_user_ns -_make_user_global_ns = make_user_global_ns - - -def make_user_namespaces(user_ns = None,user_global_ns = None): - """Return a valid local and global user interactive namespaces. - - This builds a dict with the minimal information needed to operate as a - valid IPython user namespace, which you can pass to the various embedding - classes in ipython. The default implementation returns the same dict for - both the locals and the globals to allow functions to refer to variables in - the namespace. Customized implementations can return different dicts. The - locals dictionary can actually be anything following the basic mapping - protocol of a dict, but the globals dict must be a true dict, not even - a subclass. It is recommended that any custom object for the locals - namespace synchronize with the globals dict somehow. - - Raises TypeError if the provided globals namespace is not a true dict. - - :Parameters: - user_ns : dict-like, optional - The current user namespace. The items in this namespace should be - included in the output. If None, an appropriate blank namespace - should be created. - user_global_ns : dict, optional - The current user global namespace. The items in this namespace - should be included in the output. If None, an appropriate blank - namespace should be created. - - :Returns: - A tuple pair of dictionary-like object to be used as the local namespace - of the interpreter and a dict to be used as the global namespace. - """ - - if user_ns is None: - if make_user_ns is not _make_user_ns: - # Old API overridden. - # FIXME: Issue DeprecationWarning, or just let the old API live on? - user_ns = make_user_ns(user_ns) - else: - # Set __name__ to __main__ to better match the behavior of the - # normal interpreter. - user_ns = {'__name__' :'__main__', - '__builtins__' : __builtin__, - } - else: - user_ns.setdefault('__name__','__main__') - user_ns.setdefault('__builtins__',__builtin__) - - if user_global_ns is None: - if make_user_global_ns is not _make_user_global_ns: - # Old API overridden. - user_global_ns = make_user_global_ns(user_global_ns) - else: - user_global_ns = user_ns - if type(user_global_ns) is not dict: - raise TypeError("user_global_ns must be a true dict; got %r" - % type(user_global_ns)) - - return user_ns, user_global_ns - - -def make_session(user_ns = None, shellclass = None): - """Makes, but does not launch an IPython session. - - Later on you can call obj.mainloop() on the returned object. - - Inputs: - - - user_ns(None): a dict to be used as the user's namespace with initial - data. - - WARNING: This should *not* be run when a session exists already.""" - - import IPython.Shell - if shellclass is None: - return IPython.Shell.start(user_ns) - return shellclass(user_ns = user_ns) diff --git a/IPython/iplib.py b/IPython/iplib.py index 0de50ec..95dcc88 100644 --- a/IPython/iplib.py +++ b/IPython/iplib.py @@ -1,2870 +1,28 @@ -# -*- coding: utf-8 -*- +#!/usr/bin/env python +# encoding: utf-8 """ -IPython -- An enhanced Interactive Python +A backwards compatibility layer for IPython.iplib. -Requires Python 2.4 or newer. - -This file contains all the classes and helper functions specific to IPython. +Previously, IPython had an IPython.iplib module. IPython.iplib has been moved +to IPython.core.iplib and is being refactored. This new module is provided +for backwards compatability. We strongly encourage everyone to start using +the new code in IPython.core.iplib. """ -#***************************************************************************** -# Copyright (C) 2001 Janko Hauser and -# Copyright (C) 2001-2006 Fernando Perez. +#----------------------------------------------------------------------------- +# Copyright (C) 2008-2009 The IPython Development Team # # Distributed under the terms of the BSD License. The full license is in # the file COPYING, distributed as part of this software. -# -# Note: this code originally subclassed code.InteractiveConsole from the -# Python standard library. Over time, all of that class has been copied -# verbatim here for modifications which could not be accomplished by -# subclassing. At this point, there are no dependencies at all on the code -# module anymore (it is not even imported). The Python License (sec. 2) -# allows for this, but it's always nice to acknowledge credit where credit is -# due. -#***************************************************************************** - -#**************************************************************************** -# Modules and globals - -# Python standard modules -import __main__ -import __builtin__ -import StringIO -import bdb -import cPickle as pickle -import codeop -import exceptions -import glob -import inspect -import keyword -import new -import os -import pydoc -import re -import shutil -import string -import sys -import tempfile -import traceback -import types -from pprint import pprint, pformat - -# IPython's own modules -#import IPython -from IPython import Debugger,OInspect,PyColorize,ultraTB -from IPython.ColorANSI import ColorScheme,ColorSchemeTable # too long names -from IPython.Extensions import pickleshare -from IPython.FakeModule import FakeModule, init_fakemod_dict -from IPython.Itpl import Itpl,itpl,printpl,ItplNS,itplns -from IPython.Logger import Logger -from IPython.Magic import Magic -from IPython.Prompts import CachedOutput -from IPython.ipstruct import Struct -from IPython.background_jobs import BackgroundJobManager -from IPython.usage import cmd_line_usage,interactive_usage -from IPython.genutils import * -from IPython.strdispatch import StrDispatch -import IPython.ipapi -import IPython.history -import IPython.prefilter as prefilter -import IPython.shadowns -# Globals - -# store the builtin raw_input globally, and use this always, in case user code -# overwrites it (like wx.py.PyShell does) -raw_input_original = raw_input - -# compiled regexps for autoindent management -dedent_re = re.compile(r'^\s+raise|^\s+return|^\s+pass') - - -#**************************************************************************** -# Some utility function definitions - -ini_spaces_re = re.compile(r'^(\s+)') - -def num_ini_spaces(strng): - """Return the number of initial spaces in a string""" - - ini_spaces = ini_spaces_re.match(strng) - if ini_spaces: - return ini_spaces.end() - else: - return 0 - -def softspace(file, newvalue): - """Copied from code.py, to remove the dependency""" - - oldvalue = 0 - try: - oldvalue = file.softspace - except AttributeError: - pass - try: - file.softspace = newvalue - except (AttributeError, TypeError): - # "attribute-less object" or "read-only attributes" - pass - return oldvalue - - -def user_setup(ipythondir,rc_suffix,mode='install',interactive=True): - """Install or upgrade the user configuration directory. - - Can be called when running for the first time or to upgrade the user's - .ipython/ directory. - - Parameters - ---------- - ipythondir : path - The directory to be used for installation/upgrade. In 'install' mode, - if this path already exists, the function exits immediately. - - rc_suffix : str - Extension for the config files. On *nix platforms it is typically the - empty string, while Windows normally uses '.ini'. - - mode : str, optional - Valid modes are 'install' and 'upgrade'. - - interactive : bool, optional - If False, do not wait for user input on any errors. Normally after - printing its status information, this function waits for the user to - hit Return before proceeding. This is because the default use case is - when first installing the IPython configuration, so we want the user to - acknowledge the initial message, which contains some useful - information. - """ - - # For automatic use, deactivate all i/o - if interactive: - def wait(): - try: - raw_input("Please press to start IPython.") - except EOFError: - print >> Term.cout - print '*'*70 - - def printf(s): - print s - else: - wait = lambda : None - printf = lambda s : None - - # Install mode should be re-entrant: if the install dir already exists, - # bail out cleanly. - # XXX. This is too hasty to return. We need to check to make sure that - # all the expected config files and directories are actually there. We - # currently have a failure mode if someone deletes a needed config file - # but still has the ipythondir. - if mode == 'install' and os.path.isdir(ipythondir): - return - - cwd = os.getcwd() # remember where we started - glb = glob.glob - - printf('*'*70) - if mode == 'install': - printf( -"""Welcome to IPython. I will try to create a personal configuration directory -where you can customize many aspects of IPython's functionality in:\n""") - else: - printf('I am going to upgrade your configuration in:') - - printf(ipythondir) - - rcdirend = os.path.join('IPython','UserConfig') - cfg = lambda d: os.path.join(d,rcdirend) - try: - rcdir = filter(os.path.isdir,map(cfg,sys.path))[0] - printf("Initializing from configuration: %s" % rcdir) - except IndexError: - warning = """ -Installation error. IPython's directory was not found. - -Check the following: - -The ipython/IPython directory should be in a directory belonging to your -PYTHONPATH environment variable (that is, it should be in a directory -belonging to sys.path). You can copy it explicitly there or just link to it. - -IPython will create a minimal default configuration for you. - -""" - warn(warning) - wait() - - if sys.platform =='win32': - inif = 'ipythonrc.ini' - else: - inif = 'ipythonrc' - minimal_setup = {'ipy_user_conf.py' : 'import ipy_defaults', - inif : '# intentionally left blank' } - os.makedirs(ipythondir, mode = 0777) - for f, cont in minimal_setup.items(): - # In 2.5, this can be more cleanly done using 'with' - fobj = file(ipythondir + '/' + f,'w') - fobj.write(cont) - fobj.close() - - return - - if mode == 'install': - try: - shutil.copytree(rcdir,ipythondir) - os.chdir(ipythondir) - rc_files = glb("ipythonrc*") - for rc_file in rc_files: - os.rename(rc_file,rc_file+rc_suffix) - except: - warning = """ - -There was a problem with the installation: -%s -Try to correct it or contact the developers if you think it's a bug. -IPython will proceed with builtin defaults.""" % sys.exc_info()[1] - warn(warning) - wait() - return - - elif mode == 'upgrade': - try: - os.chdir(ipythondir) - except: - printf(""" -Can not upgrade: changing to directory %s failed. Details: -%s -""" % (ipythondir,sys.exc_info()[1]) ) - wait() - return - else: - sources = glb(os.path.join(rcdir,'[A-Za-z]*')) - for new_full_path in sources: - new_filename = os.path.basename(new_full_path) - if new_filename.startswith('ipythonrc'): - new_filename = new_filename + rc_suffix - # The config directory should only contain files, skip any - # directories which may be there (like CVS) - if os.path.isdir(new_full_path): - continue - if os.path.exists(new_filename): - old_file = new_filename+'.old' - if os.path.exists(old_file): - os.remove(old_file) - os.rename(new_filename,old_file) - shutil.copy(new_full_path,new_filename) - else: - raise ValueError('unrecognized mode for install: %r' % mode) - - # Fix line-endings to those native to each platform in the config - # directory. - try: - os.chdir(ipythondir) - except: - printf(""" -Problem: changing to directory %s failed. -Details: -%s - -Some configuration files may have incorrect line endings. This should not -cause any problems during execution. """ % (ipythondir,sys.exc_info()[1]) ) - wait() - else: - for fname in glb('ipythonrc*'): - try: - native_line_ends(fname,backup=0) - except IOError: - pass - - if mode == 'install': - printf(""" -Successful installation! - -Please read the sections 'Initial Configuration' and 'Quick Tips' in the -IPython manual (there are both HTML and PDF versions supplied with the -distribution) to make sure that your system environment is properly configured -to take advantage of IPython's features. - -Important note: the configuration system has changed! The old system is -still in place, but its setting may be partly overridden by the settings in -"~/.ipython/ipy_user_conf.py" config file. Please take a look at the file -if some of the new settings bother you. - -""") - else: - printf(""" -Successful upgrade! - -All files in your directory: -%(ipythondir)s -which would have been overwritten by the upgrade were backed up with a .old -extension. If you had made particular customizations in those files you may -want to merge them back into the new files.""" % locals() ) - wait() - os.chdir(cwd) - -#**************************************************************************** -# Local use exceptions -class SpaceInInput(exceptions.Exception): pass - - -#**************************************************************************** -# Local use classes -class Bunch: pass - -class Undefined: pass - -class Quitter(object): - """Simple class to handle exit, similar to Python 2.5's. - - It handles exiting in an ipython-safe manner, which the one in Python 2.5 - doesn't do (obviously, since it doesn't know about ipython).""" - - def __init__(self,shell,name): - self.shell = shell - self.name = name - - def __repr__(self): - return 'Type %s() to exit.' % self.name - __str__ = __repr__ - - def __call__(self): - self.shell.exit() - -class InputList(list): - """Class to store user input. - - It's basically a list, but slices return a string instead of a list, thus - allowing things like (assuming 'In' is an instance): - - exec In[4:7] - - or - - exec In[5:9] + In[14] + In[21:25]""" - - def __getslice__(self,i,j): - return ''.join(list.__getslice__(self,i,j)) - -class SyntaxTB(ultraTB.ListTB): - """Extension which holds some state: the last exception value""" - - def __init__(self,color_scheme = 'NoColor'): - ultraTB.ListTB.__init__(self,color_scheme) - self.last_syntax_error = None - - def __call__(self, etype, value, elist): - self.last_syntax_error = value - ultraTB.ListTB.__call__(self,etype,value,elist) - - def clear_err_state(self): - """Return the current error state and clear it""" - e = self.last_syntax_error - self.last_syntax_error = None - return e - -#**************************************************************************** -# Main IPython class - -# FIXME: the Magic class is a mixin for now, and will unfortunately remain so -# until a full rewrite is made. I've cleaned all cross-class uses of -# attributes and methods, but too much user code out there relies on the -# equlity %foo == __IP.magic_foo, so I can't actually remove the mixin usage. -# -# But at least now, all the pieces have been separated and we could, in -# principle, stop using the mixin. This will ease the transition to the -# chainsaw branch. - -# For reference, the following is the list of 'self.foo' uses in the Magic -# class as of 2005-12-28. These are names we CAN'T use in the main ipython -# class, to prevent clashes. - -# ['self.__class__', 'self.__dict__', 'self._inspect', 'self._ofind', -# 'self.arg_err', 'self.extract_input', 'self.format_', 'self.lsmagic', -# 'self.magic_', 'self.options_table', 'self.parse', 'self.shell', -# 'self.value'] - -class InteractiveShell(object,Magic): - """An enhanced console for Python.""" - - # class attribute to indicate whether the class supports threads or not. - # Subclasses with thread support should override this as needed. - isthreaded = False - - def __init__(self,name,usage=None,rc=Struct(opts=None,args=None), - user_ns=None,user_global_ns=None,banner2='', - custom_exceptions=((),None),embedded=False): - - # log system - self.logger = Logger(self,logfname='ipython_log.py',logmode='rotate') - - # Job manager (for jobs run as background threads) - self.jobs = BackgroundJobManager() - - # Store the actual shell's name - self.name = name - self.more = False - - # We need to know whether the instance is meant for embedding, since - # global/local namespaces need to be handled differently in that case - self.embedded = embedded - if embedded: - # Control variable so users can, from within the embedded instance, - # permanently deactivate it. - self.embedded_active = True - - # command compiler - self.compile = codeop.CommandCompiler() - - # User input buffer - self.buffer = [] - - # Default name given in compilation of code - self.filename = '' - - # Install our own quitter instead of the builtins. For python2.3-2.4, - # this brings in behavior like 2.5, and for 2.5 it's identical. - __builtin__.exit = Quitter(self,'exit') - __builtin__.quit = Quitter(self,'quit') - - # Make an empty namespace, which extension writers can rely on both - # existing and NEVER being used by ipython itself. This gives them a - # convenient location for storing additional information and state - # their extensions may require, without fear of collisions with other - # ipython names that may develop later. - self.meta = Struct() - - # Create the namespace where the user will operate. user_ns is - # normally the only one used, and it is passed to the exec calls as - # the locals argument. But we do carry a user_global_ns namespace - # given as the exec 'globals' argument, This is useful in embedding - # situations where the ipython shell opens in a context where the - # distinction between locals and globals is meaningful. For - # non-embedded contexts, it is just the same object as the user_ns dict. - - # FIXME. For some strange reason, __builtins__ is showing up at user - # level as a dict instead of a module. This is a manual fix, but I - # should really track down where the problem is coming from. Alex - # Schmolck reported this problem first. - - # A useful post by Alex Martelli on this topic: - # Re: inconsistent value from __builtins__ - # Von: Alex Martelli - # Datum: Freitag 01 Oktober 2004 04:45:34 nachmittags/abends - # Gruppen: comp.lang.python - - # Michael Hohn wrote: - # > >>> print type(builtin_check.get_global_binding('__builtins__')) - # > - # > >>> print type(__builtins__) - # > - # > Is this difference in return value intentional? - - # Well, it's documented that '__builtins__' can be either a dictionary - # or a module, and it's been that way for a long time. Whether it's - # intentional (or sensible), I don't know. In any case, the idea is - # that if you need to access the built-in namespace directly, you - # should start with "import __builtin__" (note, no 's') which will - # definitely give you a module. Yeah, it's somewhat confusing:-(. - - # These routines return properly built dicts as needed by the rest of - # the code, and can also be used by extension writers to generate - # properly initialized namespaces. - user_ns, user_global_ns = IPython.ipapi.make_user_namespaces(user_ns, - user_global_ns) - - # Assign namespaces - # This is the namespace where all normal user variables live - self.user_ns = user_ns - self.user_global_ns = user_global_ns - - # An auxiliary namespace that checks what parts of the user_ns were - # loaded at startup, so we can list later only variables defined in - # actual interactive use. Since it is always a subset of user_ns, it - # doesn't need to be seaparately tracked in the ns_table - self.user_config_ns = {} - - # A namespace to keep track of internal data structures to prevent - # them from cluttering user-visible stuff. Will be updated later - self.internal_ns = {} - - # Namespace of system aliases. Each entry in the alias - # table must be a 2-tuple of the form (N,name), where N is the number - # of positional arguments of the alias. - self.alias_table = {} - - # Now that FakeModule produces a real module, we've run into a nasty - # problem: after script execution (via %run), the module where the user - # code ran is deleted. Now that this object is a true module (needed - # so docetst and other tools work correctly), the Python module - # teardown mechanism runs over it, and sets to None every variable - # present in that module. Top-level references to objects from the - # script survive, because the user_ns is updated with them. However, - # calling functions defined in the script that use other things from - # the script will fail, because the function's closure had references - # to the original objects, which are now all None. So we must protect - # these modules from deletion by keeping a cache. - # - # To avoid keeping stale modules around (we only need the one from the - # last run), we use a dict keyed with the full path to the script, so - # only the last version of the module is held in the cache. Note, - # however, that we must cache the module *namespace contents* (their - # __dict__). Because if we try to cache the actual modules, old ones - # (uncached) could be destroyed while still holding references (such as - # those held by GUI objects that tend to be long-lived)> - # - # The %reset command will flush this cache. See the cache_main_mod() - # and clear_main_mod_cache() methods for details on use. - - # This is the cache used for 'main' namespaces - self._main_ns_cache = {} - # And this is the single instance of FakeModule whose __dict__ we keep - # copying and clearing for reuse on each %run - self._user_main_module = FakeModule() - - # A table holding all the namespaces IPython deals with, so that - # introspection facilities can search easily. - self.ns_table = {'user':user_ns, - 'user_global':user_global_ns, - 'alias':self.alias_table, - 'internal':self.internal_ns, - 'builtin':__builtin__.__dict__ - } - - # Similarly, track all namespaces where references can be held and that - # we can safely clear (so it can NOT include builtin). This one can be - # a simple list. - self.ns_refs_table = [ user_ns, user_global_ns, self.user_config_ns, - self.alias_table, self.internal_ns, - self._main_ns_cache ] - - # We need to insert into sys.modules something that looks like a - # module but which accesses the IPython namespace, for shelve and - # pickle to work interactively. Normally they rely on getting - # everything out of __main__, but for embedding purposes each IPython - # instance has its own private namespace, so we can't go shoving - # everything into __main__. - - # note, however, that we should only do this for non-embedded - # ipythons, which really mimic the __main__.__dict__ with their own - # namespace. Embedded instances, on the other hand, should not do - # this because they need to manage the user local/global namespaces - # only, but they live within a 'normal' __main__ (meaning, they - # shouldn't overtake the execution environment of the script they're - # embedded in). - - if not embedded: - try: - main_name = self.user_ns['__name__'] - except KeyError: - raise KeyError,'user_ns dictionary MUST have a "__name__" key' - else: - #print "pickle hack in place" # dbg - #print 'main_name:',main_name # dbg - sys.modules[main_name] = FakeModule(self.user_ns) - - # List of input with multi-line handling. - self.input_hist = InputList() - # This one will hold the 'raw' input history, without any - # pre-processing. This will allow users to retrieve the input just as - # it was exactly typed in by the user, with %hist -r. - self.input_hist_raw = InputList() - - # list of visited directories - try: - self.dir_hist = [os.getcwd()] - except OSError: - self.dir_hist = [] - - # dict of output history - self.output_hist = {} - - # Get system encoding at startup time. Certain terminals (like Emacs - # under Win32 have it set to None, and we need to have a known valid - # encoding to use in the raw_input() method - try: - self.stdin_encoding = sys.stdin.encoding or 'ascii' - except AttributeError: - self.stdin_encoding = 'ascii' - - # dict of things NOT to alias (keywords, builtins and some magics) - no_alias = {} - no_alias_magics = ['cd','popd','pushd','dhist','alias','unalias'] - for key in keyword.kwlist + no_alias_magics: - no_alias[key] = 1 - no_alias.update(__builtin__.__dict__) - self.no_alias = no_alias - - # Object variable to store code object waiting execution. This is - # used mainly by the multithreaded shells, but it can come in handy in - # other situations. No need to use a Queue here, since it's a single - # item which gets cleared once run. - self.code_to_run = None - - # escapes for automatic behavior on the command line - self.ESC_SHELL = '!' - self.ESC_SH_CAP = '!!' - self.ESC_HELP = '?' - self.ESC_MAGIC = '%' - self.ESC_QUOTE = ',' - self.ESC_QUOTE2 = ';' - self.ESC_PAREN = '/' - - # And their associated handlers - self.esc_handlers = {self.ESC_PAREN : self.handle_auto, - self.ESC_QUOTE : self.handle_auto, - self.ESC_QUOTE2 : self.handle_auto, - self.ESC_MAGIC : self.handle_magic, - self.ESC_HELP : self.handle_help, - self.ESC_SHELL : self.handle_shell_escape, - self.ESC_SH_CAP : self.handle_shell_escape, - } - - # class initializations - Magic.__init__(self,self) - - # Python source parser/formatter for syntax highlighting - pyformat = PyColorize.Parser().format - self.pycolorize = lambda src: pyformat(src,'str',self.rc['colors']) - - # hooks holds pointers used for user-side customizations - self.hooks = Struct() - - self.strdispatchers = {} - - # Set all default hooks, defined in the IPython.hooks module. - hooks = IPython.hooks - for hook_name in hooks.__all__: - # default hooks have priority 100, i.e. low; user hooks should have - # 0-100 priority - self.set_hook(hook_name,getattr(hooks,hook_name), 100) - #print "bound hook",hook_name - - # Flag to mark unconditional exit - self.exit_now = False - - self.usage_min = """\ - An enhanced console for Python. - Some of its features are: - - Readline support if the readline library is present. - - Tab completion in the local namespace. - - Logging of input, see command-line options. - - System shell escape via ! , eg !ls. - - Magic commands, starting with a % (like %ls, %pwd, %cd, etc.) - - Keeps track of locally defined variables via %who, %whos. - - Show object information with a ? eg ?x or x? (use ?? for more info). - """ - if usage: self.usage = usage - else: self.usage = self.usage_min - - # Storage - self.rc = rc # This will hold all configuration information - self.pager = 'less' - # temporary files used for various purposes. Deleted at exit. - self.tempfiles = [] - - # Keep track of readline usage (later set by init_readline) - self.has_readline = False - - # template for logfile headers. It gets resolved at runtime by the - # logstart method. - self.loghead_tpl = \ -"""#log# Automatic Logger file. *** THIS MUST BE THE FIRST LINE *** -#log# DO NOT CHANGE THIS LINE OR THE TWO BELOW -#log# opts = %s -#log# args = %s -#log# It is safe to make manual edits below here. -#log#----------------------------------------------------------------------- -""" - # for pushd/popd management - try: - self.home_dir = get_home_dir() - except HomeDirError,msg: - fatal(msg) - - self.dir_stack = [] - - # Functions to call the underlying shell. - - # The first is similar to os.system, but it doesn't return a value, - # and it allows interpolation of variables in the user's namespace. - self.system = lambda cmd: \ - self.hooks.shell_hook(self.var_expand(cmd,depth=2)) - - # These are for getoutput and getoutputerror: - self.getoutput = lambda cmd: \ - getoutput(self.var_expand(cmd,depth=2), - header=self.rc.system_header, - verbose=self.rc.system_verbose) - - self.getoutputerror = lambda cmd: \ - getoutputerror(self.var_expand(cmd,depth=2), - header=self.rc.system_header, - verbose=self.rc.system_verbose) - - - # keep track of where we started running (mainly for crash post-mortem) - self.starting_dir = os.getcwd() - - # Various switches which can be set - self.CACHELENGTH = 5000 # this is cheap, it's just text - self.BANNER = "Python %(version)s on %(platform)s\n" % sys.__dict__ - self.banner2 = banner2 - - # TraceBack handlers: - - # Syntax error handler. - self.SyntaxTB = SyntaxTB(color_scheme='NoColor') - - # The interactive one is initialized with an offset, meaning we always - # want to remove the topmost item in the traceback, which is our own - # internal code. Valid modes: ['Plain','Context','Verbose'] - self.InteractiveTB = ultraTB.AutoFormattedTB(mode = 'Plain', - color_scheme='NoColor', - tb_offset = 1) - - # IPython itself shouldn't crash. This will produce a detailed - # post-mortem if it does. But we only install the crash handler for - # non-threaded shells, the threaded ones use a normal verbose reporter - # and lose the crash handler. This is because exceptions in the main - # thread (such as in GUI code) propagate directly to sys.excepthook, - # and there's no point in printing crash dumps for every user exception. - if self.isthreaded: - ipCrashHandler = ultraTB.FormattedTB() - else: - from IPython import CrashHandler - ipCrashHandler = CrashHandler.IPythonCrashHandler(self) - self.set_crash_handler(ipCrashHandler) - - # and add any custom exception handlers the user may have specified - self.set_custom_exc(*custom_exceptions) - - # indentation management - self.autoindent = False - self.indent_current_nsp = 0 - - # Make some aliases automatically - # Prepare list of shell aliases to auto-define - if os.name == 'posix': - auto_alias = ('mkdir mkdir', 'rmdir rmdir', - 'mv mv -i','rm rm -i','cp cp -i', - 'cat cat','less less','clear clear', - # a better ls - 'ls ls -F', - # long ls - 'll ls -lF') - # Extra ls aliases with color, which need special treatment on BSD - # variants - ls_extra = ( # color ls - 'lc ls -F -o --color', - # ls normal files only - 'lf ls -F -o --color %l | grep ^-', - # ls symbolic links - 'lk ls -F -o --color %l | grep ^l', - # directories or links to directories, - 'ldir ls -F -o --color %l | grep /$', - # things which are executable - 'lx ls -F -o --color %l | grep ^-..x', - ) - # The BSDs don't ship GNU ls, so they don't understand the - # --color switch out of the box - if 'bsd' in sys.platform: - ls_extra = ( # ls normal files only - 'lf ls -lF | grep ^-', - # ls symbolic links - 'lk ls -lF | grep ^l', - # directories or links to directories, - 'ldir ls -lF | grep /$', - # things which are executable - 'lx ls -lF | grep ^-..x', - ) - auto_alias = auto_alias + ls_extra - elif os.name in ['nt','dos']: - auto_alias = ('ls dir /on', - 'ddir dir /ad /on', 'ldir dir /ad /on', - 'mkdir mkdir','rmdir rmdir','echo echo', - 'ren ren','cls cls','copy copy') - else: - auto_alias = () - self.auto_alias = [s.split(None,1) for s in auto_alias] - - # Produce a public API instance - self.api = IPython.ipapi.IPApi(self) - - # Initialize all user-visible namespaces - self.init_namespaces() - - # Call the actual (public) initializer - self.init_auto_alias() - - # track which builtins we add, so we can clean up later - self.builtins_added = {} - # This method will add the necessary builtins for operation, but - # tracking what it did via the builtins_added dict. - - #TODO: remove this, redundant - self.add_builtins() - # end __init__ - - def var_expand(self,cmd,depth=0): - """Expand python variables in a string. - - The depth argument indicates how many frames above the caller should - be walked to look for the local namespace where to expand variables. - - The global namespace for expansion is always the user's interactive - namespace. - """ - - return str(ItplNS(cmd, - self.user_ns, # globals - # Skip our own frame in searching for locals: - sys._getframe(depth+1).f_locals # locals - )) - - def pre_config_initialization(self): - """Pre-configuration init method - - This is called before the configuration files are processed to - prepare the services the config files might need. - - self.rc already has reasonable default values at this point. - """ - rc = self.rc - try: - self.db = pickleshare.PickleShareDB(rc.ipythondir + "/db") - except exceptions.UnicodeDecodeError: - print "Your ipythondir can't be decoded to unicode!" - print "Please set HOME environment variable to something that" - print r"only has ASCII characters, e.g. c:\home" - print "Now it is",rc.ipythondir - sys.exit() - self.shadowhist = IPython.history.ShadowHist(self.db) - - def post_config_initialization(self): - """Post configuration init method - - This is called after the configuration files have been processed to - 'finalize' the initialization.""" - - rc = self.rc - - # Object inspector - self.inspector = OInspect.Inspector(OInspect.InspectColors, - PyColorize.ANSICodeColors, - 'NoColor', - rc.object_info_string_level) - - self.rl_next_input = None - self.rl_do_indent = False - # Load readline proper - if rc.readline: - self.init_readline() - - # local shortcut, this is used a LOT - self.log = self.logger.log - - # Initialize cache, set in/out prompts and printing system - self.outputcache = CachedOutput(self, - rc.cache_size, - rc.pprint, - input_sep = rc.separate_in, - output_sep = rc.separate_out, - output_sep2 = rc.separate_out2, - ps1 = rc.prompt_in1, - ps2 = rc.prompt_in2, - ps_out = rc.prompt_out, - pad_left = rc.prompts_pad_left) - - # user may have over-ridden the default print hook: - try: - self.outputcache.__class__.display = self.hooks.display - except AttributeError: - pass - - # I don't like assigning globally to sys, because it means when - # embedding instances, each embedded instance overrides the previous - # choice. But sys.displayhook seems to be called internally by exec, - # so I don't see a way around it. We first save the original and then - # overwrite it. - self.sys_displayhook = sys.displayhook - sys.displayhook = self.outputcache - - # Do a proper resetting of doctest, including the necessary displayhook - # monkeypatching - try: - doctest_reload() - except ImportError: - warn("doctest module does not exist.") - - # Set user colors (don't do it in the constructor above so that it - # doesn't crash if colors option is invalid) - self.magic_colors(rc.colors) - - # Set calling of pdb on exceptions - self.call_pdb = rc.pdb - - # Load user aliases - for alias in rc.alias: - self.magic_alias(alias) - - self.hooks.late_startup_hook() - - for cmd in self.rc.autoexec: - #print "autoexec>",cmd #dbg - self.api.runlines(cmd) - - batchrun = False - for batchfile in [path(arg) for arg in self.rc.args - if arg.lower().endswith('.ipy')]: - if not batchfile.isfile(): - print "No such batch file:", batchfile - continue - self.api.runlines(batchfile.text()) - batchrun = True - # without -i option, exit after running the batch file - if batchrun and not self.rc.interact: - self.ask_exit() - - def init_namespaces(self): - """Initialize all user-visible namespaces to their minimum defaults. - - Certain history lists are also initialized here, as they effectively - act as user namespaces. - - Note - ---- - All data structures here are only filled in, they are NOT reset by this - method. If they were not empty before, data will simply be added to - therm. - """ - # The user namespace MUST have a pointer to the shell itself. - self.user_ns[self.name] = self - - # Store the public api instance - self.user_ns['_ip'] = self.api - - # make global variables for user access to the histories - self.user_ns['_ih'] = self.input_hist - self.user_ns['_oh'] = self.output_hist - self.user_ns['_dh'] = self.dir_hist - - # user aliases to input and output histories - self.user_ns['In'] = self.input_hist - self.user_ns['Out'] = self.output_hist - - self.user_ns['_sh'] = IPython.shadowns - - # Fill the history zero entry, user counter starts at 1 - self.input_hist.append('\n') - self.input_hist_raw.append('\n') - - def add_builtins(self): - """Store ipython references into the builtin namespace. - - Some parts of ipython operate via builtins injected here, which hold a - reference to IPython itself.""" - - # TODO: deprecate all of these, they are unsafe - builtins_new = dict(__IPYTHON__ = self, - ip_set_hook = self.set_hook, - jobs = self.jobs, - ipmagic = wrap_deprecated(self.ipmagic,'_ip.magic()'), - ipalias = wrap_deprecated(self.ipalias), - ipsystem = wrap_deprecated(self.ipsystem,'_ip.system()'), - #_ip = self.api - ) - for biname,bival in builtins_new.items(): - try: - # store the orignal value so we can restore it - self.builtins_added[biname] = __builtin__.__dict__[biname] - except KeyError: - # or mark that it wasn't defined, and we'll just delete it at - # cleanup - self.builtins_added[biname] = Undefined - __builtin__.__dict__[biname] = bival - - # Keep in the builtins a flag for when IPython is active. We set it - # with setdefault so that multiple nested IPythons don't clobber one - # another. Each will increase its value by one upon being activated, - # which also gives us a way to determine the nesting level. - __builtin__.__dict__.setdefault('__IPYTHON__active',0) - - def clean_builtins(self): - """Remove any builtins which might have been added by add_builtins, or - restore overwritten ones to their previous values.""" - for biname,bival in self.builtins_added.items(): - if bival is Undefined: - del __builtin__.__dict__[biname] - else: - __builtin__.__dict__[biname] = bival - self.builtins_added.clear() - - def set_hook(self,name,hook, priority = 50, str_key = None, re_key = None): - """set_hook(name,hook) -> sets an internal IPython hook. - - IPython exposes some of its internal API as user-modifiable hooks. By - adding your function to one of these hooks, you can modify IPython's - behavior to call at runtime your own routines.""" - - # At some point in the future, this should validate the hook before it - # accepts it. Probably at least check that the hook takes the number - # of args it's supposed to. - - f = new.instancemethod(hook,self,self.__class__) - - # check if the hook is for strdispatcher first - if str_key is not None: - sdp = self.strdispatchers.get(name, StrDispatch()) - sdp.add_s(str_key, f, priority ) - self.strdispatchers[name] = sdp - return - if re_key is not None: - sdp = self.strdispatchers.get(name, StrDispatch()) - sdp.add_re(re.compile(re_key), f, priority ) - self.strdispatchers[name] = sdp - return - - dp = getattr(self.hooks, name, None) - if name not in IPython.hooks.__all__: - print "Warning! Hook '%s' is not one of %s" % (name, IPython.hooks.__all__ ) - if not dp: - dp = IPython.hooks.CommandChainDispatcher() - - try: - dp.add(f,priority) - except AttributeError: - # it was not commandchain, plain old func - replace - dp = f - - setattr(self.hooks,name, dp) - - - #setattr(self.hooks,name,new.instancemethod(hook,self,self.__class__)) - - def set_crash_handler(self,crashHandler): - """Set the IPython crash handler. - - This must be a callable with a signature suitable for use as - sys.excepthook.""" - - # Install the given crash handler as the Python exception hook - sys.excepthook = crashHandler - - # The instance will store a pointer to this, so that runtime code - # (such as magics) can access it. This is because during the - # read-eval loop, it gets temporarily overwritten (to deal with GUI - # frameworks). - self.sys_excepthook = sys.excepthook - - - def set_custom_exc(self,exc_tuple,handler): - """set_custom_exc(exc_tuple,handler) - - Set a custom exception handler, which will be called if any of the - exceptions in exc_tuple occur in the mainloop (specifically, in the - runcode() method. - - Inputs: - - - exc_tuple: a *tuple* of valid exceptions to call the defined - handler for. It is very important that you use a tuple, and NOT A - LIST here, because of the way Python's except statement works. If - you only want to trap a single exception, use a singleton tuple: - - exc_tuple == (MyCustomException,) - - - handler: this must be defined as a function with the following - basic interface: def my_handler(self,etype,value,tb). - - This will be made into an instance method (via new.instancemethod) - of IPython itself, and it will be called if any of the exceptions - listed in the exc_tuple are caught. If the handler is None, an - internal basic one is used, which just prints basic info. - - WARNING: by putting in your own exception handler into IPython's main - execution loop, you run a very good chance of nasty crashes. This - facility should only be used if you really know what you are doing.""" - - assert type(exc_tuple)==type(()) , \ - "The custom exceptions must be given AS A TUPLE." - - def dummy_handler(self,etype,value,tb): - print '*** Simple custom exception handler ***' - print 'Exception type :',etype - print 'Exception value:',value - print 'Traceback :',tb - print 'Source code :','\n'.join(self.buffer) - - if handler is None: handler = dummy_handler - - self.CustomTB = new.instancemethod(handler,self,self.__class__) - self.custom_exceptions = exc_tuple - - def set_custom_completer(self,completer,pos=0): - """set_custom_completer(completer,pos=0) - - Adds a new custom completer function. - - The position argument (defaults to 0) is the index in the completers - list where you want the completer to be inserted.""" - - newcomp = new.instancemethod(completer,self.Completer, - self.Completer.__class__) - self.Completer.matchers.insert(pos,newcomp) - - def set_completer(self): - """reset readline's completer to be our own.""" - self.readline.set_completer(self.Completer.complete) - - def _get_call_pdb(self): - return self._call_pdb - - def _set_call_pdb(self,val): - - if val not in (0,1,False,True): - raise ValueError,'new call_pdb value must be boolean' - - # store value in instance - self._call_pdb = val - - # notify the actual exception handlers - self.InteractiveTB.call_pdb = val - if self.isthreaded: - try: - self.sys_excepthook.call_pdb = val - except: - warn('Failed to activate pdb for threaded exception handler') - - call_pdb = property(_get_call_pdb,_set_call_pdb,None, - 'Control auto-activation of pdb at exceptions') - - # These special functions get installed in the builtin namespace, to - # provide programmatic (pure python) access to magics, aliases and system - # calls. This is important for logging, user scripting, and more. - - # We are basically exposing, via normal python functions, the three - # mechanisms in which ipython offers special call modes (magics for - # internal control, aliases for direct system access via pre-selected - # names, and !cmd for calling arbitrary system commands). - - def ipmagic(self,arg_s): - """Call a magic function by name. - - Input: a string containing the name of the magic function to call and any - additional arguments to be passed to the magic. - - ipmagic('name -opt foo bar') is equivalent to typing at the ipython - prompt: - - In[1]: %name -opt foo bar - - To call a magic without arguments, simply use ipmagic('name'). - - This provides a proper Python function to call IPython's magics in any - valid Python code you can type at the interpreter, including loops and - compound statements. It is added by IPython to the Python builtin - namespace upon initialization.""" - - args = arg_s.split(' ',1) - magic_name = args[0] - magic_name = magic_name.lstrip(self.ESC_MAGIC) - - try: - magic_args = args[1] - except IndexError: - magic_args = '' - fn = getattr(self,'magic_'+magic_name,None) - if fn is None: - error("Magic function `%s` not found." % magic_name) - else: - magic_args = self.var_expand(magic_args,1) - return fn(magic_args) - - def ipalias(self,arg_s): - """Call an alias by name. - - Input: a string containing the name of the alias to call and any - additional arguments to be passed to the magic. - - ipalias('name -opt foo bar') is equivalent to typing at the ipython - prompt: - - In[1]: name -opt foo bar - - To call an alias without arguments, simply use ipalias('name'). - - This provides a proper Python function to call IPython's aliases in any - valid Python code you can type at the interpreter, including loops and - compound statements. It is added by IPython to the Python builtin - namespace upon initialization.""" - - args = arg_s.split(' ',1) - alias_name = args[0] - try: - alias_args = args[1] - except IndexError: - alias_args = '' - if alias_name in self.alias_table: - self.call_alias(alias_name,alias_args) - else: - error("Alias `%s` not found." % alias_name) - - def ipsystem(self,arg_s): - """Make a system call, using IPython.""" - - self.system(arg_s) - - def complete(self,text): - """Return a sorted list of all possible completions on text. - - Inputs: - - - text: a string of text to be completed on. - - This is a wrapper around the completion mechanism, similar to what - readline does at the command line when the TAB key is hit. By - exposing it as a method, it can be used by other non-readline - environments (such as GUIs) for text completion. - - Simple usage example: - - In [7]: x = 'hello' - - In [8]: x - Out[8]: 'hello' - - In [9]: print x - hello - - In [10]: _ip.IP.complete('x.l') - Out[10]: ['x.ljust', 'x.lower', 'x.lstrip'] - """ - - complete = self.Completer.complete - state = 0 - # use a dict so we get unique keys, since ipyhton's multiple - # completers can return duplicates. When we make 2.4 a requirement, - # start using sets instead, which are faster. - comps = {} - while True: - newcomp = complete(text,state,line_buffer=text) - if newcomp is None: - break - comps[newcomp] = 1 - state += 1 - outcomps = comps.keys() - outcomps.sort() - #print "T:",text,"OC:",outcomps # dbg - #print "vars:",self.user_ns.keys() - return outcomps - - def set_completer_frame(self, frame=None): - if frame: - self.Completer.namespace = frame.f_locals - self.Completer.global_namespace = frame.f_globals - else: - self.Completer.namespace = self.user_ns - self.Completer.global_namespace = self.user_global_ns - - def init_auto_alias(self): - """Define some aliases automatically. - - These are ALL parameter-less aliases""" - - for alias,cmd in self.auto_alias: - self.getapi().defalias(alias,cmd) - - - def alias_table_validate(self,verbose=0): - """Update information about the alias table. - - In particular, make sure no Python keywords/builtins are in it.""" - - no_alias = self.no_alias - for k in self.alias_table.keys(): - if k in no_alias: - del self.alias_table[k] - if verbose: - print ("Deleting alias <%s>, it's a Python " - "keyword or builtin." % k) - - def set_autoindent(self,value=None): - """Set the autoindent flag, checking for readline support. - - If called with no arguments, it acts as a toggle.""" - - if not self.has_readline: - if os.name == 'posix': - warn("The auto-indent feature requires the readline library") - self.autoindent = 0 - return - if value is None: - self.autoindent = not self.autoindent - else: - self.autoindent = value - - def rc_set_toggle(self,rc_field,value=None): - """Set or toggle a field in IPython's rc config. structure. - - If called with no arguments, it acts as a toggle. - - If called with a non-existent field, the resulting AttributeError - exception will propagate out.""" - - rc_val = getattr(self.rc,rc_field) - if value is None: - value = not rc_val - setattr(self.rc,rc_field,value) - - def user_setup(self,ipythondir,rc_suffix,mode='install'): - """Install the user configuration directory. - - Note - ---- - DEPRECATED: use the top-level user_setup() function instead. - """ - return user_setup(ipythondir,rc_suffix,mode) - - def atexit_operations(self): - """This will be executed at the time of exit. - - Saving of persistent data should be performed here. """ - - #print '*** IPython exit cleanup ***' # dbg - # input history - self.savehist() - - # Cleanup all tempfiles left around - for tfile in self.tempfiles: - try: - os.unlink(tfile) - except OSError: - pass - - # Clear all user namespaces to release all references cleanly. - self.reset() - - # Run user hooks - self.hooks.shutdown_hook() - - def reset(self): - """Clear all internal namespaces. - - Note that this is much more aggressive than %reset, since it clears - fully all namespaces, as well as all input/output lists. - """ - for ns in self.ns_refs_table: - ns.clear() - - # Clear input and output histories - self.input_hist[:] = [] - self.input_hist_raw[:] = [] - self.output_hist.clear() - # Restore the user namespaces to minimal usability - self.init_namespaces() - - def savehist(self): - """Save input history to a file (via readline library).""" - - if not self.has_readline: - return - - try: - self.readline.write_history_file(self.histfile) - except: - print 'Unable to save IPython command history to file: ' + \ - `self.histfile` - - def reloadhist(self): - """Reload the input history from disk file.""" - - if self.has_readline: - try: - self.readline.clear_history() - self.readline.read_history_file(self.shell.histfile) - except AttributeError: - pass - - - def history_saving_wrapper(self, func): - """ Wrap func for readline history saving - - Convert func into callable that saves & restores - history around the call """ - - if not self.has_readline: - return func - - def wrapper(): - self.savehist() - try: - func() - finally: - readline.read_history_file(self.histfile) - return wrapper - - def pre_readline(self): - """readline hook to be used at the start of each line. - - Currently it handles auto-indent only.""" - - #debugx('self.indent_current_nsp','pre_readline:') - - if self.rl_do_indent: - self.readline.insert_text(self.indent_current_str()) - if self.rl_next_input is not None: - self.readline.insert_text(self.rl_next_input) - self.rl_next_input = None - - def init_readline(self): - """Command history completion/saving/reloading.""" - - - import IPython.rlineimpl as readline - - if not readline.have_readline: - self.has_readline = 0 - self.readline = None - # no point in bugging windows users with this every time: - warn('Readline services not available on this platform.') - else: - sys.modules['readline'] = readline - import atexit - from IPython.completer import IPCompleter - self.Completer = IPCompleter(self, - self.user_ns, - self.user_global_ns, - self.rc.readline_omit__names, - self.alias_table) - sdisp = self.strdispatchers.get('complete_command', StrDispatch()) - self.strdispatchers['complete_command'] = sdisp - self.Completer.custom_completers = sdisp - # Platform-specific configuration - if os.name == 'nt': - self.readline_startup_hook = readline.set_pre_input_hook - else: - self.readline_startup_hook = readline.set_startup_hook - - # Load user's initrc file (readline config) - # Or if libedit is used, load editrc. - inputrc_name = os.environ.get('INPUTRC') - if inputrc_name is None: - home_dir = get_home_dir() - if home_dir is not None: - inputrc_name = '.inputrc' - if readline.uses_libedit: - inputrc_name = '.editrc' - inputrc_name = os.path.join(home_dir, inputrc_name) - if os.path.isfile(inputrc_name): - try: - readline.read_init_file(inputrc_name) - except: - warn('Problems reading readline initialization file <%s>' - % inputrc_name) - - self.has_readline = 1 - self.readline = readline - # save this in sys so embedded copies can restore it properly - sys.ipcompleter = self.Completer.complete - self.set_completer() - - # Configure readline according to user's prefs - # This is only done if GNU readline is being used. If libedit - # is being used (as on Leopard) the readline config is - # not run as the syntax for libedit is different. - if not readline.uses_libedit: - for rlcommand in self.rc.readline_parse_and_bind: - #print "loading rl:",rlcommand # dbg - readline.parse_and_bind(rlcommand) - - # Remove some chars from the delimiters list. If we encounter - # unicode chars, discard them. - delims = readline.get_completer_delims().encode("ascii", "ignore") - delims = delims.translate(string._idmap, - self.rc.readline_remove_delims) - readline.set_completer_delims(delims) - # otherwise we end up with a monster history after a while: - readline.set_history_length(1000) - try: - #print '*** Reading readline history' # dbg - readline.read_history_file(self.histfile) - except IOError: - pass # It doesn't exist yet. - - atexit.register(self.atexit_operations) - del atexit - - # Configure auto-indent for all platforms - self.set_autoindent(self.rc.autoindent) - - def ask_yes_no(self,prompt,default=True): - if self.rc.quiet: - return True - return ask_yes_no(prompt,default) - - def new_main_mod(self,ns=None): - """Return a new 'main' module object for user code execution. - """ - main_mod = self._user_main_module - init_fakemod_dict(main_mod,ns) - return main_mod - - def cache_main_mod(self,ns,fname): - """Cache a main module's namespace. - - When scripts are executed via %run, we must keep a reference to the - namespace of their __main__ module (a FakeModule instance) around so - that Python doesn't clear it, rendering objects defined therein - useless. - - This method keeps said reference in a private dict, keyed by the - absolute path of the module object (which corresponds to the script - path). This way, for multiple executions of the same script we only - keep one copy of the namespace (the last one), thus preventing memory - leaks from old references while allowing the objects from the last - execution to be accessible. - - Note: we can not allow the actual FakeModule instances to be deleted, - because of how Python tears down modules (it hard-sets all their - references to None without regard for reference counts). This method - must therefore make a *copy* of the given namespace, to allow the - original module's __dict__ to be cleared and reused. - - - Parameters - ---------- - ns : a namespace (a dict, typically) - - fname : str - Filename associated with the namespace. - - Examples - -------- - - In [10]: import IPython - - In [11]: _ip.IP.cache_main_mod(IPython.__dict__,IPython.__file__) - - In [12]: IPython.__file__ in _ip.IP._main_ns_cache - Out[12]: True - """ - self._main_ns_cache[os.path.abspath(fname)] = ns.copy() - - def clear_main_mod_cache(self): - """Clear the cache of main modules. - - Mainly for use by utilities like %reset. - - Examples - -------- - - In [15]: import IPython - - In [16]: _ip.IP.cache_main_mod(IPython.__dict__,IPython.__file__) - - In [17]: len(_ip.IP._main_ns_cache) > 0 - Out[17]: True - - In [18]: _ip.IP.clear_main_mod_cache() - - In [19]: len(_ip.IP._main_ns_cache) == 0 - Out[19]: True - """ - self._main_ns_cache.clear() - - def _should_recompile(self,e): - """Utility routine for edit_syntax_error""" - - if e.filename in ('','','', - '','', - None): - - return False - try: - if (self.rc.autoedit_syntax and - not self.ask_yes_no('Return to editor to correct syntax error? ' - '[Y/n] ','y')): - return False - except EOFError: - return False - - def int0(x): - try: - return int(x) - except TypeError: - return 0 - # always pass integer line and offset values to editor hook - try: - self.hooks.fix_error_editor(e.filename, - int0(e.lineno),int0(e.offset),e.msg) - except IPython.ipapi.TryNext: - warn('Could not open editor') - return False - return True - - def edit_syntax_error(self): - """The bottom half of the syntax error handler called in the main loop. - - Loop until syntax error is fixed or user cancels. - """ - - while self.SyntaxTB.last_syntax_error: - # copy and clear last_syntax_error - err = self.SyntaxTB.clear_err_state() - if not self._should_recompile(err): - return - try: - # may set last_syntax_error again if a SyntaxError is raised - self.safe_execfile(err.filename,self.user_ns) - except: - self.showtraceback() - else: - try: - f = file(err.filename) - try: - sys.displayhook(f.read()) - finally: - f.close() - except: - self.showtraceback() - - def showsyntaxerror(self, filename=None): - """Display the syntax error that just occurred. - - This doesn't display a stack trace because there isn't one. - - If a filename is given, it is stuffed in the exception instead - of what was there before (because Python's parser always uses - "" when reading from a string). - """ - etype, value, last_traceback = sys.exc_info() - - # See note about these variables in showtraceback() below - sys.last_type = etype - sys.last_value = value - sys.last_traceback = last_traceback - - if filename and etype is SyntaxError: - # Work hard to stuff the correct filename in the exception - try: - msg, (dummy_filename, lineno, offset, line) = value - except: - # Not the format we expect; leave it alone - pass - else: - # Stuff in the right filename - try: - # Assume SyntaxError is a class exception - value = SyntaxError(msg, (filename, lineno, offset, line)) - except: - # If that failed, assume SyntaxError is a string - value = msg, (filename, lineno, offset, line) - self.SyntaxTB(etype,value,[]) - - def debugger(self,force=False): - """Call the pydb/pdb debugger. - - Keywords: - - - force(False): by default, this routine checks the instance call_pdb - flag and does not actually invoke the debugger if the flag is false. - The 'force' option forces the debugger to activate even if the flag - is false. - """ - - if not (force or self.call_pdb): - return - - if not hasattr(sys,'last_traceback'): - error('No traceback has been produced, nothing to debug.') - return - - # use pydb if available - if Debugger.has_pydb: - from pydb import pm - else: - # fallback to our internal debugger - pm = lambda : self.InteractiveTB.debugger(force=True) - self.history_saving_wrapper(pm)() - - def showtraceback(self,exc_tuple = None,filename=None,tb_offset=None): - """Display the exception that just occurred. - - If nothing is known about the exception, this is the method which - should be used throughout the code for presenting user tracebacks, - rather than directly invoking the InteractiveTB object. - - A specific showsyntaxerror() also exists, but this method can take - care of calling it if needed, so unless you are explicitly catching a - SyntaxError exception, don't try to analyze the stack manually and - simply call this method.""" - - - # Though this won't be called by syntax errors in the input line, - # there may be SyntaxError cases whith imported code. - - try: - if exc_tuple is None: - etype, value, tb = sys.exc_info() - else: - etype, value, tb = exc_tuple - - if etype is SyntaxError: - self.showsyntaxerror(filename) - elif etype is IPython.ipapi.UsageError: - print "UsageError:", value - else: - # WARNING: these variables are somewhat deprecated and not - # necessarily safe to use in a threaded environment, but tools - # like pdb depend on their existence, so let's set them. If we - # find problems in the field, we'll need to revisit their use. - sys.last_type = etype - sys.last_value = value - sys.last_traceback = tb - - if etype in self.custom_exceptions: - self.CustomTB(etype,value,tb) - else: - self.InteractiveTB(etype,value,tb,tb_offset=tb_offset) - if self.InteractiveTB.call_pdb and self.has_readline: - # pdb mucks up readline, fix it back - self.set_completer() - except KeyboardInterrupt: - self.write("\nKeyboardInterrupt\n") - - def mainloop(self,banner=None): - """Creates the local namespace and starts the mainloop. - - If an optional banner argument is given, it will override the - internally created default banner.""" - - if self.rc.c: # Emulate Python's -c option - self.exec_init_cmd() - if banner is None: - if not self.rc.banner: - banner = '' - # banner is string? Use it directly! - elif isinstance(self.rc.banner,basestring): - banner = self.rc.banner - else: - banner = self.BANNER+self.banner2 - - # if you run stuff with -c , raw hist is not updated - # ensure that it's in sync - if len(self.input_hist) != len (self.input_hist_raw): - self.input_hist_raw = InputList(self.input_hist) - - while 1: - try: - self.interact(banner) - #self.interact_with_readline() - - # XXX for testing of a readline-decoupled repl loop, call - # interact_with_readline above - - break - except KeyboardInterrupt: - # this should not be necessary, but KeyboardInterrupt - # handling seems rather unpredictable... - self.write("\nKeyboardInterrupt in interact()\n") - - def exec_init_cmd(self): - """Execute a command given at the command line. - - This emulates Python's -c option.""" - - #sys.argv = ['-c'] - self.push(self.prefilter(self.rc.c, False)) - if not self.rc.interact: - self.ask_exit() - - def embed_mainloop(self,header='',local_ns=None,global_ns=None,stack_depth=0): - """Embeds IPython into a running python program. - - Input: - - - header: An optional header message can be specified. - - - local_ns, global_ns: working namespaces. If given as None, the - IPython-initialized one is updated with __main__.__dict__, so that - program variables become visible but user-specific configuration - remains possible. - - - stack_depth: specifies how many levels in the stack to go to - looking for namespaces (when local_ns and global_ns are None). This - allows an intermediate caller to make sure that this function gets - the namespace from the intended level in the stack. By default (0) - it will get its locals and globals from the immediate caller. - - Warning: it's possible to use this in a program which is being run by - IPython itself (via %run), but some funny things will happen (a few - globals get overwritten). In the future this will be cleaned up, as - there is no fundamental reason why it can't work perfectly.""" - - # Get locals and globals from caller - if local_ns is None or global_ns is None: - call_frame = sys._getframe(stack_depth).f_back - - if local_ns is None: - local_ns = call_frame.f_locals - if global_ns is None: - global_ns = call_frame.f_globals - - # Update namespaces and fire up interpreter - - # The global one is easy, we can just throw it in - self.user_global_ns = global_ns - - # but the user/local one is tricky: ipython needs it to store internal - # data, but we also need the locals. We'll copy locals in the user - # one, but will track what got copied so we can delete them at exit. - # This is so that a later embedded call doesn't see locals from a - # previous call (which most likely existed in a separate scope). - local_varnames = local_ns.keys() - self.user_ns.update(local_ns) - #self.user_ns['local_ns'] = local_ns # dbg - - # Patch for global embedding to make sure that things don't overwrite - # user globals accidentally. Thanks to Richard - # FIXME. Test this a bit more carefully (the if.. is new) - if local_ns is None and global_ns is None: - self.user_global_ns.update(__main__.__dict__) - - # make sure the tab-completer has the correct frame information, so it - # actually completes using the frame's locals/globals - self.set_completer_frame() - - # before activating the interactive mode, we need to make sure that - # all names in the builtin namespace needed by ipython point to - # ourselves, and not to other instances. - self.add_builtins() - - self.interact(header) - - # now, purge out the user namespace from anything we might have added - # from the caller's local namespace - delvar = self.user_ns.pop - for var in local_varnames: - delvar(var,None) - # and clean builtins we may have overridden - self.clean_builtins() - - def interact_prompt(self): - """ Print the prompt (in read-eval-print loop) - - Provided for those who want to implement their own read-eval-print loop (e.g. GUIs), not - used in standard IPython flow. - """ - if self.more: - try: - prompt = self.hooks.generate_prompt(True) - except: - self.showtraceback() - if self.autoindent: - self.rl_do_indent = True - - else: - try: - prompt = self.hooks.generate_prompt(False) - except: - self.showtraceback() - self.write(prompt) - - def interact_handle_input(self,line): - """ Handle the input line (in read-eval-print loop) - - Provided for those who want to implement their own read-eval-print loop (e.g. GUIs), not - used in standard IPython flow. - """ - if line.lstrip() == line: - self.shadowhist.add(line.strip()) - lineout = self.prefilter(line,self.more) - - if line.strip(): - if self.more: - self.input_hist_raw[-1] += '%s\n' % line - else: - self.input_hist_raw.append('%s\n' % line) - - - self.more = self.push(lineout) - if (self.SyntaxTB.last_syntax_error and - self.rc.autoedit_syntax): - self.edit_syntax_error() - - def interact_with_readline(self): - """ Demo of using interact_handle_input, interact_prompt - - This is the main read-eval-print loop. If you need to implement your own (e.g. for GUI), - it should work like this. - """ - self.readline_startup_hook(self.pre_readline) - while not self.exit_now: - self.interact_prompt() - if self.more: - self.rl_do_indent = True - else: - self.rl_do_indent = False - line = raw_input_original().decode(self.stdin_encoding) - self.interact_handle_input(line) - - - def interact(self, banner=None): - """Closely emulate the interactive Python console. - - The optional banner argument specify the banner to print - before the first interaction; by default it prints a banner - similar to the one printed by the real Python interpreter, - followed by the current class name in parentheses (so as not - to confuse this with the real interpreter -- since it's so - close!). - - """ - - if self.exit_now: - # batch run -> do not interact - return - cprt = 'Type "copyright", "credits" or "license" for more information.' - if banner is None: - self.write("Python %s on %s\n%s\n(%s)\n" % - (sys.version, sys.platform, cprt, - self.__class__.__name__)) - else: - self.write(banner) - - more = 0 - - # Mark activity in the builtins - __builtin__.__dict__['__IPYTHON__active'] += 1 - - if self.has_readline: - self.readline_startup_hook(self.pre_readline) - # exit_now is set by a call to %Exit or %Quit, through the - # ask_exit callback. - - while not self.exit_now: - self.hooks.pre_prompt_hook() - if more: - try: - prompt = self.hooks.generate_prompt(True) - except: - self.showtraceback() - if self.autoindent: - self.rl_do_indent = True - - else: - try: - prompt = self.hooks.generate_prompt(False) - except: - self.showtraceback() - try: - line = self.raw_input(prompt,more) - if self.exit_now: - # quick exit on sys.std[in|out] close - break - if self.autoindent: - self.rl_do_indent = False - - except KeyboardInterrupt: - #double-guard against keyboardinterrupts during kbdint handling - try: - self.write('\nKeyboardInterrupt\n') - self.resetbuffer() - # keep cache in sync with the prompt counter: - self.outputcache.prompt_count -= 1 - - if self.autoindent: - self.indent_current_nsp = 0 - more = 0 - except KeyboardInterrupt: - pass - except EOFError: - if self.autoindent: - self.rl_do_indent = False - self.readline_startup_hook(None) - self.write('\n') - self.exit() - except bdb.BdbQuit: - warn('The Python debugger has exited with a BdbQuit exception.\n' - 'Because of how pdb handles the stack, it is impossible\n' - 'for IPython to properly format this particular exception.\n' - 'IPython will resume normal operation.') - except: - # exceptions here are VERY RARE, but they can be triggered - # asynchronously by signal handlers, for example. - self.showtraceback() - else: - more = self.push(line) - if (self.SyntaxTB.last_syntax_error and - self.rc.autoedit_syntax): - self.edit_syntax_error() - - # We are off again... - __builtin__.__dict__['__IPYTHON__active'] -= 1 - - def excepthook(self, etype, value, tb): - """One more defense for GUI apps that call sys.excepthook. - - GUI frameworks like wxPython trap exceptions and call - sys.excepthook themselves. I guess this is a feature that - enables them to keep running after exceptions that would - otherwise kill their mainloop. This is a bother for IPython - which excepts to catch all of the program exceptions with a try: - except: statement. - - Normally, IPython sets sys.excepthook to a CrashHandler instance, so if - any app directly invokes sys.excepthook, it will look to the user like - IPython crashed. In order to work around this, we can disable the - CrashHandler and replace it with this excepthook instead, which prints a - regular traceback using our InteractiveTB. In this fashion, apps which - call sys.excepthook will generate a regular-looking exception from - IPython, and the CrashHandler will only be triggered by real IPython - crashes. - - This hook should be used sparingly, only in places which are not likely - to be true IPython errors. - """ - self.showtraceback((etype,value,tb),tb_offset=0) - - def expand_aliases(self,fn,rest): - """ Expand multiple levels of aliases: - - if: - - alias foo bar /tmp - alias baz foo - - then: - - baz huhhahhei -> bar /tmp huhhahhei - - """ - line = fn + " " + rest - - done = set() - while 1: - pre,fn,rest = prefilter.splitUserInput(line, - prefilter.shell_line_split) - if fn in self.alias_table: - if fn in done: - warn("Cyclic alias definition, repeated '%s'" % fn) - return "" - done.add(fn) - - l2 = self.transform_alias(fn,rest) - # dir -> dir - # print "alias",line, "->",l2 #dbg - if l2 == line: - break - # ls -> ls -F should not recurse forever - if l2.split(None,1)[0] == line.split(None,1)[0]: - line = l2 - break - - line=l2 - - - # print "al expand to",line #dbg - else: - break - - return line - - def transform_alias(self, alias,rest=''): - """ Transform alias to system command string. - """ - trg = self.alias_table[alias] - - nargs,cmd = trg - # print trg #dbg - if ' ' in cmd and os.path.isfile(cmd): - cmd = '"%s"' % cmd - - # Expand the %l special to be the user's input line - if cmd.find('%l') >= 0: - cmd = cmd.replace('%l',rest) - rest = '' - if nargs==0: - # Simple, argument-less aliases - cmd = '%s %s' % (cmd,rest) - else: - # Handle aliases with positional arguments - args = rest.split(None,nargs) - if len(args)< nargs: - error('Alias <%s> requires %s arguments, %s given.' % - (alias,nargs,len(args))) - return None - cmd = '%s %s' % (cmd % tuple(args[:nargs]),' '.join(args[nargs:])) - # Now call the macro, evaluating in the user's namespace - #print 'new command: <%r>' % cmd # dbg - return cmd - - def call_alias(self,alias,rest=''): - """Call an alias given its name and the rest of the line. - - This is only used to provide backwards compatibility for users of - ipalias(), use of which is not recommended for anymore.""" - - # Now call the macro, evaluating in the user's namespace - cmd = self.transform_alias(alias, rest) - try: - self.system(cmd) - except: - self.showtraceback() - - def indent_current_str(self): - """return the current level of indentation as a string""" - return self.indent_current_nsp * ' ' - - def autoindent_update(self,line): - """Keep track of the indent level.""" - - #debugx('line') - #debugx('self.indent_current_nsp') - if self.autoindent: - if line: - inisp = num_ini_spaces(line) - if inisp < self.indent_current_nsp: - self.indent_current_nsp = inisp - - if line[-1] == ':': - self.indent_current_nsp += 4 - elif dedent_re.match(line): - self.indent_current_nsp -= 4 - else: - self.indent_current_nsp = 0 - - def runlines(self,lines): - """Run a string of one or more lines of source. - - This method is capable of running a string containing multiple source - lines, as if they had been entered at the IPython prompt. Since it - exposes IPython's processing machinery, the given strings can contain - magic calls (%magic), special shell access (!cmd), etc.""" - - # We must start with a clean buffer, in case this is run from an - # interactive IPython session (via a magic, for example). - self.resetbuffer() - lines = lines.split('\n') - more = 0 - - for line in lines: - # skip blank lines so we don't mess up the prompt counter, but do - # NOT skip even a blank line if we are in a code block (more is - # true) - - if line or more: - # push to raw history, so hist line numbers stay in sync - self.input_hist_raw.append("# " + line + "\n") - more = self.push(self.prefilter(line,more)) - # IPython's runsource returns None if there was an error - # compiling the code. This allows us to stop processing right - # away, so the user gets the error message at the right place. - if more is None: - break - else: - self.input_hist_raw.append("\n") - # final newline in case the input didn't have it, so that the code - # actually does get executed - if more: - self.push('\n') - - def runsource(self, source, filename='', symbol='single'): - """Compile and run some source in the interpreter. - - Arguments are as for compile_command(). - - One several things can happen: - - 1) The input is incorrect; compile_command() raised an - exception (SyntaxError or OverflowError). A syntax traceback - will be printed by calling the showsyntaxerror() method. - - 2) The input is incomplete, and more input is required; - compile_command() returned None. Nothing happens. - - 3) The input is complete; compile_command() returned a code - object. The code is executed by calling self.runcode() (which - also handles run-time exceptions, except for SystemExit). - - The return value is: - - - True in case 2 - - - False in the other cases, unless an exception is raised, where - None is returned instead. This can be used by external callers to - know whether to continue feeding input or not. - - The return value can be used to decide whether to use sys.ps1 or - sys.ps2 to prompt the next line.""" - - # if the source code has leading blanks, add 'if 1:\n' to it - # this allows execution of indented pasted code. It is tempting - # to add '\n' at the end of source to run commands like ' a=1' - # directly, but this fails for more complicated scenarios - source=source.encode(self.stdin_encoding) - if source[:1] in [' ', '\t']: - source = 'if 1:\n%s' % source - - try: - code = self.compile(source,filename,symbol) - except (OverflowError, SyntaxError, ValueError, TypeError, MemoryError): - # Case 1 - self.showsyntaxerror(filename) - return None - - if code is None: - # Case 2 - return True - - # Case 3 - # We store the code object so that threaded shells and - # custom exception handlers can access all this info if needed. - # The source corresponding to this can be obtained from the - # buffer attribute as '\n'.join(self.buffer). - self.code_to_run = code - # now actually execute the code object - if self.runcode(code) == 0: - return False - else: - return None - - def runcode(self,code_obj): - """Execute a code object. - - When an exception occurs, self.showtraceback() is called to display a - traceback. - - Return value: a flag indicating whether the code to be run completed - successfully: - - - 0: successful execution. - - 1: an error occurred. - """ - - # Set our own excepthook in case the user code tries to call it - # directly, so that the IPython crash handler doesn't get triggered - old_excepthook,sys.excepthook = sys.excepthook, self.excepthook - - # we save the original sys.excepthook in the instance, in case config - # code (such as magics) needs access to it. - self.sys_excepthook = old_excepthook - outflag = 1 # happens in more places, so it's easier as default - try: - try: - self.hooks.pre_runcode_hook() - exec code_obj in self.user_global_ns, self.user_ns - finally: - # Reset our crash handler in place - sys.excepthook = old_excepthook - except SystemExit: - self.resetbuffer() - self.showtraceback() - warn("Type %exit or %quit to exit IPython " - "(%Exit or %Quit do so unconditionally).",level=1) - except self.custom_exceptions: - etype,value,tb = sys.exc_info() - self.CustomTB(etype,value,tb) - except: - self.showtraceback() - else: - outflag = 0 - if softspace(sys.stdout, 0): - print - # Flush out code object which has been run (and source) - self.code_to_run = None - return outflag - - def push(self, line): - """Push a line to the interpreter. - - The line should not have a trailing newline; it may have - internal newlines. The line is appended to a buffer and the - interpreter's runsource() method is called with the - concatenated contents of the buffer as source. If this - indicates that the command was executed or invalid, the buffer - is reset; otherwise, the command is incomplete, and the buffer - is left as it was after the line was appended. The return - value is 1 if more input is required, 0 if the line was dealt - with in some way (this is the same as runsource()). - """ - - # autoindent management should be done here, and not in the - # interactive loop, since that one is only seen by keyboard input. We - # need this done correctly even for code run via runlines (which uses - # push). - - #print 'push line: <%s>' % line # dbg - for subline in line.splitlines(): - self.autoindent_update(subline) - self.buffer.append(line) - more = self.runsource('\n'.join(self.buffer), self.filename) - if not more: - self.resetbuffer() - return more - - def split_user_input(self, line): - # This is really a hold-over to support ipapi and some extensions - return prefilter.splitUserInput(line) - - def resetbuffer(self): - """Reset the input buffer.""" - self.buffer[:] = [] - - def raw_input(self,prompt='',continue_prompt=False): - """Write a prompt and read a line. - - The returned line does not include the trailing newline. - When the user enters the EOF key sequence, EOFError is raised. - - Optional inputs: - - - prompt(''): a string to be printed to prompt the user. - - - continue_prompt(False): whether this line is the first one or a - continuation in a sequence of inputs. - """ - - # Code run by the user may have modified the readline completer state. - # We must ensure that our completer is back in place. - if self.has_readline: - self.set_completer() - - try: - line = raw_input_original(prompt).decode(self.stdin_encoding) - except ValueError: - warn("\n********\nYou or a %run:ed script called sys.stdin.close()" - " or sys.stdout.close()!\nExiting IPython!") - self.ask_exit() - return "" - - # Try to be reasonably smart about not re-indenting pasted input more - # than necessary. We do this by trimming out the auto-indent initial - # spaces, if the user's actual input started itself with whitespace. - #debugx('self.buffer[-1]') - - if self.autoindent: - if num_ini_spaces(line) > self.indent_current_nsp: - line = line[self.indent_current_nsp:] - self.indent_current_nsp = 0 - - # store the unfiltered input before the user has any chance to modify - # it. - if line.strip(): - if continue_prompt: - self.input_hist_raw[-1] += '%s\n' % line - if self.has_readline: # and some config option is set? - try: - histlen = self.readline.get_current_history_length() - if histlen > 1: - newhist = self.input_hist_raw[-1].rstrip() - self.readline.remove_history_item(histlen-1) - self.readline.replace_history_item(histlen-2, - newhist.encode(self.stdin_encoding)) - except AttributeError: - pass # re{move,place}_history_item are new in 2.4. - else: - self.input_hist_raw.append('%s\n' % line) - # only entries starting at first column go to shadow history - if line.lstrip() == line: - self.shadowhist.add(line.strip()) - elif not continue_prompt: - self.input_hist_raw.append('\n') - try: - lineout = self.prefilter(line,continue_prompt) - except: - # blanket except, in case a user-defined prefilter crashes, so it - # can't take all of ipython with it. - self.showtraceback() - return '' - else: - return lineout - - def _prefilter(self, line, continue_prompt): - """Calls different preprocessors, depending on the form of line.""" - - # All handlers *must* return a value, even if it's blank (''). - - # Lines are NOT logged here. Handlers should process the line as - # needed, update the cache AND log it (so that the input cache array - # stays synced). - - #..................................................................... - # Code begins - - #if line.startswith('%crash'): raise RuntimeError,'Crash now!' # dbg - - # save the line away in case we crash, so the post-mortem handler can - # record it - self._last_input_line = line - - #print '***line: <%s>' % line # dbg - - if not line: - # Return immediately on purely empty lines, so that if the user - # previously typed some whitespace that started a continuation - # prompt, he can break out of that loop with just an empty line. - # This is how the default python prompt works. - - # Only return if the accumulated input buffer was just whitespace! - if ''.join(self.buffer).isspace(): - self.buffer[:] = [] - return '' - - line_info = prefilter.LineInfo(line, continue_prompt) - - # the input history needs to track even empty lines - stripped = line.strip() - - if not stripped: - if not continue_prompt: - self.outputcache.prompt_count -= 1 - return self.handle_normal(line_info) - - # print '***cont',continue_prompt # dbg - # special handlers are only allowed for single line statements - if continue_prompt and not self.rc.multi_line_specials: - return self.handle_normal(line_info) - - - # See whether any pre-existing handler can take care of it - rewritten = self.hooks.input_prefilter(stripped) - if rewritten != stripped: # ok, some prefilter did something - rewritten = line_info.pre + rewritten # add indentation - return self.handle_normal(prefilter.LineInfo(rewritten, - continue_prompt)) - - #print 'pre <%s> iFun <%s> rest <%s>' % (pre,iFun,theRest) # dbg - - return prefilter.prefilter(line_info, self) - - - def _prefilter_dumb(self, line, continue_prompt): - """simple prefilter function, for debugging""" - return self.handle_normal(line,continue_prompt) - - - def multiline_prefilter(self, line, continue_prompt): - """ Run _prefilter for each line of input - - Covers cases where there are multiple lines in the user entry, - which is the case when the user goes back to a multiline history - entry and presses enter. - - """ - out = [] - for l in line.rstrip('\n').split('\n'): - out.append(self._prefilter(l, continue_prompt)) - return '\n'.join(out) - - # Set the default prefilter() function (this can be user-overridden) - prefilter = multiline_prefilter - - def handle_normal(self,line_info): - """Handle normal input lines. Use as a template for handlers.""" - - # With autoindent on, we need some way to exit the input loop, and I - # don't want to force the user to have to backspace all the way to - # clear the line. The rule will be in this case, that either two - # lines of pure whitespace in a row, or a line of pure whitespace but - # of a size different to the indent level, will exit the input loop. - line = line_info.line - continue_prompt = line_info.continue_prompt - - if (continue_prompt and self.autoindent and line.isspace() and - (0 < abs(len(line) - self.indent_current_nsp) <= 2 or - (self.buffer[-1]).isspace() )): - line = '' - - self.log(line,line,continue_prompt) - return line - - def handle_alias(self,line_info): - """Handle alias input lines. """ - tgt = self.alias_table[line_info.iFun] - # print "=>",tgt #dbg - if callable(tgt): - if '$' in line_info.line: - call_meth = '(_ip, _ip.itpl(%s))' - else: - call_meth = '(_ip,%s)' - line_out = ("%s_sh.%s" + call_meth) % (line_info.preWhitespace, - line_info.iFun, - make_quoted_expr(line_info.line)) - else: - transformed = self.expand_aliases(line_info.iFun,line_info.theRest) - - # pre is needed, because it carries the leading whitespace. Otherwise - # aliases won't work in indented sections. - line_out = '%s_ip.system(%s)' % (line_info.preWhitespace, - make_quoted_expr( transformed )) - - self.log(line_info.line,line_out,line_info.continue_prompt) - #print 'line out:',line_out # dbg - return line_out - - def handle_shell_escape(self, line_info): - """Execute the line in a shell, empty return value""" - #print 'line in :', `line` # dbg - line = line_info.line - if line.lstrip().startswith('!!'): - # rewrite LineInfo's line, iFun and theRest to properly hold the - # call to %sx and the actual command to be executed, so - # handle_magic can work correctly. Note that this works even if - # the line is indented, so it handles multi_line_specials - # properly. - new_rest = line.lstrip()[2:] - line_info.line = '%ssx %s' % (self.ESC_MAGIC,new_rest) - line_info.iFun = 'sx' - line_info.theRest = new_rest - return self.handle_magic(line_info) - else: - cmd = line.lstrip().lstrip('!') - line_out = '%s_ip.system(%s)' % (line_info.preWhitespace, - make_quoted_expr(cmd)) - # update cache/log and return - self.log(line,line_out,line_info.continue_prompt) - return line_out - - def handle_magic(self, line_info): - """Execute magic functions.""" - iFun = line_info.iFun - theRest = line_info.theRest - cmd = '%s_ip.magic(%s)' % (line_info.preWhitespace, - make_quoted_expr(iFun + " " + theRest)) - self.log(line_info.line,cmd,line_info.continue_prompt) - #print 'in handle_magic, cmd=<%s>' % cmd # dbg - return cmd - - def handle_auto(self, line_info): - """Hande lines which can be auto-executed, quoting if requested.""" - - line = line_info.line - iFun = line_info.iFun - theRest = line_info.theRest - pre = line_info.pre - continue_prompt = line_info.continue_prompt - obj = line_info.ofind(self)['obj'] - - #print 'pre <%s> iFun <%s> rest <%s>' % (pre,iFun,theRest) # dbg - - # This should only be active for single-line input! - if continue_prompt: - self.log(line,line,continue_prompt) - return line - - force_auto = isinstance(obj, IPython.ipapi.IPyAutocall) - auto_rewrite = True - - if pre == self.ESC_QUOTE: - # Auto-quote splitting on whitespace - newcmd = '%s("%s")' % (iFun,'", "'.join(theRest.split()) ) - elif pre == self.ESC_QUOTE2: - # Auto-quote whole string - newcmd = '%s("%s")' % (iFun,theRest) - elif pre == self.ESC_PAREN: - newcmd = '%s(%s)' % (iFun,",".join(theRest.split())) - else: - # Auto-paren. - # We only apply it to argument-less calls if the autocall - # parameter is set to 2. We only need to check that autocall is < - # 2, since this function isn't called unless it's at least 1. - if not theRest and (self.rc.autocall < 2) and not force_auto: - newcmd = '%s %s' % (iFun,theRest) - auto_rewrite = False - else: - if not force_auto and theRest.startswith('['): - if hasattr(obj,'__getitem__'): - # Don't autocall in this case: item access for an object - # which is BOTH callable and implements __getitem__. - newcmd = '%s %s' % (iFun,theRest) - auto_rewrite = False - else: - # if the object doesn't support [] access, go ahead and - # autocall - newcmd = '%s(%s)' % (iFun.rstrip(),theRest) - elif theRest.endswith(';'): - newcmd = '%s(%s);' % (iFun.rstrip(),theRest[:-1]) - else: - newcmd = '%s(%s)' % (iFun.rstrip(), theRest) - - if auto_rewrite: - rw = self.outputcache.prompt1.auto_rewrite() + newcmd - - try: - # plain ascii works better w/ pyreadline, on some machines, so - # we use it and only print uncolored rewrite if we have unicode - rw = str(rw) - print >>Term.cout, rw - except UnicodeEncodeError: - print "-------------->" + newcmd - - # log what is now valid Python, not the actual user input (without the - # final newline) - self.log(line,newcmd,continue_prompt) - return newcmd - - def handle_help(self, line_info): - """Try to get some help for the object. - - obj? or ?obj -> basic information. - obj?? or ??obj -> more details. - """ - - line = line_info.line - # We need to make sure that we don't process lines which would be - # otherwise valid python, such as "x=1 # what?" - try: - codeop.compile_command(line) - except SyntaxError: - # We should only handle as help stuff which is NOT valid syntax - if line[0]==self.ESC_HELP: - line = line[1:] - elif line[-1]==self.ESC_HELP: - line = line[:-1] - self.log(line,'#?'+line,line_info.continue_prompt) - if line: - #print 'line:<%r>' % line # dbg - self.magic_pinfo(line) - else: - page(self.usage,screen_lines=self.rc.screen_length) - return '' # Empty string is needed here! - except: - # Pass any other exceptions through to the normal handler - return self.handle_normal(line_info) - else: - # If the code compiles ok, we should handle it normally - return self.handle_normal(line_info) - - def getapi(self): - """ Get an IPApi object for this shell instance - - Getting an IPApi object is always preferable to accessing the shell - directly, but this holds true especially for extensions. - - It should always be possible to implement an extension with IPApi - alone. If not, contact maintainer to request an addition. - - """ - return self.api - - def handle_emacs(self, line_info): - """Handle input lines marked by python-mode.""" - - # Currently, nothing is done. Later more functionality can be added - # here if needed. - - # The input cache shouldn't be updated - return line_info.line - - - def mktempfile(self,data=None): - """Make a new tempfile and return its filename. - - This makes a call to tempfile.mktemp, but it registers the created - filename internally so ipython cleans it up at exit time. - - Optional inputs: - - - data(None): if data is given, it gets written out to the temp file - immediately, and the file is closed again.""" - - filename = tempfile.mktemp('.py','ipython_edit_') - self.tempfiles.append(filename) - - if data: - tmp_file = open(filename,'w') - tmp_file.write(data) - tmp_file.close() - return filename - - def write(self,data): - """Write a string to the default output""" - Term.cout.write(data) - - def write_err(self,data): - """Write a string to the default error output""" - Term.cerr.write(data) - - def ask_exit(self): - """ Call for exiting. Can be overiden and used as a callback. """ - self.exit_now = True - - def exit(self): - """Handle interactive exit. - - This method calls the ask_exit callback.""" - - if self.rc.confirm_exit: - if self.ask_yes_no('Do you really want to exit ([y]/n)?','y'): - self.ask_exit() - else: - self.ask_exit() - - def safe_execfile(self,fname,*where,**kw): - """A safe version of the builtin execfile(). - - This version will never throw an exception, and knows how to handle - ipython logs as well. - - :Parameters: - fname : string - Name of the file to be executed. - - where : tuple - One or two namespaces, passed to execfile() as (globals,locals). - If only one is given, it is passed as both. - - :Keywords: - islog : boolean (False) - - quiet : boolean (True) - - exit_ignore : boolean (False) - """ - - def syspath_cleanup(): - """Internal cleanup routine for sys.path.""" - if add_dname: - try: - sys.path.remove(dname) - except ValueError: - # For some reason the user has already removed it, ignore. - pass - - fname = os.path.expanduser(fname) - - # Find things also in current directory. This is needed to mimic the - # behavior of running a script from the system command line, where - # Python inserts the script's directory into sys.path - dname = os.path.dirname(os.path.abspath(fname)) - add_dname = False - if dname not in sys.path: - sys.path.insert(0,dname) - add_dname = True - - try: - xfile = open(fname) - except: - print >> Term.cerr, \ - 'Could not open file <%s> for safe execution.' % fname - syspath_cleanup() - return None - - kw.setdefault('islog',0) - kw.setdefault('quiet',1) - kw.setdefault('exit_ignore',0) - - first = xfile.readline() - loghead = str(self.loghead_tpl).split('\n',1)[0].strip() - xfile.close() - # line by line execution - if first.startswith(loghead) or kw['islog']: - print 'Loading log file <%s> one line at a time...' % fname - if kw['quiet']: - stdout_save = sys.stdout - sys.stdout = StringIO.StringIO() - try: - globs,locs = where[0:2] - except: - try: - globs = locs = where[0] - except: - globs = locs = globals() - badblocks = [] +#----------------------------------------------------------------------------- - # we also need to identify indented blocks of code when replaying - # logs and put them together before passing them to an exec - # statement. This takes a bit of regexp and look-ahead work in the - # file. It's easiest if we swallow the whole thing in memory - # first, and manually walk through the lines list moving the - # counter ourselves. - indent_re = re.compile('\s+\S') - xfile = open(fname) - filelines = xfile.readlines() - xfile.close() - nlines = len(filelines) - lnum = 0 - while lnum < nlines: - line = filelines[lnum] - lnum += 1 - # don't re-insert logger status info into cache - if line.startswith('#log#'): - continue - else: - # build a block of code (maybe a single line) for execution - block = line - try: - next = filelines[lnum] # lnum has already incremented - except: - next = None - while next and indent_re.match(next): - block += next - lnum += 1 - try: - next = filelines[lnum] - except: - next = None - # now execute the block of one or more lines - try: - exec block in globs,locs - except SystemExit: - pass - except: - badblocks.append(block.rstrip()) - if kw['quiet']: # restore stdout - sys.stdout.close() - sys.stdout = stdout_save - print 'Finished replaying log file <%s>' % fname - if badblocks: - print >> sys.stderr, ('\nThe following lines/blocks in file ' - '<%s> reported errors:' % fname) - - for badline in badblocks: - print >> sys.stderr, badline - else: # regular file execution - try: - if sys.platform == 'win32' and sys.version_info < (2,5,1): - # Work around a bug in Python for Windows. The bug was - # fixed in in Python 2.5 r54159 and 54158, but that's still - # SVN Python as of March/07. For details, see: - # http://projects.scipy.org/ipython/ipython/ticket/123 - try: - globs,locs = where[0:2] - except: - try: - globs = locs = where[0] - except: - globs = locs = globals() - exec file(fname) in globs,locs - else: - execfile(fname,*where) - except SyntaxError: - self.showsyntaxerror() - warn('Failure executing file: <%s>' % fname) - except SystemExit,status: - # Code that correctly sets the exit status flag to success (0) - # shouldn't be bothered with a traceback. Note that a plain - # sys.exit() does NOT set the message to 0 (it's empty) so that - # will still get a traceback. Note that the structure of the - # SystemExit exception changed between Python 2.4 and 2.5, so - # the checks must be done in a version-dependent way. - show = False +from warnings import warn - if sys.version_info[:2] > (2,5): - if status.message!=0 and not kw['exit_ignore']: - show = True - else: - if status.code and not kw['exit_ignore']: - show = True - if show: - self.showtraceback() - warn('Failure executing file: <%s>' % fname) - except: - self.showtraceback() - warn('Failure executing file: <%s>' % fname) +msg = """ +This module (IPython.iplib) has been moved to a new location +(IPython.core.iplib) and is being refactored. Please update your code +to use the new IPython.core.iplib module""" - syspath_cleanup() +warn(msg, category=DeprecationWarning, stacklevel=1) -#************************* end of file ***************************** +from IPython.core.iplib import * diff --git a/IPython/ipmaker.py b/IPython/ipmaker.py deleted file mode 100644 index f514338..0000000 --- a/IPython/ipmaker.py +++ /dev/null @@ -1,773 +0,0 @@ -# -*- coding: utf-8 -*- -""" -IPython -- An enhanced Interactive Python - -Requires Python 2.1 or better. - -This file contains the main make_IPython() starter function. -""" - -#***************************************************************************** -# Copyright (C) 2008-2009 The IPython Development Team -# Copyright (C) 2001-2007 Fernando Perez. -# -# Distributed under the terms of the BSD License. The full license is in -# the file COPYING, distributed as part of this software. -#***************************************************************************** - -try: - credits._Printer__data = """ - Python: %s - - IPython: The IPython Development Team. - See http://ipython.scipy.org for more information.""" \ - % credits._Printer__data - - copyright._Printer__data += """ - - Copyright (c) 2008-2009 The IPython Development Team. - Copyright (c) 2001-2007 Fernando Perez, Janko Hauser, Nathan Gray. - All Rights Reserved.""" -except NameError: - # Can happen if ipython was started with 'python -S', so that site.py is - # not loaded - pass - -#**************************************************************************** -# Required modules - -# From the standard library -import __main__ -import __builtin__ -import os -import re -import sys -import types -from pprint import pprint,pformat - -# Our own -from IPython import DPyGetOpt -from IPython import Release -from IPython.ipstruct import Struct -from IPython.OutputTrap import OutputTrap -from IPython.ConfigLoader import ConfigLoader -from IPython.iplib import InteractiveShell -from IPython.usage import cmd_line_usage,interactive_usage -from IPython.genutils import * - -def force_import(modname,force_reload=False): - if modname in sys.modules and force_reload: - info("reloading: %s" % modname) - reload(sys.modules[modname]) - else: - __import__(modname) - - -#----------------------------------------------------------------------------- -def make_IPython(argv=None,user_ns=None,user_global_ns=None,debug=1, - rc_override=None,shell_class=InteractiveShell, - embedded=False,**kw): - """This is a dump of IPython into a single function. - - Later it will have to be broken up in a sensible manner. - - Arguments: - - - argv: a list similar to sys.argv[1:]. It should NOT contain the desired - script name, b/c DPyGetOpt strips the first argument only for the real - sys.argv. - - - user_ns: a dict to be used as the user's namespace.""" - - #---------------------------------------------------------------------- - # Defaults and initialization - - # For developer debugging, deactivates crash handler and uses pdb. - DEVDEBUG = False - - if argv is None: - argv = sys.argv - - # __IP is the main global that lives throughout and represents the whole - # application. If the user redefines it, all bets are off as to what - # happens. - - # __IP is the name of he global which the caller will have accessible as - # __IP.name. We set its name via the first parameter passed to - # InteractiveShell: - - IP = shell_class('__IP',user_ns=user_ns,user_global_ns=user_global_ns, - embedded=embedded,**kw) - - # Put 'help' in the user namespace - try: - from site import _Helper - IP.user_ns['help'] = _Helper() - except ImportError: - warn('help() not available - check site.py') - - if DEVDEBUG: - # For developer debugging only (global flag) - from IPython import ultraTB - sys.excepthook = ultraTB.VerboseTB(call_pdb=1) - - IP.BANNER_PARTS = ['Python %s\n' - 'Type "copyright", "credits" or "license" ' - 'for more information.\n' - % (sys.version.split('\n')[0],), - "IPython %s -- An enhanced Interactive Python." - % (Release.version,), -"""\ -? -> Introduction and overview of IPython's features. -%quickref -> Quick reference. -help -> Python's own help system. -object? -> Details about 'object'. ?object also works, ?? prints more. -""" ] - - IP.usage = interactive_usage - - # Platform-dependent suffix. - if os.name == 'posix': - rc_suffix = '' - else: - rc_suffix = '.ini' - - # default directory for configuration - ipythondir_def = get_ipython_dir() - - sys.path.insert(0, '') # add . to sys.path. Fix from Prabhu Ramachandran - - # we need the directory where IPython itself is installed - import IPython - IPython_dir = os.path.dirname(IPython.__file__) - del IPython - - #------------------------------------------------------------------------- - # Command line handling - - # Valid command line options (uses DPyGetOpt syntax, like Perl's - # GetOpt::Long) - - # Any key not listed here gets deleted even if in the file (like session - # or profile). That's deliberate, to maintain the rc namespace clean. - - # Each set of options appears twice: under _conv only the names are - # listed, indicating which type they must be converted to when reading the - # ipythonrc file. And under DPyGetOpt they are listed with the regular - # DPyGetOpt syntax (=s,=i,:f,etc). - - # Make sure there's a space before each end of line (they get auto-joined!) - cmdline_opts = ('autocall=i autoindent! automagic! banner! cache_size|cs=i ' - 'c=s classic|cl color_info! colors=s confirm_exit! ' - 'debug! deep_reload! editor=s log|l messages! nosep ' - 'object_info_string_level=i pdb! ' - 'pprint! prompt_in1|pi1=s prompt_in2|pi2=s prompt_out|po=s ' - 'pydb! ' - 'pylab_import_all! ' - 'quick screen_length|sl=i prompts_pad_left=i ' - 'logfile|lf=s logplay|lp=s profile|p=s ' - 'readline! readline_merge_completions! ' - 'readline_omit__names! ' - 'rcfile=s separate_in|si=s separate_out|so=s ' - 'separate_out2|so2=s xmode=s wildcards_case_sensitive! ' - 'magic_docstrings system_verbose! ' - 'multi_line_specials! ' - 'term_title! wxversion=s ' - 'autoedit_syntax!') - - # Options that can *only* appear at the cmd line (not in rcfiles). - - cmdline_only = ('help interact|i ipythondir=s Version upgrade ' - 'gthread! qthread! q4thread! wthread! tkthread! pylab! tk! ' - # 'twisted!' # disabled for now. - ) - - # Build the actual name list to be used by DPyGetOpt - opts_names = qw(cmdline_opts) + qw(cmdline_only) - - # Set sensible command line defaults. - # This should have everything from cmdline_opts and cmdline_only - opts_def = Struct(autocall = 1, - autoedit_syntax = 0, - autoindent = 0, - automagic = 1, - autoexec = [], - banner = 1, - c = '', - cache_size = 1000, - classic = 0, - color_info = 0, - colors = 'NoColor', - confirm_exit = 1, - debug = 0, - deep_reload = 0, - editor = '0', - gthread = 0, - help = 0, - interact = 0, - ipythondir = ipythondir_def, - log = 0, - logfile = '', - logplay = '', - messages = 1, - multi_line_specials = 1, - nosep = 0, - object_info_string_level = 0, - pdb = 0, - pprint = 0, - profile = '', - prompt_in1 = 'In [\\#]: ', - prompt_in2 = ' .\\D.: ', - prompt_out = 'Out[\\#]: ', - prompts_pad_left = 1, - pydb = 0, - pylab = 0, - pylab_import_all = 1, - q4thread = 0, - qthread = 0, - quick = 0, - quiet = 0, - rcfile = 'ipythonrc' + rc_suffix, - readline = 1, - readline_merge_completions = 1, - readline_omit__names = 0, - screen_length = 0, - separate_in = '\n', - separate_out = '\n', - separate_out2 = '', - system_header = 'IPython system call: ', - system_verbose = 0, - term_title = 1, - tk = 0, - #twisted= 0, # disabled for now - upgrade = 0, - Version = 0, - wildcards_case_sensitive = 1, - wthread = 0, - wxversion = '0', - xmode = 'Context', - magic_docstrings = 0, # undocumented, for doc generation - ) - - # Things that will *only* appear in rcfiles (not at the command line). - # Make sure there's a space before each end of line (they get auto-joined!) - rcfile_opts = { qwflat: 'include import_mod import_all execfile ', - qw_lol: 'import_some ', - # for things with embedded whitespace: - list_strings:'execute alias readline_parse_and_bind ', - # Regular strings need no conversion: - None:'readline_remove_delims ', - } - # Default values for these - rc_def = Struct(include = [], - import_mod = [], - import_all = [], - import_some = [[]], - execute = [], - execfile = [], - alias = [], - readline_parse_and_bind = [], - readline_remove_delims = '', - ) - - # Build the type conversion dictionary from the above tables: - typeconv = rcfile_opts.copy() - typeconv.update(optstr2types(cmdline_opts)) - - # FIXME: the None key appears in both, put that back together by hand. Ugly! - typeconv[None] += ' ' + rcfile_opts[None] - - # Remove quotes at ends of all strings (used to protect spaces) - typeconv[unquote_ends] = typeconv[None] - del typeconv[None] - - # Build the list we'll use to make all config decisions with defaults: - opts_all = opts_def.copy() - opts_all.update(rc_def) - - # Build conflict resolver for recursive loading of config files: - # - preserve means the outermost file maintains the value, it is not - # overwritten if an included file has the same key. - # - add_flip applies + to the two values, so it better make sense to add - # those types of keys. But it flips them first so that things loaded - # deeper in the inclusion chain have lower precedence. - conflict = {'preserve': ' '.join([ typeconv[int], - typeconv[unquote_ends] ]), - 'add_flip': ' '.join([ typeconv[qwflat], - typeconv[qw_lol], - typeconv[list_strings] ]) - } - - # Now actually process the command line - getopt = DPyGetOpt.DPyGetOpt() - getopt.setIgnoreCase(0) - - getopt.parseConfiguration(opts_names) - - try: - getopt.processArguments(argv) - except DPyGetOpt.ArgumentError, exc: - print cmd_line_usage - warn('\nError in Arguments: "%s"' % exc) - sys.exit(1) - - # convert the options dict to a struct for much lighter syntax later - opts = Struct(getopt.optionValues) - args = getopt.freeValues - - # this is the struct (which has default values at this point) with which - # we make all decisions: - opts_all.update(opts) - - # Options that force an immediate exit - if opts_all.help: - page(cmd_line_usage) - sys.exit() - - if opts_all.Version: - print Release.version - sys.exit() - - if opts_all.magic_docstrings: - IP.magic_magic('-latex') - sys.exit() - - # add personal ipythondir to sys.path so that users can put things in - # there for customization - sys.path.append(os.path.abspath(opts_all.ipythondir)) - - # Create user config directory if it doesn't exist. This must be done - # *after* getting the cmd line options. - if not os.path.isdir(opts_all.ipythondir): - IP.user_setup(opts_all.ipythondir,rc_suffix,'install') - - # upgrade user config files while preserving a copy of the originals - if opts_all.upgrade: - IP.user_setup(opts_all.ipythondir,rc_suffix,'upgrade') - - # check mutually exclusive options in the *original* command line - mutex_opts(opts,[qw('log logfile'),qw('rcfile profile'), - qw('classic profile'),qw('classic rcfile')]) - - #--------------------------------------------------------------------------- - # Log replay - - # if -logplay, we need to 'become' the other session. That basically means - # replacing the current command line environment with that of the old - # session and moving on. - - # this is needed so that later we know we're in session reload mode, as - # opts_all will get overwritten: - load_logplay = 0 - - if opts_all.logplay: - load_logplay = opts_all.logplay - opts_debug_save = opts_all.debug - try: - logplay = open(opts_all.logplay) - except IOError: - if opts_all.debug: IP.InteractiveTB() - warn('Could not open logplay file '+`opts_all.logplay`) - # restore state as if nothing had happened and move on, but make - # sure that later we don't try to actually load the session file - logplay = None - load_logplay = 0 - del opts_all.logplay - else: - try: - logplay.readline() - logplay.readline(); - # this reloads that session's command line - cmd = logplay.readline()[6:] - exec cmd - # restore the true debug flag given so that the process of - # session loading itself can be monitored. - opts.debug = opts_debug_save - # save the logplay flag so later we don't overwrite the log - opts.logplay = load_logplay - # now we must update our own structure with defaults - opts_all.update(opts) - # now load args - cmd = logplay.readline()[6:] - exec cmd - logplay.close() - except: - logplay.close() - if opts_all.debug: IP.InteractiveTB() - warn("Logplay file lacking full configuration information.\n" - "I'll try to read it, but some things may not work.") - - #------------------------------------------------------------------------- - # set up output traps: catch all output from files, being run, modules - # loaded, etc. Then give it to the user in a clean form at the end. - - msg_out = 'Output messages. ' - msg_err = 'Error messages. ' - msg_sep = '\n' - msg = Struct(config = OutputTrap('Configuration Loader',msg_out, - msg_err,msg_sep,debug, - quiet_out=1), - user_exec = OutputTrap('User File Execution',msg_out, - msg_err,msg_sep,debug), - logplay = OutputTrap('Log Loader',msg_out, - msg_err,msg_sep,debug), - summary = '' - ) - - #------------------------------------------------------------------------- - # Process user ipythonrc-type configuration files - - # turn on output trapping and log to msg.config - # remember that with debug on, trapping is actually disabled - msg.config.trap_all() - - # look for rcfile in current or default directory - try: - opts_all.rcfile = filefind(opts_all.rcfile,opts_all.ipythondir) - except IOError: - if opts_all.debug: IP.InteractiveTB() - warn('Configuration file %s not found. Ignoring request.' - % (opts_all.rcfile) ) - - # 'profiles' are a shorthand notation for config filenames - profile_handled_by_legacy = False - if opts_all.profile: - - try: - opts_all.rcfile = filefind('ipythonrc-' + opts_all.profile - + rc_suffix, - opts_all.ipythondir) - profile_handled_by_legacy = True - except IOError: - if opts_all.debug: IP.InteractiveTB() - opts.profile = '' # remove profile from options if invalid - # We won't warn anymore, primary method is ipy_profile_PROFNAME - # which does trigger a warning. - - # load the config file - rcfiledata = None - if opts_all.quick: - print 'Launching IPython in quick mode. No config file read.' - elif opts_all.rcfile: - try: - cfg_loader = ConfigLoader(conflict) - rcfiledata = cfg_loader.load(opts_all.rcfile,typeconv, - 'include',opts_all.ipythondir, - purge = 1, - unique = conflict['preserve']) - except: - IP.InteractiveTB() - warn('Problems loading configuration file '+ - `opts_all.rcfile`+ - '\nStarting with default -bare bones- configuration.') - else: - warn('No valid configuration file found in either currrent directory\n'+ - 'or in the IPython config. directory: '+`opts_all.ipythondir`+ - '\nProceeding with internal defaults.') - - #------------------------------------------------------------------------ - # Set exception handlers in mode requested by user. - otrap = OutputTrap(trap_out=1) # trap messages from magic_xmode - IP.magic_xmode(opts_all.xmode) - otrap.release_out() - - #------------------------------------------------------------------------ - # Execute user config - - # Create a valid config structure with the right precedence order: - # defaults < rcfile < command line. This needs to be in the instance, so - # that method calls below that rely on it find it. - IP.rc = rc_def.copy() - - # Work with a local alias inside this routine to avoid unnecessary - # attribute lookups. - IP_rc = IP.rc - - IP_rc.update(opts_def) - if rcfiledata: - # now we can update - IP_rc.update(rcfiledata) - IP_rc.update(opts) - IP_rc.update(rc_override) - - # Store the original cmd line for reference: - IP_rc.opts = opts - IP_rc.args = args - - # create a *runtime* Struct like rc for holding parameters which may be - # created and/or modified by runtime user extensions. - IP.runtime_rc = Struct() - - # from this point on, all config should be handled through IP_rc, - # opts* shouldn't be used anymore. - - - # update IP_rc with some special things that need manual - # tweaks. Basically options which affect other options. I guess this - # should just be written so that options are fully orthogonal and we - # wouldn't worry about this stuff! - - if IP_rc.classic: - IP_rc.quick = 1 - IP_rc.cache_size = 0 - IP_rc.pprint = 0 - IP_rc.prompt_in1 = '>>> ' - IP_rc.prompt_in2 = '... ' - IP_rc.prompt_out = '' - IP_rc.separate_in = IP_rc.separate_out = IP_rc.separate_out2 = '0' - IP_rc.colors = 'NoColor' - IP_rc.xmode = 'Plain' - - IP.pre_config_initialization() - # configure readline - - # update exception handlers with rc file status - otrap.trap_out() # I don't want these messages ever. - IP.magic_xmode(IP_rc.xmode) - otrap.release_out() - - # activate logging if requested and not reloading a log - if IP_rc.logplay: - IP.magic_logstart(IP_rc.logplay + ' append') - elif IP_rc.logfile: - IP.magic_logstart(IP_rc.logfile) - elif IP_rc.log: - IP.magic_logstart() - - # find user editor so that it we don't have to look it up constantly - if IP_rc.editor.strip()=='0': - try: - ed = os.environ['EDITOR'] - except KeyError: - if os.name == 'posix': - ed = 'vi' # the only one guaranteed to be there! - else: - ed = 'notepad' # same in Windows! - IP_rc.editor = ed - - # Keep track of whether this is an embedded instance or not (useful for - # post-mortems). - IP_rc.embedded = IP.embedded - - # Recursive reload - try: - from IPython import deep_reload - if IP_rc.deep_reload: - __builtin__.reload = deep_reload.reload - else: - __builtin__.dreload = deep_reload.reload - del deep_reload - except ImportError: - pass - - # Save the current state of our namespace so that the interactive shell - # can later know which variables have been created by us from config files - # and loading. This way, loading a file (in any way) is treated just like - # defining things on the command line, and %who works as expected. - - # DON'T do anything that affects the namespace beyond this point! - IP.internal_ns.update(__main__.__dict__) - - #IP.internal_ns.update(locals()) # so our stuff doesn't show up in %who - - # Now run through the different sections of the users's config - if IP_rc.debug: - print 'Trying to execute the following configuration structure:' - print '(Things listed first are deeper in the inclusion tree and get' - print 'loaded first).\n' - pprint(IP_rc.__dict__) - - for mod in IP_rc.import_mod: - try: - exec 'import '+mod in IP.user_ns - except : - IP.InteractiveTB() - import_fail_info(mod) - - for mod_fn in IP_rc.import_some: - if not mod_fn == []: - mod,fn = mod_fn[0],','.join(mod_fn[1:]) - try: - exec 'from '+mod+' import '+fn in IP.user_ns - except : - IP.InteractiveTB() - import_fail_info(mod,fn) - - for mod in IP_rc.import_all: - try: - exec 'from '+mod+' import *' in IP.user_ns - except : - IP.InteractiveTB() - import_fail_info(mod) - - for code in IP_rc.execute: - try: - exec code in IP.user_ns - except: - IP.InteractiveTB() - warn('Failure executing code: ' + `code`) - - # Execute the files the user wants in ipythonrc - for file in IP_rc.execfile: - try: - file = filefind(file,sys.path+[IPython_dir]) - except IOError: - warn(itpl('File $file not found. Skipping it.')) - else: - IP.safe_execfile(os.path.expanduser(file),IP.user_ns) - - # finally, try importing ipy_*_conf for final configuration - try: - import ipy_system_conf - except ImportError: - if opts_all.debug: IP.InteractiveTB() - warn("Could not import 'ipy_system_conf'") - except: - IP.InteractiveTB() - import_fail_info('ipy_system_conf') - - # only import prof module if ipythonrc-PROF was not found - if opts_all.profile and not profile_handled_by_legacy: - profmodname = 'ipy_profile_' + opts_all.profile - try: - force_import(profmodname) - except: - IP.InteractiveTB() - print "Error importing",profmodname,\ - "- perhaps you should run %upgrade?" - import_fail_info(profmodname) - else: - opts.profile = opts_all.profile - else: - force_import('ipy_profile_none') - # XXX - this is wrong: ipy_user_conf should not be loaded unconditionally, - # since the user could have specified a config file path by hand. - try: - force_import('ipy_user_conf') - except: - conf = opts_all.ipythondir + "/ipy_user_conf.py" - IP.InteractiveTB() - if not os.path.isfile(conf): - warn(conf + ' does not exist, please run %upgrade!') - - import_fail_info("ipy_user_conf") - - # Define the history file for saving commands in between sessions - try: - histfname = 'history-%s' % opts.profile - except AttributeError: - histfname = 'history' - IP.histfile = os.path.join(opts_all.ipythondir,histfname) - - # finally, push the argv to options again to ensure highest priority - IP_rc.update(opts) - - # release stdout and stderr and save config log into a global summary - msg.config.release_all() - if IP_rc.messages: - msg.summary += msg.config.summary_all() - - #------------------------------------------------------------------------ - # Setup interactive session - - # Now we should be fully configured. We can then execute files or load - # things only needed for interactive use. Then we'll open the shell. - - # Take a snapshot of the user namespace before opening the shell. That way - # we'll be able to identify which things were interactively defined and - # which were defined through config files. - IP.user_config_ns.update(IP.user_ns) - - # Force reading a file as if it were a session log. Slower but safer. - if load_logplay: - print 'Replaying log...' - try: - if IP_rc.debug: - logplay_quiet = 0 - else: - logplay_quiet = 1 - - msg.logplay.trap_all() - IP.safe_execfile(load_logplay,IP.user_ns, - islog = 1, quiet = logplay_quiet) - msg.logplay.release_all() - if IP_rc.messages: - msg.summary += msg.logplay.summary_all() - except: - warn('Problems replaying logfile %s.' % load_logplay) - IP.InteractiveTB() - - # Load remaining files in command line - msg.user_exec.trap_all() - - # Do NOT execute files named in the command line as scripts to be loaded - # by embedded instances. Doing so has the potential for an infinite - # recursion if there are exceptions thrown in the process. - - # XXX FIXME: the execution of user files should be moved out to after - # ipython is fully initialized, just as if they were run via %run at the - # ipython prompt. This would also give them the benefit of ipython's - # nice tracebacks. - - if (not embedded and IP_rc.args and - not IP_rc.args[0].lower().endswith('.ipy')): - name_save = IP.user_ns['__name__'] - IP.user_ns['__name__'] = '__main__' - # Set our own excepthook in case the user code tries to call it - # directly. This prevents triggering the IPython crash handler. - old_excepthook,sys.excepthook = sys.excepthook, IP.excepthook - - save_argv = sys.argv[1:] # save it for later restoring - - sys.argv = args - - try: - IP.safe_execfile(args[0], IP.user_ns) - finally: - # Reset our crash handler in place - sys.excepthook = old_excepthook - sys.argv[:] = save_argv - IP.user_ns['__name__'] = name_save - - msg.user_exec.release_all() - - if IP_rc.messages: - msg.summary += msg.user_exec.summary_all() - - # since we can't specify a null string on the cmd line, 0 is the equivalent: - if IP_rc.nosep: - IP_rc.separate_in = IP_rc.separate_out = IP_rc.separate_out2 = '0' - if IP_rc.separate_in == '0': IP_rc.separate_in = '' - if IP_rc.separate_out == '0': IP_rc.separate_out = '' - if IP_rc.separate_out2 == '0': IP_rc.separate_out2 = '' - IP_rc.separate_in = IP_rc.separate_in.replace('\\n','\n') - IP_rc.separate_out = IP_rc.separate_out.replace('\\n','\n') - IP_rc.separate_out2 = IP_rc.separate_out2.replace('\\n','\n') - - # Determine how many lines at the bottom of the screen are needed for - # showing prompts, so we can know wheter long strings are to be printed or - # paged: - num_lines_bot = IP_rc.separate_in.count('\n')+1 - IP_rc.screen_length = IP_rc.screen_length - num_lines_bot - - # configure startup banner - if IP_rc.c: # regular python doesn't print the banner with -c - IP_rc.banner = 0 - if IP_rc.banner: - BANN_P = IP.BANNER_PARTS - else: - BANN_P = [] - - if IP_rc.profile: BANN_P.append('IPython profile: %s\n' % IP_rc.profile) - - # add message log (possibly empty) - if msg.summary: BANN_P.append(msg.summary) - # Final banner is a string - IP.BANNER = '\n'.join(BANN_P) - - # Finalize the IPython instance. This assumes the rc structure is fully - # in place. - IP.post_config_initialization() - - return IP -#************************ end of file ************************** diff --git a/IPython/ipstruct.py b/IPython/ipstruct.py deleted file mode 100644 index ef11c47..0000000 --- a/IPython/ipstruct.py +++ /dev/null @@ -1,416 +0,0 @@ -# -*- coding: utf-8 -*- -"""Mimic C structs with lots of extra functionality. -""" - -#***************************************************************************** -# Copyright (C) 2001-2004 Fernando Perez -# -# Distributed under the terms of the BSD License. The full license is in -# the file COPYING, distributed as part of this software. -#***************************************************************************** - -__all__ = ['Struct'] - -import types -import pprint - -from IPython.genutils import list2dict2 - -class Struct: - """Class to mimic C structs but also provide convenient dictionary-like - functionality. - - Instances can be initialized with a dictionary, a list of key=value pairs - or both. If both are present, the dictionary must come first. - - Because Python classes provide direct assignment to their members, it's - easy to overwrite normal methods (S.copy = 1 would destroy access to - S.copy()). For this reason, all builtin method names are protected and - can't be assigned to. An attempt to do s.copy=1 or s['copy']=1 will raise - a KeyError exception. If you really want to, you can bypass this - protection by directly assigning to __dict__: s.__dict__['copy']=1 will - still work. Doing this will break functionality, though. As in most of - Python, namespace protection is weakly enforced, so feel free to shoot - yourself if you really want to. - - Note that this class uses more memory and is *much* slower than a regular - dictionary, so be careful in situations where memory or performance are - critical. But for day to day use it should behave fine. It is particularly - convenient for storing configuration data in programs. - - +,+=,- and -= are implemented. +/+= do merges (non-destructive updates), - -/-= remove keys from the original. See the method descripitions. - - This class allows a quick access syntax: both s.key and s['key'] are - valid. This syntax has a limitation: each 'key' has to be explicitly - accessed by its original name. The normal s.key syntax doesn't provide - access to the keys via variables whose values evaluate to the desired - keys. An example should clarify this: - - Define a dictionary and initialize both with dict and k=v pairs: - >>> d={'a':1,'b':2} - >>> s=Struct(d,hi=10,ho=20) - - The return of __repr__ can be used to create a new instance: - >>> s - Struct({'__allownew': True, 'a': 1, 'b': 2, 'hi': 10, 'ho': 20}) - - Note: the special '__allownew' key is used for internal purposes. - - __str__ (called by print) shows it's not quite a regular dictionary: - >>> print s - Struct({'__allownew': True, 'a': 1, 'b': 2, 'hi': 10, 'ho': 20}) - - Access by explicitly named key with dot notation: - >>> s.a - 1 - - Or like a dictionary: - >>> s['a'] - 1 - - If you want a variable to hold the key value, only dictionary access works: - >>> key='hi' - >>> s.key - Traceback (most recent call last): - File "", line 1, in ? - AttributeError: Struct instance has no attribute 'key' - - >>> s[key] - 10 - - Another limitation of the s.key syntax (and Struct(key=val) - initialization): keys can't be numbers. But numeric keys can be used and - accessed using the dictionary syntax. Again, an example: - - This doesn't work (prompt changed to avoid confusing the test system): - ->> s=Struct(4='hi') - Traceback (most recent call last): - ... - SyntaxError: keyword can't be an expression - - But this does: - >>> s=Struct() - >>> s[4]='hi' - >>> s - Struct({4: 'hi', '__allownew': True}) - >>> s[4] - 'hi' - """ - - # Attributes to which __setitem__ and __setattr__ will block access. - # Note: much of this will be moot in Python 2.2 and will be done in a much - # cleaner way. - __protected = ('copy dict dictcopy get has_attr has_key items keys ' - 'merge popitem setdefault update values ' - '__make_dict __dict_invert ').split() - - def __init__(self,dict=None,**kw): - """Initialize with a dictionary, another Struct, or by giving - explicitly the list of attributes. - - Both can be used, but the dictionary must come first: - Struct(dict), Struct(k1=v1,k2=v2) or Struct(dict,k1=v1,k2=v2). - """ - self.__dict__['__allownew'] = True - if dict is None: - dict = {} - if isinstance(dict,Struct): - dict = dict.dict() - elif dict and type(dict) is not types.DictType: - raise TypeError,\ - 'Initialize with a dictionary or key=val pairs.' - dict.update(kw) - # do the updating by hand to guarantee that we go through the - # safety-checked __setitem__ - for k,v in dict.items(): - self[k] = v - - - def __setitem__(self,key,value): - """Used when struct[key] = val calls are made.""" - if key in Struct.__protected: - raise KeyError,'Key '+`key`+' is a protected key of class Struct.' - if not self['__allownew'] and key not in self.__dict__: - raise KeyError( - "Can't create unknown attribute %s - Check for typos, or use allow_new_attr to create new attributes!" % - key) - - self.__dict__[key] = value - - def __setattr__(self, key, value): - """Used when struct.key = val calls are made.""" - self.__setitem__(key,value) - - def __str__(self): - """Gets called by print.""" - - return 'Struct('+ pprint.pformat(self.__dict__)+')' - - def __repr__(self): - """Gets called by repr. - - A Struct can be recreated with S_new=eval(repr(S_old)).""" - return self.__str__() - - def __getitem__(self,key): - """Allows struct[key] access.""" - return self.__dict__[key] - - def __contains__(self,key): - """Allows use of the 'in' operator. - - Examples: - >>> s = Struct(x=1) - >>> 'x' in s - True - >>> 'y' in s - False - >>> s[4] = None - >>> 4 in s - True - >>> s.z = None - >>> 'z' in s - True - """ - return key in self.__dict__ - - def __iadd__(self,other): - """S += S2 is a shorthand for S.merge(S2).""" - self.merge(other) - return self - - def __add__(self,other): - """S + S2 -> New Struct made form S and S.merge(S2)""" - Sout = self.copy() - Sout.merge(other) - return Sout - - def __sub__(self,other): - """Return S1-S2, where all keys in S2 have been deleted (if present) - from S1.""" - Sout = self.copy() - Sout -= other - return Sout - - def __isub__(self,other): - """Do in place S = S - S2, meaning all keys in S2 have been deleted - (if present) from S1.""" - - for k in other.keys(): - if self.has_key(k): - del self.__dict__[k] - - def __make_dict(self,__loc_data__,**kw): - "Helper function for update and merge. Return a dict from data." - - if __loc_data__ == None: - dict = {} - elif type(__loc_data__) is types.DictType: - dict = __loc_data__ - elif isinstance(__loc_data__,Struct): - dict = __loc_data__.__dict__ - else: - raise TypeError, 'Update with a dict, a Struct or key=val pairs.' - if kw: - dict.update(kw) - return dict - - def __dict_invert(self,dict): - """Helper function for merge. Takes a dictionary whose values are - lists and returns a dict. with the elements of each list as keys and - the original keys as values.""" - - outdict = {} - for k,lst in dict.items(): - if type(lst) is types.StringType: - lst = lst.split() - for entry in lst: - outdict[entry] = k - return outdict - - def clear(self): - """Clear all attributes.""" - self.__dict__.clear() - - def copy(self): - """Return a (shallow) copy of a Struct.""" - return Struct(self.__dict__.copy()) - - def dict(self): - """Return the Struct's dictionary.""" - return self.__dict__ - - def dictcopy(self): - """Return a (shallow) copy of the Struct's dictionary.""" - return self.__dict__.copy() - - def popitem(self): - """S.popitem() -> (k, v), remove and return some (key, value) pair as - a 2-tuple; but raise KeyError if S is empty.""" - return self.__dict__.popitem() - - def update(self,__loc_data__=None,**kw): - """Update (merge) with data from another Struct or from a dictionary. - Optionally, one or more key=value pairs can be given at the end for - direct update.""" - - # The funny name __loc_data__ is to prevent a common variable name - # which could be a fieled of a Struct to collide with this - # parameter. The problem would arise if the function is called with a - # keyword with this same name that a user means to add as a Struct - # field. - newdict = Struct.__make_dict(self,__loc_data__,**kw) - for k,v in newdict.iteritems(): - self[k] = v - - def merge(self,__loc_data__=None,__conflict_solve=None,**kw): - """S.merge(data,conflict,k=v1,k=v2,...) -> merge data and k=v into S. - - This is similar to update(), but much more flexible. First, a dict is - made from data+key=value pairs. When merging this dict with the Struct - S, the optional dictionary 'conflict' is used to decide what to do. - - If conflict is not given, the default behavior is to preserve any keys - with their current value (the opposite of the update method's - behavior). - - conflict is a dictionary of binary functions which will be used to - solve key conflicts. It must have the following structure: - - conflict == { fn1 : [Skey1,Skey2,...], fn2 : [Skey3], etc } - - Values must be lists or whitespace separated strings which are - automatically converted to lists of strings by calling string.split(). - - Each key of conflict is a function which defines a policy for - resolving conflicts when merging with the input data. Each fn must be - a binary function which returns the desired outcome for a key - conflict. These functions will be called as fn(old,new). - - An example is probably in order. Suppose you are merging the struct S - with a dict D and the following conflict policy dict: - - S.merge(D,{fn1:['a','b',4], fn2:'key_c key_d'}) - - If the key 'a' is found in both S and D, the merge method will call: - - S['a'] = fn1(S['a'],D['a']) - - As a convenience, merge() provides five (the most commonly needed) - pre-defined policies: preserve, update, add, add_flip and add_s. The - easiest explanation is their implementation: - - preserve = lambda old,new: old - update = lambda old,new: new - add = lambda old,new: old + new - add_flip = lambda old,new: new + old # note change of order! - add_s = lambda old,new: old + ' ' + new # only works for strings! - - You can use those four words (as strings) as keys in conflict instead - of defining them as functions, and the merge method will substitute - the appropriate functions for you. That is, the call - - S.merge(D,{'preserve':'a b c','add':[4,5,'d'],my_function:[6]}) - - will automatically substitute the functions preserve and add for the - names 'preserve' and 'add' before making any function calls. - - For more complicated conflict resolution policies, you still need to - construct your own functions. """ - - data_dict = Struct.__make_dict(self,__loc_data__,**kw) - - # policies for conflict resolution: two argument functions which return - # the value that will go in the new struct - preserve = lambda old,new: old - update = lambda old,new: new - add = lambda old,new: old + new - add_flip = lambda old,new: new + old # note change of order! - add_s = lambda old,new: old + ' ' + new - - # default policy is to keep current keys when there's a conflict - conflict_solve = list2dict2(self.keys(),default = preserve) - - # the conflict_solve dictionary is given by the user 'inverted': we - # need a name-function mapping, it comes as a function -> names - # dict. Make a local copy (b/c we'll make changes), replace user - # strings for the three builtin policies and invert it. - if __conflict_solve: - inv_conflict_solve_user = __conflict_solve.copy() - for name, func in [('preserve',preserve), ('update',update), - ('add',add), ('add_flip',add_flip), - ('add_s',add_s)]: - if name in inv_conflict_solve_user.keys(): - inv_conflict_solve_user[func] = inv_conflict_solve_user[name] - del inv_conflict_solve_user[name] - conflict_solve.update(Struct.__dict_invert(self,inv_conflict_solve_user)) - #print 'merge. conflict_solve: '; pprint(conflict_solve) # dbg - #print '*'*50,'in merger. conflict_solver:'; pprint(conflict_solve) - for key in data_dict: - if key not in self: - self[key] = data_dict[key] - else: - self[key] = conflict_solve[key](self[key],data_dict[key]) - - def has_key(self,key): - """Like has_key() dictionary method.""" - return self.__dict__.has_key(key) - - def hasattr(self,key): - """hasattr function available as a method. - - Implemented like has_key, to make sure that all available keys in the - internal dictionary of the Struct appear also as attributes (even - numeric keys).""" - return self.__dict__.has_key(key) - - def items(self): - """Return the items in the Struct's dictionary, in the same format - as a call to {}.items().""" - return self.__dict__.items() - - def keys(self): - """Return the keys in the Struct's dictionary, in the same format - as a call to {}.keys().""" - return self.__dict__.keys() - - def values(self,keys=None): - """Return the values in the Struct's dictionary, in the same format - as a call to {}.values(). - - Can be called with an optional argument keys, which must be a list or - tuple of keys. In this case it returns only the values corresponding - to those keys (allowing a form of 'slicing' for Structs).""" - if not keys: - return self.__dict__.values() - else: - ret=[] - for k in keys: - ret.append(self[k]) - return ret - - def get(self,attr,val=None): - """S.get(k[,d]) -> S[k] if k in S, else d. d defaults to None.""" - try: - return self[attr] - except KeyError: - return val - - def setdefault(self,attr,val=None): - """S.setdefault(k[,d]) -> S.get(k,d), also set S[k]=d if k not in S""" - if not self.has_key(attr): - self[attr] = val - return self.get(attr,val) - - def allow_new_attr(self, allow = True): - """ Set whether new attributes can be created inside struct - - This can be used to catch typos by verifying that the attribute user - tries to change already exists in this Struct. - """ - self['__allownew'] = allow - - -# end class Struct - diff --git a/IPython/kernel/asyncclient.py b/IPython/kernel/asyncclient.py index 586202b..b59058c 100644 --- a/IPython/kernel/asyncclient.py +++ b/IPython/kernel/asyncclient.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python # encoding: utf-8 """Asynchronous clients for the IPython controller. @@ -9,32 +10,32 @@ deferreds to the result. The main methods are are `get_*_client` and `get_client`. """ - -__docformat__ = "restructuredtext en" - -#------------------------------------------------------------------------------- -# Copyright (C) 2008 The IPython Development Team +#----------------------------------------------------------------------------- +# Copyright (C) 2008-2009 The IPython Development Team # # Distributed under the terms of the BSD License. The full license is in # the file COPYING, distributed as part of this software. -#------------------------------------------------------------------------------- +#----------------------------------------------------------------------------- -#------------------------------------------------------------------------------- +#----------------------------------------------------------------------------- # Imports -#------------------------------------------------------------------------------- +#----------------------------------------------------------------------------- from IPython.kernel import codeutil -from IPython.kernel.clientconnector import ClientConnector +from IPython.kernel.clientconnector import ( + AsyncClientConnector, + AsyncCluster +) # Other things that the user will need from IPython.kernel.task import MapTask, StringTask from IPython.kernel.error import CompositeError -#------------------------------------------------------------------------------- +#----------------------------------------------------------------------------- # Code -#------------------------------------------------------------------------------- +#----------------------------------------------------------------------------- -_client_tub = ClientConnector() +_client_tub = AsyncClientConnector() get_multiengine_client = _client_tub.get_multiengine_client get_task_client = _client_tub.get_task_client get_client = _client_tub.get_client diff --git a/IPython/kernel/client.py b/IPython/kernel/client.py index efbc160..872ec3a 100644 --- a/IPython/kernel/client.py +++ b/IPython/kernel/client.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python # encoding: utf-8 """This module contains blocking clients for the controller interfaces. @@ -15,33 +16,36 @@ The main classes in this module are: * CompositeError """ -__docformat__ = "restructuredtext en" - -#------------------------------------------------------------------------------- -# Copyright (C) 2008 The IPython Development Team +#----------------------------------------------------------------------------- +# Copyright (C) 2008-2009 The IPython Development Team # # Distributed under the terms of the BSD License. The full license is in # the file COPYING, distributed as part of this software. -#------------------------------------------------------------------------------- +#----------------------------------------------------------------------------- -#------------------------------------------------------------------------------- +#----------------------------------------------------------------------------- # Imports -#------------------------------------------------------------------------------- +#----------------------------------------------------------------------------- +from cStringIO import StringIO import sys +import warnings -# from IPython.tools import growl +# from IPython.utils import growl # growl.start("IPython1 Client") from twisted.internet import reactor -from IPython.kernel.clientconnector import ClientConnector +from twisted.internet.error import PotentialZombieWarning +from twisted.python import log + +from IPython.kernel.clientconnector import ClientConnector, Cluster from IPython.kernel.twistedutil import ReactorInThread from IPython.kernel.twistedutil import blockingCallFromThread # These enable various things from IPython.kernel import codeutil -import IPython.kernel.magic +# import IPython.kernel.magic # Other things that the user will need from IPython.kernel.task import MapTask, StringTask @@ -51,46 +55,34 @@ from IPython.kernel.error import CompositeError # Code #------------------------------------------------------------------------------- -_client_tub = ClientConnector() - - -def get_multiengine_client(furl_or_file=''): - """Get the blocking MultiEngine client. - - :Parameters: - furl_or_file : str - A furl or a filename containing a furl. If empty, the - default furl_file will be used - - :Returns: - The connected MultiEngineClient instance - """ - client = blockingCallFromThread(_client_tub.get_multiengine_client, - furl_or_file) - return client.adapt_to_blocking_client() - -def get_task_client(furl_or_file=''): - """Get the blocking Task client. - - :Parameters: - furl_or_file : str - A furl or a filename containing a furl. If empty, the - default furl_file will be used - - :Returns: - The connected TaskClient instance - """ - client = blockingCallFromThread(_client_tub.get_task_client, - furl_or_file) - return client.adapt_to_blocking_client() +warnings.simplefilter('ignore', PotentialZombieWarning) +_client_tub = ClientConnector() +get_multiengine_client = _client_tub.get_multiengine_client +get_task_client = _client_tub.get_task_client MultiEngineClient = get_multiengine_client TaskClient = get_task_client - +# This isn't great. I should probably set this up in the ReactorInThread +# class below. But, it does work for now. +log.startLogging(sys.stdout, setStdout=0) # Now we start the reactor in a thread rit = ReactorInThread() rit.setDaemon(True) -rit.start() \ No newline at end of file +rit.start() + + + + +__all__ = [ + 'MapTask', + 'StringTask', + 'MultiEngineClient', + 'TaskClient', + 'CompositeError', + 'get_task_client', + 'get_multiengine_client', + 'Cluster' +] diff --git a/IPython/kernel/clientconnector.py b/IPython/kernel/clientconnector.py index 595a353..ffa55d4 100644 --- a/IPython/kernel/clientconnector.py +++ b/IPython/kernel/clientconnector.py @@ -1,142 +1,268 @@ +#!/usr/bin/env python # encoding: utf-8 -"""A class for handling client connections to the controller.""" +"""Facilities for handling client connections to the controller.""" -__docformat__ = "restructuredtext en" - -#------------------------------------------------------------------------------- -# Copyright (C) 2008 The IPython Development Team +#----------------------------------------------------------------------------- +# Copyright (C) 2008-2009 The IPython Development Team # # Distributed under the terms of the BSD License. The full license is in # the file COPYING, distributed as part of this software. -#------------------------------------------------------------------------------- +#----------------------------------------------------------------------------- -#------------------------------------------------------------------------------- +#----------------------------------------------------------------------------- # Imports -#------------------------------------------------------------------------------- +#----------------------------------------------------------------------------- + +from __future__ import with_statement +import os + +from IPython.kernel.fcutil import ( + Tub, + find_furl, + is_valid_furl_or_file, + validate_furl_or_file, + FURLError +) +from IPython.kernel.clusterdir import ClusterDir, ClusterDirError +from IPython.kernel.launcher import IPClusterLauncher +from IPython.kernel.twistedutil import ( + gatherBoth, + make_deferred, + blockingCallFromThread, + sleep_deferred +) +from IPython.utils.importstring import import_item +from IPython.utils.genutils import get_ipython_dir from twisted.internet import defer +from twisted.internet.defer import inlineCallbacks, returnValue +from twisted.python import failure, log -from IPython.kernel.fcutil import Tub, UnauthenticatedTub +#----------------------------------------------------------------------------- +# The ClientConnector class +#----------------------------------------------------------------------------- -from IPython.kernel.config import config_manager as kernel_config_manager -from IPython.config.cutils import import_item -from IPython.kernel.fcutil import find_furl +DELAY = 0.2 +MAX_TRIES = 9 -co = kernel_config_manager.get_config_obj() -client_co = co['client'] -#------------------------------------------------------------------------------- -# The ClientConnector class -#------------------------------------------------------------------------------- +class ClientConnectorError(Exception): + pass -class ClientConnector(object): - """ - This class gets remote references from furls and returns the wrapped clients. - - This class is also used in `client.py` and `asyncclient.py` to create - a single per client-process Tub. + +class AsyncClientConnector(object): + """A class for getting remote references and clients from furls. + + This start a single :class:`Tub` for all remote reference and caches + references. """ - + def __init__(self): self._remote_refs = {} self.tub = Tub() self.tub.startService() - - def get_reference(self, furl_or_file): + + def _find_furl(self, profile='default', cluster_dir=None, + furl_or_file=None, furl_file_name=None, + ipython_dir=None): + """Find a FURL file by profile+ipython_dir or cluster dir. + + This raises an :exc:`~IPython.kernel.fcutil.FURLError` exception + if a FURL file can't be found. """ - Get a remote reference using a furl or a file containing a furl. - + # Try by furl_or_file + if furl_or_file is not None: + validate_furl_or_file(furl_or_file) + return furl_or_file + + if furl_file_name is None: + raise FURLError('A furl_file_name must be provided') + + # Try by cluster_dir + if cluster_dir is not None: + cluster_dir_obj = ClusterDir.find_cluster_dir(cluster_dir) + sdir = cluster_dir_obj.security_dir + furl_file = os.path.join(sdir, furl_file_name) + validate_furl_or_file(furl_file) + return furl_file + + # Try by profile + if ipython_dir is None: + ipython_dir = get_ipython_dir() + if profile is not None: + cluster_dir_obj = ClusterDir.find_cluster_dir_by_profile( + ipython_dir, profile) + sdir = cluster_dir_obj.security_dir + furl_file = os.path.join(sdir, furl_file_name) + validate_furl_or_file(furl_file) + return furl_file + + raise FURLError('Could not find a valid FURL file.') + + def get_reference(self, furl_or_file): + """Get a remote reference using a furl or a file containing a furl. + Remote references are cached locally so once a remote reference has been retrieved for a given furl, the cached version is returned. - - :Parameters: - furl_or_file : str - A furl or a filename containing a furl - - :Returns: - A deferred to a remote reference + + Parameters + ---------- + furl_or_file : str + A furl or a filename containing a furl. This should already be + validated, but might not yet exist. + + Returns + ------- + A deferred to a remote reference """ - furl = find_furl(furl_or_file) + furl = furl_or_file if furl in self._remote_refs: d = defer.succeed(self._remote_refs[furl]) else: d = self.tub.getReference(furl) - d.addCallback(self.save_ref, furl) + d.addCallback(self._save_ref, furl) return d - def save_ref(self, ref, furl): - """ - Cache a remote reference by its furl. - """ + def _save_ref(self, ref, furl): + """Cache a remote reference by its furl.""" self._remote_refs[furl] = ref return ref - def get_task_client(self, furl_or_file=''): - """ - Get the task controller client. + def get_task_client(self, profile='default', cluster_dir=None, + furl_or_file=None, ipython_dir=None, + delay=DELAY, max_tries=MAX_TRIES): + """Get the task controller client. - This method is a simple wrapper around `get_client` that allow - `furl_or_file` to be empty, in which case, the furls is taken - from the default furl file given in the configuration. + This method is a simple wrapper around `get_client` that passes in + the default name of the task client FURL file. Usually only + the ``profile`` option will be needed. If a FURL file can't be + found by its profile, use ``cluster_dir`` or ``furl_or_file``. - :Parameters: - furl_or_file : str - A furl or a filename containing a furl. If empty, the - default furl_file will be used - - :Returns: - A deferred to the actual client class - """ - task_co = client_co['client_interfaces']['task'] - if furl_or_file: - ff = furl_or_file - else: - ff = task_co['furl_file'] - return self.get_client(ff) + Parameters + ---------- + profile : str + The name of a cluster directory profile (default="default"). The + cluster directory "cluster_" will be searched for + in ``os.getcwd()``, the ipython_dir and then in the directories + listed in the :env:`IPCLUSTER_DIR_PATH` environment variable. + cluster_dir : str + The full path to a cluster directory. This is useful if profiles + are not being used. + furl_or_file : str + A furl or a filename containing a FURLK. This is useful if you + simply know the location of the FURL file. + ipython_dir : str + The location of the ipython_dir if different from the default. + This is used if the cluster directory is being found by profile. + delay : float + The initial delay between re-connection attempts. Susequent delays + get longer according to ``delay[i] = 1.5*delay[i-1]``. + max_tries : int + The max number of re-connection attempts. - def get_multiengine_client(self, furl_or_file=''): + Returns + ------- + A deferred to the actual client class. """ - Get the multiengine controller client. + return self.get_client( + profile, cluster_dir, furl_or_file, + 'ipcontroller-tc.furl', ipython_dir, + delay, max_tries + ) + + def get_multiengine_client(self, profile='default', cluster_dir=None, + furl_or_file=None, ipython_dir=None, + delay=DELAY, max_tries=MAX_TRIES): + """Get the multiengine controller client. - This method is a simple wrapper around `get_client` that allow - `furl_or_file` to be empty, in which case, the furls is taken - from the default furl file given in the configuration. + This method is a simple wrapper around `get_client` that passes in + the default name of the task client FURL file. Usually only + the ``profile`` option will be needed. If a FURL file can't be + found by its profile, use ``cluster_dir`` or ``furl_or_file``. - :Parameters: - furl_or_file : str - A furl or a filename containing a furl. If empty, the - default furl_file will be used - - :Returns: - A deferred to the actual client class + Parameters + ---------- + profile : str + The name of a cluster directory profile (default="default"). The + cluster directory "cluster_" will be searched for + in ``os.getcwd()``, the ipython_dir and then in the directories + listed in the :env:`IPCLUSTER_DIR_PATH` environment variable. + cluster_dir : str + The full path to a cluster directory. This is useful if profiles + are not being used. + furl_or_file : str + A furl or a filename containing a FURLK. This is useful if you + simply know the location of the FURL file. + ipython_dir : str + The location of the ipython_dir if different from the default. + This is used if the cluster directory is being found by profile. + delay : float + The initial delay between re-connection attempts. Susequent delays + get longer according to ``delay[i] = 1.5*delay[i-1]``. + max_tries : int + The max number of re-connection attempts. + + Returns + ------- + A deferred to the actual client class. """ - task_co = client_co['client_interfaces']['multiengine'] - if furl_or_file: - ff = furl_or_file - else: - ff = task_co['furl_file'] - return self.get_client(ff) + return self.get_client( + profile, cluster_dir, furl_or_file, + 'ipcontroller-mec.furl', ipython_dir, + delay, max_tries + ) - def get_client(self, furl_or_file): - """ - Get a remote reference and wrap it in a client by furl. - - This method first gets a remote reference and then calls its - `get_client_name` method to find the apprpriate client class - that should be used to wrap the remote reference. - - :Parameters: - furl_or_file : str - A furl or a filename containing a furl + def get_client(self, profile='default', cluster_dir=None, + furl_or_file=None, furl_file_name=None, ipython_dir=None, + delay=DELAY, max_tries=MAX_TRIES): + """Get a remote reference and wrap it in a client by furl. + + This method is a simple wrapper around `get_client` that passes in + the default name of the task client FURL file. Usually only + the ``profile`` option will be needed. If a FURL file can't be + found by its profile, use ``cluster_dir`` or ``furl_or_file``. - :Returns: - A deferred to the actual client class + Parameters + ---------- + profile : str + The name of a cluster directory profile (default="default"). The + cluster directory "cluster_" will be searched for + in ``os.getcwd()``, the ipython_dir and then in the directories + listed in the :env:`IPCLUSTER_DIR_PATH` environment variable. + cluster_dir : str + The full path to a cluster directory. This is useful if profiles + are not being used. + furl_or_file : str + A furl or a filename containing a FURL. This is useful if you + simply know the location of the FURL file. + furl_file_name : str + The filename (not the full path) of the FURL. This must be + provided if ``furl_or_file`` is not. + ipython_dir : str + The location of the ipython_dir if different from the default. + This is used if the cluster directory is being found by profile. + delay : float + The initial delay between re-connection attempts. Susequent delays + get longer according to ``delay[i] = 1.5*delay[i-1]``. + max_tries : int + The max number of re-connection attempts. + + Returns + ------- + A deferred to the actual client class. Or a failure to a + :exc:`FURLError`. """ - furl = find_furl(furl_or_file) - d = self.get_reference(furl) - def wrap_remote_reference(rr): + try: + furl_file = self._find_furl( + profile, cluster_dir, furl_or_file, + furl_file_name, ipython_dir + ) + except FURLError: + return defer.fail(failure.Failure()) + + def _wrap_remote_reference(rr): d = rr.callRemote('get_client_name') d.addCallback(lambda name: import_item(name)) def adapt(client_interface): @@ -146,5 +272,502 @@ class ClientConnector(object): d.addCallback(adapt) return d - d.addCallback(wrap_remote_reference) + + d = self._try_to_connect(furl_file, delay, max_tries, attempt=0) + d.addCallback(_wrap_remote_reference) + d.addErrback(self._handle_error, furl_file) + return d + + def _handle_error(self, f, furl_file): + raise ClientConnectorError('Could not connect to the controller ' + 'using the FURL file. This usually means that i) the controller ' + 'was not started or ii) a firewall was blocking the client from ' + 'connecting to the controller: %s' % furl_file) + + @inlineCallbacks + def _try_to_connect(self, furl_or_file, delay, max_tries, attempt): + """Try to connect to the controller with retry logic.""" + if attempt < max_tries: + log.msg("Connecting [%r]" % attempt) + try: + self.furl = find_furl(furl_or_file) + # Uncomment this to see the FURL being tried. + # log.msg("FURL: %s" % self.furl) + rr = yield self.get_reference(self.furl) + log.msg("Connected: %s" % furl_or_file) + except: + if attempt==max_tries-1: + # This will propagate the exception all the way to the top + # where it can be handled. + raise + else: + yield sleep_deferred(delay) + rr = yield self._try_to_connect( + furl_or_file, 1.5*delay, max_tries, attempt+1 + ) + returnValue(rr) + else: + returnValue(rr) + else: + raise ClientConnectorError( + 'Could not connect to controller, max_tries (%r) exceeded. ' + 'This usually means that i) the controller was not started, ' + 'or ii) a firewall was blocking the client from connecting ' + 'to the controller.' % max_tries + ) + + +class ClientConnector(object): + """A blocking version of a client connector. + + This class creates a single :class:`Tub` instance and allows remote + references and client to be retrieved by their FURLs. Remote references + are cached locally and FURL files can be found using profiles and cluster + directories. + """ + + def __init__(self): + self.async_cc = AsyncClientConnector() + + def get_task_client(self, profile='default', cluster_dir=None, + furl_or_file=None, ipython_dir=None, + delay=DELAY, max_tries=MAX_TRIES): + """Get the task client. + + Usually only the ``profile`` option will be needed. If a FURL file + can't be found by its profile, use ``cluster_dir`` or + ``furl_or_file``. + + Parameters + ---------- + profile : str + The name of a cluster directory profile (default="default"). The + cluster directory "cluster_" will be searched for + in ``os.getcwd()``, the ipython_dir and then in the directories + listed in the :env:`IPCLUSTER_DIR_PATH` environment variable. + cluster_dir : str + The full path to a cluster directory. This is useful if profiles + are not being used. + furl_or_file : str + A furl or a filename containing a FURLK. This is useful if you + simply know the location of the FURL file. + ipython_dir : str + The location of the ipython_dir if different from the default. + This is used if the cluster directory is being found by profile. + delay : float + The initial delay between re-connection attempts. Susequent delays + get longer according to ``delay[i] = 1.5*delay[i-1]``. + max_tries : int + The max number of re-connection attempts. + + Returns + ------- + The task client instance. + """ + client = blockingCallFromThread( + self.async_cc.get_task_client, profile, cluster_dir, + furl_or_file, ipython_dir, delay, max_tries + ) + return client.adapt_to_blocking_client() + + def get_multiengine_client(self, profile='default', cluster_dir=None, + furl_or_file=None, ipython_dir=None, + delay=DELAY, max_tries=MAX_TRIES): + """Get the multiengine client. + + Usually only the ``profile`` option will be needed. If a FURL file + can't be found by its profile, use ``cluster_dir`` or + ``furl_or_file``. + + Parameters + ---------- + profile : str + The name of a cluster directory profile (default="default"). The + cluster directory "cluster_" will be searched for + in ``os.getcwd()``, the ipython_dir and then in the directories + listed in the :env:`IPCLUSTER_DIR_PATH` environment variable. + cluster_dir : str + The full path to a cluster directory. This is useful if profiles + are not being used. + furl_or_file : str + A furl or a filename containing a FURLK. This is useful if you + simply know the location of the FURL file. + ipython_dir : str + The location of the ipython_dir if different from the default. + This is used if the cluster directory is being found by profile. + delay : float + The initial delay between re-connection attempts. Susequent delays + get longer according to ``delay[i] = 1.5*delay[i-1]``. + max_tries : int + The max number of re-connection attempts. + + Returns + ------- + The multiengine client instance. + """ + client = blockingCallFromThread( + self.async_cc.get_multiengine_client, profile, cluster_dir, + furl_or_file, ipython_dir, delay, max_tries + ) + return client.adapt_to_blocking_client() + + def get_client(self, profile='default', cluster_dir=None, + furl_or_file=None, ipython_dir=None, + delay=DELAY, max_tries=MAX_TRIES): + client = blockingCallFromThread( + self.async_cc.get_client, profile, cluster_dir, + furl_or_file, ipython_dir, + delay, max_tries + ) + return client.adapt_to_blocking_client() + + +class ClusterStateError(Exception): + pass + + +class AsyncCluster(object): + """An class that wraps the :command:`ipcluster` script.""" + + def __init__(self, profile='default', cluster_dir=None, ipython_dir=None, + auto_create=False, auto_stop=True): + """Create a class to manage an IPython cluster. + + This class calls the :command:`ipcluster` command with the right + options to start an IPython cluster. Typically a cluster directory + must be created (:command:`ipcluster create`) and configured before + using this class. Configuration is done by editing the + configuration files in the top level of the cluster directory. + + Parameters + ---------- + profile : str + The name of a cluster directory profile (default="default"). The + cluster directory "cluster_" will be searched for + in ``os.getcwd()``, the ipython_dir and then in the directories + listed in the :env:`IPCLUSTER_DIR_PATH` environment variable. + cluster_dir : str + The full path to a cluster directory. This is useful if profiles + are not being used. + ipython_dir : str + The location of the ipython_dir if different from the default. + This is used if the cluster directory is being found by profile. + auto_create : bool + Automatically create the cluster directory it is dones't exist. + This will usually only make sense if using a local cluster + (default=False). + auto_stop : bool + Automatically stop the cluster when this instance is garbage + collected (default=True). This is useful if you want the cluster + to live beyond your current process. There is also an instance + attribute ``auto_stop`` to change this behavior. + """ + self._setup_cluster_dir(profile, cluster_dir, ipython_dir, auto_create) + self.state = 'before' + self.launcher = None + self.client_connector = None + self.auto_stop = auto_stop + + def __del__(self): + if self.auto_stop and self.state=='running': + print "Auto stopping the cluster..." + self.stop() + + @property + def location(self): + if hasattr(self, 'cluster_dir_obj'): + return self.cluster_dir_obj.location + else: + return '' + + @property + def running(self): + if self.state=='running': + return True + else: + return False + + def _setup_cluster_dir(self, profile, cluster_dir, ipython_dir, auto_create): + if ipython_dir is None: + ipython_dir = get_ipython_dir() + if cluster_dir is not None: + try: + self.cluster_dir_obj = ClusterDir.find_cluster_dir(cluster_dir) + except ClusterDirError: + pass + if profile is not None: + try: + self.cluster_dir_obj = ClusterDir.find_cluster_dir_by_profile( + ipython_dir, profile) + except ClusterDirError: + pass + if auto_create or profile=='default': + # This should call 'ipcluster create --profile default + self.cluster_dir_obj = ClusterDir.create_cluster_dir_by_profile( + ipython_dir, profile) + else: + raise ClusterDirError('Cluster dir not found.') + + @make_deferred + def start(self, n=2): + """Start the IPython cluster with n engines. + + Parameters + ---------- + n : int + The number of engine to start. + """ + # We might want to add logic to test if the cluster has started + # by another process.... + if not self.state=='running': + self.launcher = IPClusterLauncher(os.getcwd()) + self.launcher.ipcluster_n = n + self.launcher.ipcluster_subcommand = 'start' + d = self.launcher.start() + d.addCallback(self._handle_start) + return d + else: + raise ClusterStateError('Cluster is already running') + + @make_deferred + def stop(self): + """Stop the IPython cluster if it is running.""" + if self.state=='running': + d1 = self.launcher.observe_stop() + d1.addCallback(self._handle_stop) + d2 = self.launcher.stop() + return gatherBoth([d1, d2], consumeErrors=True) + else: + raise ClusterStateError("Cluster not running") + + def get_multiengine_client(self, delay=DELAY, max_tries=MAX_TRIES): + """Get the multiengine client for the running cluster. + + If this fails, it means that the cluster has not finished starting. + Usually waiting a few seconds are re-trying will solve this. + """ + if self.client_connector is None: + self.client_connector = AsyncClientConnector() + return self.client_connector.get_multiengine_client( + cluster_dir=self.cluster_dir_obj.location, + delay=delay, max_tries=max_tries + ) + + def get_task_client(self, delay=DELAY, max_tries=MAX_TRIES): + """Get the task client for the running cluster. + + If this fails, it means that the cluster has not finished starting. + Usually waiting a few seconds are re-trying will solve this. + """ + if self.client_connector is None: + self.client_connector = AsyncClientConnector() + return self.client_connector.get_task_client( + cluster_dir=self.cluster_dir_obj.location, + delay=delay, max_tries=max_tries + ) + + def get_ipengine_logs(self): + return self.get_logs_by_name('ipengine') + + def get_ipcontroller_logs(self): + return self.get_logs_by_name('ipcontroller') + + def get_ipcluster_logs(self): + return self.get_logs_by_name('ipcluster') + + def get_logs_by_name(self, name='ipcluster'): + log_dir = self.cluster_dir_obj.log_dir + logs = {} + for log in os.listdir(log_dir): + if log.startswith(name + '-') and log.endswith('.log'): + with open(os.path.join(log_dir, log), 'r') as f: + logs[log] = f.read() + return logs + + def get_logs(self): + d = self.get_ipcluster_logs() + d.update(self.get_ipengine_logs()) + d.update(self.get_ipcontroller_logs()) return d + + def _handle_start(self, r): + self.state = 'running' + + def _handle_stop(self, r): + self.state = 'after' + + +class Cluster(object): + + + def __init__(self, profile='default', cluster_dir=None, ipython_dir=None, + auto_create=False, auto_stop=True): + """Create a class to manage an IPython cluster. + + This class calls the :command:`ipcluster` command with the right + options to start an IPython cluster. Typically a cluster directory + must be created (:command:`ipcluster create`) and configured before + using this class. Configuration is done by editing the + configuration files in the top level of the cluster directory. + + Parameters + ---------- + profile : str + The name of a cluster directory profile (default="default"). The + cluster directory "cluster_" will be searched for + in ``os.getcwd()``, the ipython_dir and then in the directories + listed in the :env:`IPCLUSTER_DIR_PATH` environment variable. + cluster_dir : str + The full path to a cluster directory. This is useful if profiles + are not being used. + ipython_dir : str + The location of the ipython_dir if different from the default. + This is used if the cluster directory is being found by profile. + auto_create : bool + Automatically create the cluster directory it is dones't exist. + This will usually only make sense if using a local cluster + (default=False). + auto_stop : bool + Automatically stop the cluster when this instance is garbage + collected (default=True). This is useful if you want the cluster + to live beyond your current process. There is also an instance + attribute ``auto_stop`` to change this behavior. + """ + self.async_cluster = AsyncCluster( + profile, cluster_dir, ipython_dir, auto_create, auto_stop + ) + self.cluster_dir_obj = self.async_cluster.cluster_dir_obj + self.client_connector = None + + def _set_auto_stop(self, value): + self.async_cluster.auto_stop = value + + def _get_auto_stop(self): + return self.async_cluster.auto_stop + + auto_stop = property(_get_auto_stop, _set_auto_stop) + + @property + def location(self): + return self.async_cluster.location + + @property + def running(self): + return self.async_cluster.running + + def start(self, n=2): + """Start the IPython cluster with n engines. + + Parameters + ---------- + n : int + The number of engine to start. + """ + return blockingCallFromThread(self.async_cluster.start, n) + + def stop(self): + """Stop the IPython cluster if it is running.""" + return blockingCallFromThread(self.async_cluster.stop) + + def get_multiengine_client(self, delay=DELAY, max_tries=MAX_TRIES): + """Get the multiengine client for the running cluster. + + This will try to attempt to the controller multiple times. If this + fails altogether, try looking at the following: + * Make sure the controller is starting properly by looking at its + log files. + * Make sure the controller is writing its FURL file in the location + expected by the client. + * Make sure a firewall on the controller's host is not blocking the + client from connecting. + + Parameters + ---------- + delay : float + The initial delay between re-connection attempts. Susequent delays + get longer according to ``delay[i] = 1.5*delay[i-1]``. + max_tries : int + The max number of re-connection attempts. + """ + if self.client_connector is None: + self.client_connector = ClientConnector() + return self.client_connector.get_multiengine_client( + cluster_dir=self.cluster_dir_obj.location, + delay=delay, max_tries=max_tries + ) + + def get_task_client(self, delay=DELAY, max_tries=MAX_TRIES): + """Get the task client for the running cluster. + + This will try to attempt to the controller multiple times. If this + fails altogether, try looking at the following: + * Make sure the controller is starting properly by looking at its + log files. + * Make sure the controller is writing its FURL file in the location + expected by the client. + * Make sure a firewall on the controller's host is not blocking the + client from connecting. + + Parameters + ---------- + delay : float + The initial delay between re-connection attempts. Susequent delays + get longer according to ``delay[i] = 1.5*delay[i-1]``. + max_tries : int + The max number of re-connection attempts. + """ + if self.client_connector is None: + self.client_connector = ClientConnector() + return self.client_connector.get_task_client( + cluster_dir=self.cluster_dir_obj.location, + delay=delay, max_tries=max_tries + ) + + def __repr__(self): + s = "". + """ + if not os.path.isdir(path): + raise ClusterDirError('Directory not found: %s' % path) + cluster_dir = os.path.join(path, u'cluster_' + profile) + return ClusterDir(cluster_dir) + + @classmethod + def find_cluster_dir_by_profile(cls, ipython_dir, profile=u'default'): + """Find an existing cluster dir by profile name, return its ClusterDir. + + This searches through a sequence of paths for a cluster dir. If it + is not found, a :class:`ClusterDirError` exception will be raised. + + The search path algorithm is: + 1. ``os.getcwd()`` + 2. ``ipython_dir`` + 3. The directories found in the ":" separated + :env:`IPCLUSTER_DIR_PATH` environment variable. + + Parameters + ---------- + ipython_dir : unicode or str + The IPython directory to use. + profile : unicode or str + The name of the profile. The name of the cluster directory + will be "cluster_". + """ + dirname = u'cluster_' + profile + cluster_dir_paths = os.environ.get('IPCLUSTER_DIR_PATH','') + if cluster_dir_paths: + cluster_dir_paths = cluster_dir_paths.split(':') + else: + cluster_dir_paths = [] + paths = [os.getcwd(), ipython_dir] + cluster_dir_paths + for p in paths: + cluster_dir = os.path.join(p, dirname) + if os.path.isdir(cluster_dir): + return ClusterDir(cluster_dir) + else: + raise ClusterDirError('Cluster directory not found in paths: %s' % dirname) + + @classmethod + def find_cluster_dir(cls, cluster_dir): + """Find/create a cluster dir and return its ClusterDir. + + This will create the cluster directory if it doesn't exist. + + Parameters + ---------- + cluster_dir : unicode or str + The path of the cluster directory. This is expanded using + :func:`IPython.utils.genutils.expand_path`. + """ + cluster_dir = genutils.expand_path(cluster_dir) + if not os.path.isdir(cluster_dir): + raise ClusterDirError('Cluster directory not found: %s' % cluster_dir) + return ClusterDir(cluster_dir) + + +class AppWithClusterDirArgParseConfigLoader(ArgParseConfigLoader): + """Default command line options for IPython cluster applications.""" + + def _add_other_arguments(self): + self.parser.add_argument('--ipython-dir', + dest='Global.ipython_dir',type=unicode, + help='Set to override default location of Global.ipython_dir.', + default=NoConfigDefault, + metavar='Global.ipython_dir' + ) + self.parser.add_argument('-p', '--profile', + dest='Global.profile',type=unicode, + help='The string name of the profile to be used. This determines ' + 'the name of the cluster dir as: cluster_. The default profile ' + 'is named "default". The cluster directory is resolve this way ' + 'if the --cluster-dir option is not used.', + default=NoConfigDefault, + metavar='Global.profile' + ) + self.parser.add_argument('--log-level', + dest="Global.log_level",type=int, + help='Set the log level (0,10,20,30,40,50). Default is 30.', + default=NoConfigDefault, + metavar="Global.log_level" + ) + self.parser.add_argument('--cluster-dir', + dest='Global.cluster_dir',type=unicode, + help='Set the cluster dir. This overrides the logic used by the ' + '--profile option.', + default=NoConfigDefault, + metavar='Global.cluster_dir' + ), + self.parser.add_argument('--work-dir', + dest='Global.work_dir',type=unicode, + help='Set the working dir for the process.', + default=NoConfigDefault, + metavar='Global.work_dir' + ) + self.parser.add_argument('--clean-logs', + dest='Global.clean_logs', action='store_true', + help='Delete old log flies before starting.', + default=NoConfigDefault + ) + self.parser.add_argument('--no-clean-logs', + dest='Global.clean_logs', action='store_false', + help="Don't Delete old log flies before starting.", + default=NoConfigDefault + ) + +class ApplicationWithClusterDir(Application): + """An application that puts everything into a cluster directory. + + Instead of looking for things in the ipython_dir, this type of application + will use its own private directory called the "cluster directory" + for things like config files, log files, etc. + + The cluster directory is resolved as follows: + + * If the ``--cluster-dir`` option is given, it is used. + * If ``--cluster-dir`` is not given, the application directory is + resolve using the profile name as ``cluster_``. The search + path for this directory is then i) cwd if it is found there + and ii) in ipython_dir otherwise. + + The config file for the application is to be put in the cluster + dir and named the value of the ``config_file_name`` class attribute. + """ + + auto_create_cluster_dir = True + + def create_default_config(self): + super(ApplicationWithClusterDir, self).create_default_config() + self.default_config.Global.profile = u'default' + self.default_config.Global.cluster_dir = u'' + self.default_config.Global.work_dir = os.getcwd() + self.default_config.Global.log_to_file = False + self.default_config.Global.clean_logs = False + + def create_command_line_config(self): + """Create and return a command line config loader.""" + return AppWithClusterDirArgParseConfigLoader( + description=self.description, + version=release.version + ) + + def find_resources(self): + """This resolves the cluster directory. + + This tries to find the cluster directory and if successful, it will + have done: + * Sets ``self.cluster_dir_obj`` to the :class:`ClusterDir` object for + the application. + * Sets ``self.cluster_dir`` attribute of the application and config + objects. + + The algorithm used for this is as follows: + 1. Try ``Global.cluster_dir``. + 2. Try using ``Global.profile``. + 3. If both of these fail and ``self.auto_create_cluster_dir`` is + ``True``, then create the new cluster dir in the IPython directory. + 4. If all fails, then raise :class:`ClusterDirError`. + """ + + try: + cluster_dir = self.command_line_config.Global.cluster_dir + except AttributeError: + cluster_dir = self.default_config.Global.cluster_dir + cluster_dir = genutils.expand_path(cluster_dir) + try: + self.cluster_dir_obj = ClusterDir.find_cluster_dir(cluster_dir) + except ClusterDirError: + pass + else: + self.log.info('Using existing cluster dir: %s' % \ + self.cluster_dir_obj.location + ) + self.finish_cluster_dir() + return + + try: + self.profile = self.command_line_config.Global.profile + except AttributeError: + self.profile = self.default_config.Global.profile + try: + self.cluster_dir_obj = ClusterDir.find_cluster_dir_by_profile( + self.ipython_dir, self.profile) + except ClusterDirError: + pass + else: + self.log.info('Using existing cluster dir: %s' % \ + self.cluster_dir_obj.location + ) + self.finish_cluster_dir() + return + + if self.auto_create_cluster_dir: + self.cluster_dir_obj = ClusterDir.create_cluster_dir_by_profile( + self.ipython_dir, self.profile + ) + self.log.info('Creating new cluster dir: %s' % \ + self.cluster_dir_obj.location + ) + self.finish_cluster_dir() + else: + raise ClusterDirError('Could not find a valid cluster directory.') + + def finish_cluster_dir(self): + # Set the cluster directory + self.cluster_dir = self.cluster_dir_obj.location + + # These have to be set because they could be different from the one + # that we just computed. Because command line has the highest + # priority, this will always end up in the master_config. + self.default_config.Global.cluster_dir = self.cluster_dir + self.command_line_config.Global.cluster_dir = self.cluster_dir + + # Set the search path to the cluster directory + self.config_file_paths = (self.cluster_dir,) + + def find_config_file_name(self): + """Find the config file name for this application.""" + # For this type of Application it should be set as a class attribute. + if not hasattr(self, 'config_file_name'): + self.log.critical("No config filename found") + + def find_config_file_paths(self): + # Set the search path to the cluster directory + self.config_file_paths = (self.cluster_dir,) + + def pre_construct(self): + # The log and security dirs were set earlier, but here we put them + # into the config and log them. + config = self.master_config + sdir = self.cluster_dir_obj.security_dir + self.security_dir = config.Global.security_dir = sdir + ldir = self.cluster_dir_obj.log_dir + self.log_dir = config.Global.log_dir = ldir + pdir = self.cluster_dir_obj.pid_dir + self.pid_dir = config.Global.pid_dir = pdir + self.log.info("Cluster directory set to: %s" % self.cluster_dir) + config.Global.work_dir = unicode(genutils.expand_path(config.Global.work_dir)) + # Change to the working directory. We do this just before construct + # is called so all the components there have the right working dir. + self.to_work_dir() + + def to_work_dir(self): + wd = self.master_config.Global.work_dir + if unicode(wd) != unicode(os.getcwd()): + os.chdir(wd) + self.log.info("Changing to working dir: %s" % wd) + + def start_logging(self): + # Remove old log files + if self.master_config.Global.clean_logs: + log_dir = self.master_config.Global.log_dir + for f in os.listdir(log_dir): + if f.startswith(self.name + u'-') and f.endswith('.log'): + os.remove(os.path.join(log_dir, f)) + # Start logging to the new log file + if self.master_config.Global.log_to_file: + log_filename = self.name + u'-' + str(os.getpid()) + u'.log' + logfile = os.path.join(self.log_dir, log_filename) + open_log_file = open(logfile, 'w') + else: + open_log_file = sys.stdout + log.startLogging(open_log_file) + + def write_pid_file(self, overwrite=False): + """Create a .pid file in the pid_dir with my pid. + + This must be called after pre_construct, which sets `self.pid_dir`. + This raises :exc:`PIDFileError` if the pid file exists already. + """ + pid_file = os.path.join(self.pid_dir, self.name + u'.pid') + if os.path.isfile(pid_file): + pid = self.get_pid_from_file() + if not overwrite: + raise PIDFileError( + 'The pid file [%s] already exists. \nThis could mean that this ' + 'server is already running with [pid=%s].' % (pid_file, pid) + ) + with open(pid_file, 'w') as f: + self.log.info("Creating pid file: %s" % pid_file) + f.write(repr(os.getpid())+'\n') + + def remove_pid_file(self): + """Remove the pid file. + + This should be called at shutdown by registering a callback with + :func:`reactor.addSystemEventTrigger`. This needs to return + ``None``. + """ + pid_file = os.path.join(self.pid_dir, self.name + u'.pid') + if os.path.isfile(pid_file): + try: + self.log.info("Removing pid file: %s" % pid_file) + os.remove(pid_file) + except: + self.log.warn("Error removing the pid file: %s" % pid_file) + + def get_pid_from_file(self): + """Get the pid from the pid file. + + If the pid file doesn't exist a :exc:`PIDFileError` is raised. + """ + pid_file = os.path.join(self.pid_dir, self.name + u'.pid') + if os.path.isfile(pid_file): + with open(pid_file, 'r') as f: + pid = int(f.read().strip()) + return pid + else: + raise PIDFileError('pid file not found: %s' % pid_file) + + diff --git a/IPython/kernel/config/__init__.py b/IPython/kernel/config/__init__.py deleted file mode 100644 index b4845dc..0000000 --- a/IPython/kernel/config/__init__.py +++ /dev/null @@ -1,126 +0,0 @@ -# encoding: utf-8 - -"""Default kernel configuration.""" - -__docformat__ = "restructuredtext en" - -#------------------------------------------------------------------------------- -# Copyright (C) 2008 The IPython Development Team -# -# Distributed under the terms of the BSD License. The full license is in -# the file COPYING, distributed as part of this software. -#------------------------------------------------------------------------------- - -#------------------------------------------------------------------------------- -# Imports -#------------------------------------------------------------------------------- - -import os, sys -from os.path import join as pjoin - -from IPython.external.configobj import ConfigObj -from IPython.config.api import ConfigObjManager -from IPython.genutils import get_ipython_dir, get_security_dir - -default_kernel_config = ConfigObj() - -# This will raise OSError if ipythondir doesn't exist. -security_dir = get_security_dir() - -#------------------------------------------------------------------------------- -# Engine Configuration -#------------------------------------------------------------------------------- - -engine_config = dict( - logfile = '', # Empty means log to stdout - furl_file = pjoin(security_dir, 'ipcontroller-engine.furl') -) - -#------------------------------------------------------------------------------- -# MPI Configuration -#------------------------------------------------------------------------------- - -mpi_config = dict( - mpi4py = """from mpi4py import MPI as mpi -mpi.size = mpi.COMM_WORLD.Get_size() -mpi.rank = mpi.COMM_WORLD.Get_rank() -""", - pytrilinos = """from PyTrilinos import Epetra -class SimpleStruct: - pass -mpi = SimpleStruct() -mpi.rank = 0 -mpi.size = 0 -""", - default = '' -) - -#------------------------------------------------------------------------------- -# Controller Configuration -#------------------------------------------------------------------------------- - -controller_config = dict( - - logfile = '', # Empty means log to stdout - import_statement = '', - reuse_furls = False, # If False, old furl files are deleted - - engine_tub = dict( - ip = '', # Empty string means all interfaces - port = 0, # 0 means pick a port for me - location = '', # Empty string means try to set automatically - secure = True, - cert_file = pjoin(security_dir, 'ipcontroller-engine.pem'), - ), - engine_fc_interface = 'IPython.kernel.enginefc.IFCControllerBase', - engine_furl_file = pjoin(security_dir, 'ipcontroller-engine.furl'), - - controller_interfaces = dict( - # multiengine = dict( - # controller_interface = 'IPython.kernel.multiengine.IMultiEngine', - # fc_interface = 'IPython.kernel.multienginefc.IFCMultiEngine', - # furl_file = 'ipcontroller-mec.furl' - # ), - task = dict( - controller_interface = 'IPython.kernel.task.ITaskController', - fc_interface = 'IPython.kernel.taskfc.IFCTaskController', - furl_file = pjoin(security_dir, 'ipcontroller-tc.furl') - ), - multiengine = dict( - controller_interface = 'IPython.kernel.multiengine.IMultiEngine', - fc_interface = 'IPython.kernel.multienginefc.IFCSynchronousMultiEngine', - furl_file = pjoin(security_dir, 'ipcontroller-mec.furl') - ) - ), - - client_tub = dict( - ip = '', # Empty string means all interfaces - port = 0, # 0 means pick a port for me - location = '', # Empty string means try to set automatically - secure = True, - cert_file = pjoin(security_dir, 'ipcontroller-client.pem') - ) -) - -#------------------------------------------------------------------------------- -# Client Configuration -#------------------------------------------------------------------------------- - -client_config = dict( - client_interfaces = dict( - task = dict( - furl_file = pjoin(security_dir, 'ipcontroller-tc.furl') - ), - multiengine = dict( - furl_file = pjoin(security_dir, 'ipcontroller-mec.furl') - ) - ) -) - -default_kernel_config['engine'] = engine_config -default_kernel_config['mpi'] = mpi_config -default_kernel_config['controller'] = controller_config -default_kernel_config['client'] = client_config - - -config_manager = ConfigObjManager(default_kernel_config, 'IPython.kernel.ini') \ No newline at end of file diff --git a/IPython/kernel/configobjfactory.py b/IPython/kernel/configobjfactory.py new file mode 100644 index 0000000..745cdfb --- /dev/null +++ b/IPython/kernel/configobjfactory.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +A class for creating a Twisted service that is configured using IPython's +configuration system. +""" + +#----------------------------------------------------------------------------- +# Copyright (C) 2008-2009 The IPython Development Team +# +# Distributed under the terms of the BSD License. The full license is in +# the file COPYING, distributed as part of this software. +#----------------------------------------------------------------------------- + +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- + +import zope.interface as zi + +from IPython.core.component import Component + +#----------------------------------------------------------------------------- +# Code +#----------------------------------------------------------------------------- + + +class IConfiguredObjectFactory(zi.Interface): + """I am a component that creates a configured object. + + This class is useful if you want to configure a class that is not a + subclass of :class:`IPython.core.component.Component`. + """ + + def __init__(config): + """Get ready to configure the object using config.""" + + def create(): + """Return an instance of the configured object.""" + + +class ConfiguredObjectFactory(Component): + + zi.implements(IConfiguredObjectFactory) + + def __init__(self, config): + super(ConfiguredObjectFactory, self).__init__(None, config=config) + + def create(self): + raise NotImplementedError('create must be implemented in a subclass') + + +class IAdaptedConfiguredObjectFactory(zi.Interface): + """I am a component that adapts and configures an object. + + This class is useful if you have the adapt an instance and configure it. + """ + + def __init__(config, adaptee=None): + """Get ready to adapt adaptee and then configure it using config.""" + + def create(): + """Return an instance of the adapted and configured object.""" + + +class AdaptedConfiguredObjectFactory(Component): + + # zi.implements(IAdaptedConfiguredObjectFactory) + + def __init__(self, config, adaptee): + # print + # print "config pre:", config + super(AdaptedConfiguredObjectFactory, self).__init__(None, config=config) + # print + # print "config post:", config + self.adaptee = adaptee + + def create(self): + raise NotImplementedError('create must be implemented in a subclass') \ No newline at end of file diff --git a/IPython/kernel/contexts.py b/IPython/kernel/contexts.py index 949688e..2e9daa3 100644 --- a/IPython/kernel/contexts.py +++ b/IPython/kernel/contexts.py @@ -26,8 +26,8 @@ import sys from twisted.internet.error import ConnectionRefusedError -from IPython.ultraTB import _fixed_getinnerframes, findsource -from IPython import ipapi +from IPython.core.ultratb import _fixed_getinnerframes, findsource +from IPython.core import ipapi from IPython.kernel import error @@ -108,9 +108,9 @@ class RemoteContextBase(object): return strip_whitespace(wsource) def _findsource_ipython(self,f): - from IPython import ipapi + from IPython.core import ipapi self.ip = ipapi.get() - buf = self.ip.IP.input_hist_raw[-1].splitlines()[1:] + buf = self.ip.input_hist_raw[-1].splitlines()[1:] wsource = [l+'\n' for l in buf ] return strip_whitespace(wsource) diff --git a/IPython/kernel/controllerservice.py b/IPython/kernel/controllerservice.py index 3fe5f41..10ad03b 100644 --- a/IPython/kernel/controllerservice.py +++ b/IPython/kernel/controllerservice.py @@ -50,7 +50,7 @@ from IPython.kernel.engineservice import \ IEngineSerialized, \ IEngineQueued -from IPython.genutils import get_ipython_dir +from IPython.utils.genutils import get_ipython_dir from IPython.kernel import codeutil #------------------------------------------------------------------------------- diff --git a/IPython/kernel/core/config/__init__.py b/IPython/kernel/core/config/__init__.py deleted file mode 100644 index 6f60906..0000000 --- a/IPython/kernel/core/config/__init__.py +++ /dev/null @@ -1,25 +0,0 @@ -# encoding: utf-8 - -__docformat__ = "restructuredtext en" - -#------------------------------------------------------------------------------- -# Copyright (C) 2008 The IPython Development Team -# -# Distributed under the terms of the BSD License. The full license is in -# the file COPYING, distributed as part of this software. -#------------------------------------------------------------------------------- - -#------------------------------------------------------------------------------- -# Imports -#------------------------------------------------------------------------------- - -from IPython.external.configobj import ConfigObj -from IPython.config.api import ConfigObjManager - -default_core_config = ConfigObj() -default_core_config['shell'] = dict( - shell_class = 'IPython.kernel.core.interpreter.Interpreter', - import_statement = '' -) - -config_manager = ConfigObjManager(default_core_config, 'IPython.kernel.core.ini') \ No newline at end of file diff --git a/IPython/kernel/core/interpreter.py b/IPython/kernel/core/interpreter.py index 05cec24..56f8ce7 100644 --- a/IPython/kernel/core/interpreter.py +++ b/IPython/kernel/core/interpreter.py @@ -29,7 +29,7 @@ import sys import traceback # Local imports. -from IPython.kernel.core import ultraTB +from IPython.core import ultratb from IPython.kernel.core.display_trap import DisplayTrap from IPython.kernel.core.macro import Macro from IPython.kernel.core.prompts import CachedOutput @@ -167,9 +167,9 @@ class Interpreter(object): formatters=self.traceback_formatters) # This is used temporarily for reformating exceptions in certain - # cases. It will go away once the ultraTB stuff is ported + # cases. It will go away once the ultratb stuff is ported # to ipython1 - self.tbHandler = ultraTB.FormattedTB(color_scheme='NoColor', + self.tbHandler = ultratb.FormattedTB(color_scheme='NoColor', mode='Context', tb_offset=2) @@ -729,8 +729,8 @@ class Interpreter(object): def error(self, text): """ Pass an error message back to the shell. - Preconditions - ------------- + Notes + ----- This should only be called when self.message is set. In other words, when code is being executed. diff --git a/IPython/kernel/core/notification.py b/IPython/kernel/core/notification.py deleted file mode 100644 index 7762d3a..0000000 --- a/IPython/kernel/core/notification.py +++ /dev/null @@ -1,125 +0,0 @@ -# encoding: utf-8 - -"""The IPython Core Notification Center. - -See docs/source/development/notification_blueprint.txt for an overview of the -notification module. -""" - -__docformat__ = "restructuredtext en" - -#----------------------------------------------------------------------------- -# Copyright (C) 2008 The IPython Development Team -# -# Distributed under the terms of the BSD License. The full license is in -# the file COPYING, distributed as part of this software. -#----------------------------------------------------------------------------- - -# Tell nose to skip the testing of this module -__test__ = {} - -class NotificationCenter(object): - """Synchronous notification center - - Example - ------- - >>> import IPython.kernel.core.notification as notification - >>> def callback(theType, theSender, args={}): - ... print theType,theSender,args - ... - >>> notification.sharedCenter.add_observer(callback, 'NOTIFICATION_TYPE', None) - >>> notification.sharedCenter.post_notification('NOTIFICATION_TYPE', object()) # doctest:+ELLIPSIS - NOTIFICATION_TYPE ... - - """ - def __init__(self): - super(NotificationCenter, self).__init__() - self._init_observers() - - - def _init_observers(self): - """Initialize observer storage""" - - self.registered_types = set() #set of types that are observed - self.registered_senders = set() #set of senders that are observed - self.observers = {} #map (type,sender) => callback (callable) - - - def post_notification(self, theType, sender, **kwargs): - """Post notification (type,sender,**kwargs) to all registered - observers. - - Implementation - -------------- - * If no registered observers, performance is O(1). - * Notificaiton order is undefined. - * Notifications are posted synchronously. - """ - - if(theType==None or sender==None): - raise Exception("NotificationCenter.post_notification requires \ - type and sender.") - - # If there are no registered observers for the type/sender pair - if((theType not in self.registered_types and - None not in self.registered_types) or - (sender not in self.registered_senders and - None not in self.registered_senders)): - return - - for o in self._observers_for_notification(theType, sender): - o(theType, sender, args=kwargs) - - - def _observers_for_notification(self, theType, sender): - """Find all registered observers that should recieve notification""" - - keys = ( - (theType,sender), - (theType, None), - (None, sender), - (None,None) - ) - - - obs = set() - for k in keys: - obs.update(self.observers.get(k, set())) - - return obs - - - def add_observer(self, callback, theType, sender): - """Add an observer callback to this notification center. - - The given callback will be called upon posting of notifications of - the given type/sender and will receive any additional kwargs passed - to post_notification. - - Parameters - ---------- - observerCallback : callable - Callable. Must take at least two arguments:: - observerCallback(type, sender, args={}) - - theType : hashable - The notification type. If None, all notifications from sender - will be posted. - - sender : hashable - The notification sender. If None, all notifications of theType - will be posted. - """ - assert(callback != None) - self.registered_types.add(theType) - self.registered_senders.add(sender) - self.observers.setdefault((theType,sender), set()).add(callback) - - def remove_all_observers(self): - """Removes all observers from this notification center""" - - self._init_observers() - - - -sharedCenter = NotificationCenter() \ No newline at end of file diff --git a/IPython/kernel/core/prompts.py b/IPython/kernel/core/prompts.py index be654ac..1759df7 100644 --- a/IPython/kernel/core/prompts.py +++ b/IPython/kernel/core/prompts.py @@ -21,30 +21,27 @@ __docformat__ = "restructuredtext en" # Required modules import __builtin__ -import os import socket import sys -import time # IPython's own from IPython.external.Itpl import ItplNS -from macro import Macro -from IPython import ColorANSI -from IPython import Release -from IPython.ipapi import TryNext -from IPython.ipstruct import Struct -from IPython.genutils import * +from IPython.utils import coloransi +from IPython.core import release +from IPython.core.error import TryNext +from IPython.utils.genutils import * +import IPython.utils.generics #**************************************************************************** #Color schemes for Prompts. -PromptColors = ColorANSI.ColorSchemeTable() -InputColors = ColorANSI.InputTermColors # just a shorthand -Colors = ColorANSI.TermColors # just a shorthand +PromptColors = coloransi.ColorSchemeTable() +InputColors = coloransi.InputTermColors # just a shorthand +Colors = coloransi.TermColors # just a shorthand -__PColNoColor = ColorANSI.ColorScheme( +__PColNoColor = coloransi.ColorScheme( 'NoColor', in_prompt = InputColors.NoColor, # Input prompt in_number = InputColors.NoColor, # Input prompt number @@ -156,7 +153,7 @@ prompt_specials_color = { # Carriage return r'\r': '\r', # Release version - r'\v': Release.version, + r'\v': release.version, # Root symbol ($ or #) r'\$': ROOT_SYMBOL, } @@ -172,7 +169,7 @@ prompt_specials_nocolor[r'\#'] = '${self.cache.prompt_count}' # with a color name which may begin with a letter used by any other of the # allowed specials. This of course means that \\C will never be allowed for # anything else. -input_colors = ColorANSI.InputTermColors +input_colors = coloransi.InputTermColors for _color in dir(input_colors): if _color[0] != '_': c_name = r'\C_'+_color @@ -535,7 +532,7 @@ class CachedOutput: display, e.g. when your own objects need special formatting. """ try: - return IPython.generics.result_display(arg) + return IPython.utils.generics.result_display(arg) except TryNext: return self.shell.hooks.result_display(arg) diff --git a/IPython/kernel/core/sync_traceback_trap.py b/IPython/kernel/core/sync_traceback_trap.py index cf9e1bf..973711b 100644 --- a/IPython/kernel/core/sync_traceback_trap.py +++ b/IPython/kernel/core/sync_traceback_trap.py @@ -18,7 +18,7 @@ __docformat__ = "restructuredtext en" # Imports #------------------------------------------------------------------------------- from traceback_trap import TracebackTrap -from IPython.ultraTB import ColorTB +from IPython.core.ultratb import ColorTB class SyncTracebackTrap(TracebackTrap): """ TracebackTrap that displays immediatly the traceback in addition diff --git a/IPython/kernel/core/util.py b/IPython/kernel/core/util.py index 1dceb4a..ae69642 100644 --- a/IPython/kernel/core/util.py +++ b/IPython/kernel/core/util.py @@ -106,6 +106,9 @@ def make_quoted_expr(s): def system_shell(cmd, verbose=False, debug=False, header=''): """ Execute a command in the system shell; always return None. + This returns None so it can be conveniently used in interactive loops + without getting the return value (typically 0) printed many times. + Parameters ---------- cmd : str @@ -117,11 +120,6 @@ def system_shell(cmd, verbose=False, debug=False, header=''): header : str Header to print to screen prior to the executed command. No extra newlines are added. - - Description - ----------- - This returns None so it can be conveniently used in interactive loops - without getting the return value (typically 0) printed many times. """ if verbose or debug: diff --git a/IPython/kernel/engineconnector.py b/IPython/kernel/engineconnector.py index 389a26d..dfa983f 100644 --- a/IPython/kernel/engineconnector.py +++ b/IPython/kernel/engineconnector.py @@ -1,32 +1,38 @@ +#!/usr/bin/env python # encoding: utf-8 """A class that manages the engines connection to the controller.""" -__docformat__ = "restructuredtext en" - -#------------------------------------------------------------------------------- -# Copyright (C) 2008 The IPython Development Team +#----------------------------------------------------------------------------- +# Copyright (C) 2008-2009 The IPython Development Team # # Distributed under the terms of the BSD License. The full license is in # the file COPYING, distributed as part of this software. -#------------------------------------------------------------------------------- +#----------------------------------------------------------------------------- -#------------------------------------------------------------------------------- +#----------------------------------------------------------------------------- # Imports -#------------------------------------------------------------------------------- +#----------------------------------------------------------------------------- import os import cPickle as pickle from twisted.python import log, failure from twisted.internet import defer +from twisted.internet.defer import inlineCallbacks, returnValue -from IPython.kernel.fcutil import find_furl +from IPython.kernel.fcutil import find_furl, validate_furl_or_file from IPython.kernel.enginefc import IFCEngine +from IPython.kernel.twistedutil import sleep_deferred, make_deferred -#------------------------------------------------------------------------------- +#----------------------------------------------------------------------------- # The ClientConnector class -#------------------------------------------------------------------------------- +#----------------------------------------------------------------------------- + + +class EngineConnectorError(Exception): + pass + class EngineConnector(object): """Manage an engines connection to a controller. @@ -38,8 +44,10 @@ class EngineConnector(object): def __init__(self, tub): self.tub = tub - - def connect_to_controller(self, engine_service, furl_or_file): + + @make_deferred + def connect_to_controller(self, engine_service, furl_or_file, + delay=0.1, max_tries=10): """ Make a connection to a controller specified by a furl. @@ -48,34 +56,73 @@ class EngineConnector(object): foolscap URL contains all the information needed to connect to the controller, including the ip and port as well as any encryption and authentication information needed for the connection. - + After getting a reference to the controller, this method calls the `register_engine` method of the controller to actually register the engine. - - :Parameters: - engine_service : IEngineBase - An instance of an `IEngineBase` implementer - furl_or_file : str - A furl or a filename containing a furl + + This method will try to connect to the controller multiple times with + a delay in between. Each time the FURL file is read anew. + + Parameters + __________ + engine_service : IEngineBase + An instance of an `IEngineBase` implementer + furl_or_file : str + A furl or a filename containing a furl + delay : float + The intial time to wait between connection attempts. Subsequent + attempts have increasing delays. + max_tries : int + The maximum number of connection attempts. + + Returns + ------- + A deferred to the registered client or a failure to an error + like :exc:`FURLError`. """ if not self.tub.running: self.tub.startService() self.engine_service = engine_service self.engine_reference = IFCEngine(self.engine_service) - try: - self.furl = find_furl(furl_or_file) - except ValueError: - return defer.fail(failure.Failure()) + + validate_furl_or_file(furl_or_file) + d = self._try_to_connect(furl_or_file, delay, max_tries, attempt=0) + d.addCallback(self._register) + return d + + @inlineCallbacks + def _try_to_connect(self, furl_or_file, delay, max_tries, attempt): + """Try to connect to the controller with retry logic.""" + if attempt < max_tries: + log.msg("Attempting to connect to controller [%r]: %s" % \ + (attempt, furl_or_file)) + try: + self.furl = find_furl(furl_or_file) + # Uncomment this to see the FURL being tried. + # log.msg("FURL: %s" % self.furl) + rr = yield self.tub.getReference(self.furl) + except: + if attempt==max_tries-1: + # This will propagate the exception all the way to the top + # where it can be handled. + raise + else: + yield sleep_deferred(delay) + rr = yield self._try_to_connect( + furl_or_file, 1.5*delay, max_tries, attempt+1 + ) + # rr becomes an int when there is a connection!!! + returnValue(rr) + else: + returnValue(rr) else: - d = self.tub.getReference(self.furl) - d.addCallbacks(self._register, self._log_failure) - return d - - def _log_failure(self, reason): - log.err('EngineConnector: engine registration failed:') - log.err(reason) - return reason + raise EngineConnectorError( + 'Could not connect to controller, max_tries (%r) exceeded. ' + 'This usually means that i) the controller was not started, ' + 'or ii) a firewall was blocking the engine from connecting ' + 'to the controller.' % max_tries + ) def _register(self, rr): self.remote_ref = rr @@ -83,7 +130,7 @@ class EngineConnector(object): desired_id = self.engine_service.id d = self.remote_ref.callRemote('register_engine', self.engine_reference, desired_id, os.getpid(), pickle.dumps(self.engine_service.properties,2)) - return d.addCallbacks(self._reference_sent, self._log_failure) + return d.addCallback(self._reference_sent) def _reference_sent(self, registration_dict): self.engine_service.id = registration_dict['id'] diff --git a/IPython/kernel/engineservice.py b/IPython/kernel/engineservice.py index b400320..e5dadb3 100644 --- a/IPython/kernel/engineservice.py +++ b/IPython/kernel/engineservice.py @@ -23,6 +23,10 @@ method that automatically added methods to engines. __docformat__ = "restructuredtext en" +# Tell nose to skip this module. I don't think we need this as nose +# shouldn't ever be run on this! +__test__ = {} + #------------------------------------------------------------------------------- # Copyright (C) 2008 The IPython Development Team # @@ -34,12 +38,9 @@ __docformat__ = "restructuredtext en" # Imports #------------------------------------------------------------------------------- -# Tell nose to skip the testing of this module -__test__ = {} - -import os, sys, copy +import copy +import sys import cPickle as pickle -from new import instancemethod from twisted.application import service from twisted.internet import defer, reactor @@ -47,11 +48,7 @@ from twisted.python import log, failure, components import zope.interface as zi from IPython.kernel.core.interpreter import Interpreter -from IPython.kernel import newserialized, error, util -from IPython.kernel.util import printer -from IPython.kernel.twistedutil import gatherBoth, DeferredList -from IPython.kernel import codeutil - +from IPython.kernel import newserialized, error #------------------------------------------------------------------------------- # Interface specification for the Engine diff --git a/IPython/kernel/error.py b/IPython/kernel/error.py index d91f9e0..23f7cd8 100644 --- a/IPython/kernel/error.py +++ b/IPython/kernel/error.py @@ -4,6 +4,9 @@ __docformat__ = "restructuredtext en" +# Tell nose to skip this module +__test__ = {} + #------------------------------------------------------------------------------- # Copyright (C) 2008 The IPython Development Team # @@ -14,9 +17,9 @@ __docformat__ = "restructuredtext en" #------------------------------------------------------------------------------- # Imports #------------------------------------------------------------------------------- +from twisted.python import failure from IPython.kernel.core import error -from twisted.python import failure #------------------------------------------------------------------------------- # Error classes @@ -124,9 +127,11 @@ class TaskRejectError(KernelError): class CompositeError(KernelError): def __init__(self, message, elist): Exception.__init__(self, *(message, elist)) - self.message = message + # Don't use pack_exception because it will conflict with the .message + # attribute that is being deprecated in 2.6 and beyond. + self.msg = message self.elist = elist - + def _get_engine_str(self, ev): try: ei = ev._ipython_engine_info @@ -134,7 +139,7 @@ class CompositeError(KernelError): return '[Engine Exception]' else: return '[%i:%s]: ' % (ei['engineid'], ei['method']) - + def _get_traceback(self, ev): try: tb = ev._ipython_traceback_text @@ -142,14 +147,14 @@ class CompositeError(KernelError): return 'No traceback available' else: return tb - + def __str__(self): - s = str(self.message) + s = str(self.msg) for et, ev, etb in self.elist: engine_str = self._get_engine_str(ev) s = s + '\n' + engine_str + str(et.__name__) + ': ' + str(ev) return s - + def print_tracebacks(self, excid=None): if excid is None: for (et,ev,etb) in self.elist: diff --git a/IPython/kernel/fcutil.py b/IPython/kernel/fcutil.py index 9f7c730..4de4e89 100644 --- a/IPython/kernel/fcutil.py +++ b/IPython/kernel/fcutil.py @@ -1,27 +1,62 @@ +#!/usr/bin/env python # encoding: utf-8 +""" +Foolscap related utilities. +""" -"""Foolscap related utilities.""" - -__docformat__ = "restructuredtext en" - -#------------------------------------------------------------------------------- -# Copyright (C) 2008 The IPython Development Team +#----------------------------------------------------------------------------- +# Copyright (C) 2008-2009 The IPython Development Team # # Distributed under the terms of the BSD License. The full license is in # the file COPYING, distributed as part of this software. -#------------------------------------------------------------------------------- +#----------------------------------------------------------------------------- -#------------------------------------------------------------------------------- +#----------------------------------------------------------------------------- # Imports -#------------------------------------------------------------------------------- +#----------------------------------------------------------------------------- + +from __future__ import with_statement import os +import tempfile + +from twisted.internet import reactor, defer +from twisted.python import log from foolscap import Tub, UnauthenticatedTub +from IPython.config.loader import Config + +from IPython.kernel.configobjfactory import AdaptedConfiguredObjectFactory + +from IPython.kernel.error import SecurityError + +from IPython.utils.traitlets import Int, Str, Bool, Instance +from IPython.utils.importstring import import_item + +#----------------------------------------------------------------------------- +# Code +#----------------------------------------------------------------------------- + + +# We do this so if a user doesn't have OpenSSL installed, it will try to use +# an UnauthenticatedTub. But, they will still run into problems if they +# try to use encrypted furls. +try: + import OpenSSL +except: + Tub = UnauthenticatedTub + have_crypto = False +else: + have_crypto = True + + +class FURLError(Exception): + pass + + def check_furl_file_security(furl_file, secure): """Remove the old furl_file if changing security modes.""" - if os.path.isfile(furl_file): f = open(furl_file, 'r') oldfurl = f.read().strip() @@ -29,41 +64,210 @@ def check_furl_file_security(furl_file, secure): if (oldfurl.startswith('pb://') and not secure) or (oldfurl.startswith('pbu://') and secure): os.remove(furl_file) + def is_secure(furl): + """Is the given FURL secure or not.""" if is_valid(furl): if furl.startswith("pb://"): return True elif furl.startswith("pbu://"): return False else: - raise ValueError("invalid furl: %s" % furl) + raise FURLError("invalid FURL: %s" % furl) + def is_valid(furl): + """Is the str a valid FURL or not.""" if isinstance(furl, str): if furl.startswith("pb://") or furl.startswith("pbu://"): return True else: return False + def find_furl(furl_or_file): + """Find, validate and return a FURL in a string or file.""" if isinstance(furl_or_file, str): if is_valid(furl_or_file): return furl_or_file if os.path.isfile(furl_or_file): - furl = open(furl_or_file, 'r').read().strip() + with open(furl_or_file, 'r') as f: + furl = f.read().strip() if is_valid(furl): return furl - raise ValueError("not a furl or a file containing a furl: %s" % furl_or_file) + raise FURLError("Not a valid FURL or FURL file: %s" % furl_or_file) -# We do this so if a user doesn't have OpenSSL installed, it will try to use -# an UnauthenticatedTub. But, they will still run into problems if they -# try to use encrypted furls. -try: - import OpenSSL -except: - Tub = UnauthenticatedTub - have_crypto = False -else: - have_crypto = True +def is_valid_furl_or_file(furl_or_file): + """Validate a FURL or a FURL file. + + If ``furl_or_file`` looks like a file, we simply make sure its directory + exists and that it has a ``.furl`` file extension. We don't try to see + if the FURL file exists or to read its contents. This is useful for + cases where auto re-connection is being used. + """ + if isinstance(furl_or_file, str): + if is_valid(furl_or_file): + return True + if isinstance(furl_or_file, (str, unicode)): + path, furl_filename = os.path.split(furl_or_file) + if os.path.isdir(path) and furl_filename.endswith('.furl'): + return True + return False + + +def validate_furl_or_file(furl_or_file): + if not is_valid_furl_or_file(furl_or_file): + raise FURLError('Not a valid FURL or FURL file: %r' % furl_or_file) + + +def get_temp_furlfile(filename): + """Return a temporary FURL file.""" + return tempfile.mktemp(dir=os.path.dirname(filename), + prefix=os.path.basename(filename)) + + +def make_tub(ip, port, secure, cert_file): + """Create a listening tub given an ip, port, and cert_file location. + + Parameters + ---------- + ip : str + The ip address or hostname that the tub should listen on. + Empty means all interfaces. + port : int + The port that the tub should listen on. A value of 0 means + pick a random port + secure: bool + Will the connection be secure (in the Foolscap sense). + cert_file: str + A filename of a file to be used for theSSL certificate. + + Returns + ------- + A tub, listener tuple. + """ + if secure: + if have_crypto: + tub = Tub(certFile=cert_file) + else: + raise SecurityError("OpenSSL/pyOpenSSL is not available, so we " + "can't run in secure mode. Try running without " + "security using 'ipcontroller -xy'.") + else: + tub = UnauthenticatedTub() + + # Set the strport based on the ip and port and start listening + if ip == '': + strport = "tcp:%i" % port + else: + strport = "tcp:%i:interface=%s" % (port, ip) + log.msg("Starting listener with [secure=%r] on: %s" % (secure, strport)) + listener = tub.listenOn(strport) + + return tub, listener + + +class FCServiceFactory(AdaptedConfiguredObjectFactory): + """This class creates a tub with various services running in it. + + The basic idea is that :meth:`create` returns a running :class:`Tub` + instance that has a number of Foolscap references registered in it. + This class is a subclass of :class:`IPython.core.component.Component` + so the IPython configuration and component system are used. + + Attributes + ---------- + interfaces : Config + A Config instance whose values are sub-Config objects having two + keys: furl_file and interface_chain. + + The other attributes are the standard ones for Foolscap. + """ + + ip = Str('', config=True) + port = Int(0, config=True) + secure = Bool(True, config=True) + cert_file = Str('', config=True) + location = Str('', config=True) + reuse_furls = Bool(False, config=True) + interfaces = Instance(klass=Config, kw={}, allow_none=False, config=True) + + def __init__(self, config, adaptee): + super(FCServiceFactory, self).__init__(config, adaptee) + self._check_reuse_furls() + + def _ip_changed(self, name, old, new): + if new == 'localhost' or new == '127.0.0.1': + self.location = '127.0.0.1' + + def _check_reuse_furls(self): + furl_files = [i.furl_file for i in self.interfaces.values()] + for ff in furl_files: + fullfile = self._get_security_file(ff) + if self.reuse_furls: + if self.port==0: + raise FURLError("You are trying to reuse the FURL file " + "for this connection, but the port for this connection " + "is set to 0 (autoselect). To reuse the FURL file " + "you need to specify specific port to listen on." + ) + else: + log.msg("Reusing FURL file: %s" % fullfile) + else: + if os.path.isfile(fullfile): + log.msg("Removing old FURL file: %s" % fullfile) + os.remove(fullfile) + + def _get_security_file(self, filename): + return os.path.join(self.config.Global.security_dir, filename) + + def create(self): + """Create and return the Foolscap tub with everything running.""" + + self.tub, self.listener = make_tub( + self.ip, self.port, self.secure, + self._get_security_file(self.cert_file) + ) + # log.msg("Interfaces to register [%r]: %r" % \ + # (self.__class__, self.interfaces)) + if not self.secure: + log.msg("WARNING: running with no security: %s" % \ + self.__class__.__name__) + reactor.callWhenRunning(self.set_location_and_register) + return self.tub + + def set_location_and_register(self): + """Set the location for the tub and return a deferred.""" + + if self.location == '': + d = self.tub.setLocationAutomatically() + else: + d = defer.maybeDeferred(self.tub.setLocation, + "%s:%i" % (self.location, self.listener.getPortnum())) + self.adapt_to_interfaces(d) + + def adapt_to_interfaces(self, d): + """Run through the interfaces, adapt and register.""" + + for ifname, ifconfig in self.interfaces.iteritems(): + ff = self._get_security_file(ifconfig.furl_file) + log.msg("Adapting [%s] to interface: %s" % \ + (self.adaptee.__class__.__name__, ifname)) + log.msg("Saving FURL for interface [%s] to file: %s" % (ifname, ff)) + check_furl_file_security(ff, self.secure) + adaptee = self.adaptee + for i in ifconfig.interface_chain: + adaptee = import_item(i)(adaptee) + d.addCallback(self.register, adaptee, furl_file=ff) + + def register(self, empty, ref, furl_file): + """Register the reference with the FURL file. + + The FURL file is created and then moved to make sure that when the + file appears, the buffer has been flushed and the file closed. + """ + temp_furl_file = get_temp_furlfile(furl_file) + self.tub.registerReference(ref, furlFile=temp_furl_file) + os.rename(temp_furl_file, furl_file) diff --git a/IPython/kernel/ipclusterapp.py b/IPython/kernel/ipclusterapp.py new file mode 100644 index 0000000..fe74e83 --- /dev/null +++ b/IPython/kernel/ipclusterapp.py @@ -0,0 +1,471 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +The ipcluster application. +""" + +#----------------------------------------------------------------------------- +# Copyright (C) 2008-2009 The IPython Development Team +# +# Distributed under the terms of the BSD License. The full license is in +# the file COPYING, distributed as part of this software. +#----------------------------------------------------------------------------- + +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- + +import logging +import os +import signal +import sys + +if os.name=='posix': + from twisted.scripts._twistd_unix import daemonize + +from IPython.core import release +from IPython.external import argparse +from IPython.config.loader import ArgParseConfigLoader, NoConfigDefault +from IPython.utils.importstring import import_item + +from IPython.kernel.clusterdir import ( + ApplicationWithClusterDir, ClusterDirError, PIDFileError +) + +from twisted.internet import reactor, defer +from twisted.python import log, failure + + +#----------------------------------------------------------------------------- +# The ipcluster application +#----------------------------------------------------------------------------- + + +# Exit codes for ipcluster + +# This will be the exit code if the ipcluster appears to be running because +# a .pid file exists +ALREADY_STARTED = 10 + +# This will be the exit code if ipcluster stop is run, but there is not .pid +# file to be found. +ALREADY_STOPPED = 11 + + +class IPClusterCLLoader(ArgParseConfigLoader): + + def _add_arguments(self): + # This has all the common options that all subcommands use + parent_parser1 = argparse.ArgumentParser(add_help=False) + parent_parser1.add_argument('--ipython-dir', + dest='Global.ipython_dir',type=unicode, + help='Set to override default location of Global.ipython_dir.', + default=NoConfigDefault, + metavar='Global.ipython_dir') + parent_parser1.add_argument('--log-level', + dest="Global.log_level",type=int, + help='Set the log level (0,10,20,30,40,50). Default is 30.', + default=NoConfigDefault, + metavar='Global.log_level') + + # This has all the common options that other subcommands use + parent_parser2 = argparse.ArgumentParser(add_help=False) + parent_parser2.add_argument('-p','--profile', + dest='Global.profile',type=unicode, + help='The string name of the profile to be used. This determines ' + 'the name of the cluster dir as: cluster_. The default profile ' + 'is named "default". The cluster directory is resolve this way ' + 'if the --cluster-dir option is not used.', + default=NoConfigDefault, + metavar='Global.profile') + parent_parser2.add_argument('--cluster-dir', + dest='Global.cluster_dir',type=unicode, + help='Set the cluster dir. This overrides the logic used by the ' + '--profile option.', + default=NoConfigDefault, + metavar='Global.cluster_dir'), + parent_parser2.add_argument('--work-dir', + dest='Global.work_dir',type=unicode, + help='Set the working dir for the process.', + default=NoConfigDefault, + metavar='Global.work_dir') + parent_parser2.add_argument('--log-to-file', + action='store_true', dest='Global.log_to_file', + default=NoConfigDefault, + help='Log to a file in the log directory (default is stdout)' + ) + + subparsers = self.parser.add_subparsers( + dest='Global.subcommand', + title='ipcluster subcommands', + description='ipcluster has a variety of subcommands. ' + 'The general way of running ipcluster is "ipcluster ' + ' [options]""', + help='For more help, type "ipcluster -h"') + + parser_list = subparsers.add_parser( + 'list', + help='List all clusters in cwd and ipython_dir.', + parents=[parent_parser1] + ) + + parser_create = subparsers.add_parser( + 'create', + help='Create a new cluster directory.', + parents=[parent_parser1, parent_parser2] + ) + parser_create.add_argument( + '--reset-config', + dest='Global.reset_config', action='store_true', + default=NoConfigDefault, + help='Recopy the default config files to the cluster directory. ' + 'You will loose any modifications you have made to these files.' + ) + + parser_start = subparsers.add_parser( + 'start', + help='Start a cluster.', + parents=[parent_parser1, parent_parser2] + ) + parser_start.add_argument( + '-n', '--number', + type=int, dest='Global.n', + default=NoConfigDefault, + help='The number of engines to start.', + metavar='Global.n' + ) + parser_start.add_argument('--clean-logs', + dest='Global.clean_logs', action='store_true', + help='Delete old log flies before starting.', + default=NoConfigDefault + ) + parser_start.add_argument('--no-clean-logs', + dest='Global.clean_logs', action='store_false', + help="Don't delete old log flies before starting.", + default=NoConfigDefault + ) + parser_start.add_argument('--daemon', + dest='Global.daemonize', action='store_true', + help='Daemonize the ipcluster program. This implies --log-to-file', + default=NoConfigDefault + ) + parser_start.add_argument('--no-daemon', + dest='Global.daemonize', action='store_false', + help="Dont't daemonize the ipcluster program.", + default=NoConfigDefault + ) + + parser_start = subparsers.add_parser( + 'stop', + help='Stop a cluster.', + parents=[parent_parser1, parent_parser2] + ) + parser_start.add_argument('--signal', + dest='Global.signal', type=int, + help="The signal number to use in stopping the cluster (default=2).", + metavar="Global.signal", + default=NoConfigDefault + ) + + +default_config_file_name = u'ipcluster_config.py' + + +_description = """Start an IPython cluster for parallel computing.\n\n + +An IPython cluster consists of 1 controller and 1 or more engines. +This command automates the startup of these processes using a wide +range of startup methods (SSH, local processes, PBS, mpiexec, +Windows HPC Server 2008). To start a cluster with 4 engines on your +local host simply do "ipcluster start -n 4". For more complex usage +you will typically do "ipcluster create -p mycluster", then edit +configuration files, followed by "ipcluster start -p mycluster -n 4". +""" + + +class IPClusterApp(ApplicationWithClusterDir): + + name = u'ipcluster' + description = _description + config_file_name = default_config_file_name + default_log_level = logging.INFO + auto_create_cluster_dir = False + + def create_default_config(self): + super(IPClusterApp, self).create_default_config() + self.default_config.Global.controller_launcher = \ + 'IPython.kernel.launcher.LocalControllerLauncher' + self.default_config.Global.engine_launcher = \ + 'IPython.kernel.launcher.LocalEngineSetLauncher' + self.default_config.Global.n = 2 + self.default_config.Global.reset_config = False + self.default_config.Global.clean_logs = True + self.default_config.Global.signal = 2 + self.default_config.Global.daemonize = False + + def create_command_line_config(self): + """Create and return a command line config loader.""" + return IPClusterCLLoader( + description=self.description, + version=release.version + ) + + def find_resources(self): + subcommand = self.command_line_config.Global.subcommand + if subcommand=='list': + self.list_cluster_dirs() + # Exit immediately because there is nothing left to do. + self.exit() + elif subcommand=='create': + self.auto_create_cluster_dir = True + super(IPClusterApp, self).find_resources() + elif subcommand=='start' or subcommand=='stop': + self.auto_create_cluster_dir = True + try: + super(IPClusterApp, self).find_resources() + except ClusterDirError: + raise ClusterDirError( + "Could not find a cluster directory. A cluster dir must " + "be created before running 'ipcluster start'. Do " + "'ipcluster create -h' or 'ipcluster list -h' for more " + "information about creating and listing cluster dirs." + ) + + def list_cluster_dirs(self): + # Find the search paths + cluster_dir_paths = os.environ.get('IPCLUSTER_DIR_PATH','') + if cluster_dir_paths: + cluster_dir_paths = cluster_dir_paths.split(':') + else: + cluster_dir_paths = [] + try: + ipython_dir = self.command_line_config.Global.ipython_dir + except AttributeError: + ipython_dir = self.default_config.Global.ipython_dir + paths = [os.getcwd(), ipython_dir] + \ + cluster_dir_paths + paths = list(set(paths)) + + self.log.info('Searching for cluster dirs in paths: %r' % paths) + for path in paths: + files = os.listdir(path) + for f in files: + full_path = os.path.join(path, f) + if os.path.isdir(full_path) and f.startswith('cluster_'): + profile = full_path.split('_')[-1] + start_cmd = 'ipcluster start -p %s -n 4' % profile + print start_cmd + " ==> " + full_path + + def pre_construct(self): + # IPClusterApp.pre_construct() is where we cd to the working directory. + super(IPClusterApp, self).pre_construct() + config = self.master_config + try: + daemon = config.Global.daemonize + if daemon: + config.Global.log_to_file = True + except AttributeError: + pass + + def construct(self): + config = self.master_config + subcmd = config.Global.subcommand + reset = config.Global.reset_config + if subcmd == 'list': + return + if subcmd == 'create': + self.log.info('Copying default config files to cluster directory ' + '[overwrite=%r]' % (reset,)) + self.cluster_dir_obj.copy_all_config_files(overwrite=reset) + if subcmd =='start': + self.cluster_dir_obj.copy_all_config_files(overwrite=False) + self.start_logging() + reactor.callWhenRunning(self.start_launchers) + + def start_launchers(self): + config = self.master_config + + # Create the launchers. In both bases, we set the work_dir of + # the launcher to the cluster_dir. This is where the launcher's + # subprocesses will be launched. It is not where the controller + # and engine will be launched. + el_class = import_item(config.Global.engine_launcher) + self.engine_launcher = el_class( + work_dir=self.cluster_dir, config=config + ) + cl_class = import_item(config.Global.controller_launcher) + self.controller_launcher = cl_class( + work_dir=self.cluster_dir, config=config + ) + + # Setup signals + signal.signal(signal.SIGINT, self.sigint_handler) + + # Setup the observing of stopping. If the controller dies, shut + # everything down as that will be completely fatal for the engines. + d1 = self.controller_launcher.observe_stop() + d1.addCallback(self.stop_launchers) + # But, we don't monitor the stopping of engines. An engine dying + # is just fine and in principle a user could start a new engine. + # Also, if we did monitor engine stopping, it is difficult to + # know what to do when only some engines die. Currently, the + # observing of engine stopping is inconsistent. Some launchers + # might trigger on a single engine stopping, other wait until + # all stop. TODO: think more about how to handle this. + + # Start the controller and engines + self._stopping = False # Make sure stop_launchers is not called 2x. + d = self.start_controller() + d.addCallback(self.start_engines) + d.addCallback(self.startup_message) + # If the controller or engines fail to start, stop everything + d.addErrback(self.stop_launchers) + return d + + def startup_message(self, r=None): + log.msg("IPython cluster: started") + return r + + def start_controller(self, r=None): + # log.msg("In start_controller") + config = self.master_config + d = self.controller_launcher.start( + cluster_dir=config.Global.cluster_dir + ) + return d + + def start_engines(self, r=None): + # log.msg("In start_engines") + config = self.master_config + d = self.engine_launcher.start( + config.Global.n, + cluster_dir=config.Global.cluster_dir + ) + return d + + def stop_controller(self, r=None): + # log.msg("In stop_controller") + if self.controller_launcher.running: + d = self.controller_launcher.stop() + d.addErrback(self.log_err) + return d + else: + return defer.succeed(None) + + def stop_engines(self, r=None): + # log.msg("In stop_engines") + if self.engine_launcher.running: + d = self.engine_launcher.stop() + d.addErrback(self.log_err) + return d + else: + return defer.succeed(None) + + def log_err(self, f): + log.msg(f.getTraceback()) + return None + + def stop_launchers(self, r=None): + if not self._stopping: + self._stopping = True + if isinstance(r, failure.Failure): + log.msg('Unexpected error in ipcluster:') + log.msg(r.getTraceback()) + log.msg("IPython cluster: stopping") + d= self.stop_engines() + d2 = self.stop_controller() + # Wait a few seconds to let things shut down. + reactor.callLater(4.0, reactor.stop) + + def sigint_handler(self, signum, frame): + self.stop_launchers() + + def start_logging(self): + # Remove old log files of the controller and engine + if self.master_config.Global.clean_logs: + log_dir = self.master_config.Global.log_dir + for f in os.listdir(log_dir): + if f.startswith('ipengine' + '-'): + if f.endswith('.log') or f.endswith('.out') or f.endswith('.err'): + os.remove(os.path.join(log_dir, f)) + if f.startswith('ipcontroller' + '-'): + if f.endswith('.log') or f.endswith('.out') or f.endswith('.err'): + os.remove(os.path.join(log_dir, f)) + # This will remote old log files for ipcluster itself + super(IPClusterApp, self).start_logging() + + def start_app(self): + """Start the application, depending on what subcommand is used.""" + subcmd = self.master_config.Global.subcommand + if subcmd=='create' or subcmd=='list': + return + elif subcmd=='start': + self.start_app_start() + elif subcmd=='stop': + self.start_app_stop() + + def start_app_start(self): + """Start the app for the start subcommand.""" + config = self.master_config + # First see if the cluster is already running + try: + pid = self.get_pid_from_file() + except PIDFileError: + pass + else: + self.log.critical( + 'Cluster is already running with [pid=%s]. ' + 'use "ipcluster stop" to stop the cluster.' % pid + ) + # Here I exit with a unusual exit status that other processes + # can watch for to learn how I existed. + self.exit(ALREADY_STARTED) + + # Now log and daemonize + self.log.info( + 'Starting ipcluster with [daemon=%r]' % config.Global.daemonize + ) + # TODO: Get daemonize working on Windows or as a Windows Server. + if config.Global.daemonize: + if os.name=='posix': + daemonize() + + # Now write the new pid file AFTER our new forked pid is active. + self.write_pid_file() + reactor.addSystemEventTrigger('during','shutdown', self.remove_pid_file) + reactor.run() + + def start_app_stop(self): + """Start the app for the stop subcommand.""" + config = self.master_config + try: + pid = self.get_pid_from_file() + except PIDFileError: + self.log.critical( + 'Problem reading pid file, cluster is probably not running.' + ) + # Here I exit with a unusual exit status that other processes + # can watch for to learn how I existed. + self.exit(ALREADY_STOPPED) + else: + if os.name=='posix': + sig = config.Global.signal + self.log.info( + "Stopping cluster [pid=%r] with [signal=%r]" % (pid, sig) + ) + os.kill(pid, sig) + elif os.name=='nt': + # As of right now, we don't support daemonize on Windows, so + # stop will not do anything. Minimally, it should clean up the + # old .pid files. + self.remove_pid_file() + +def launch_new_instance(): + """Create and run the IPython cluster.""" + app = IPClusterApp() + app.start() + + +if __name__ == '__main__': + launch_new_instance() + diff --git a/IPython/kernel/ipcontrollerapp.py b/IPython/kernel/ipcontrollerapp.py new file mode 100644 index 0000000..70ce723 --- /dev/null +++ b/IPython/kernel/ipcontrollerapp.py @@ -0,0 +1,275 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +The IPython controller application. +""" + +#----------------------------------------------------------------------------- +# Copyright (C) 2008-2009 The IPython Development Team +# +# Distributed under the terms of the BSD License. The full license is in +# the file COPYING, distributed as part of this software. +#----------------------------------------------------------------------------- + +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- + +from __future__ import with_statement + +import copy +import os +import sys + +from twisted.application import service +from twisted.internet import reactor +from twisted.python import log + +from IPython.config.loader import Config, NoConfigDefault + +from IPython.kernel.clusterdir import ( + ApplicationWithClusterDir, + AppWithClusterDirArgParseConfigLoader +) + +from IPython.core import release + +from IPython.utils.traitlets import Str, Instance, Unicode + +from IPython.kernel import controllerservice + +from IPython.kernel.fcutil import FCServiceFactory + +#----------------------------------------------------------------------------- +# Default interfaces +#----------------------------------------------------------------------------- + + +# The default client interfaces for FCClientServiceFactory.interfaces +default_client_interfaces = Config() +default_client_interfaces.Task.interface_chain = [ + 'IPython.kernel.task.ITaskController', + 'IPython.kernel.taskfc.IFCTaskController' +] + +default_client_interfaces.Task.furl_file = 'ipcontroller-tc.furl' + +default_client_interfaces.MultiEngine.interface_chain = [ + 'IPython.kernel.multiengine.IMultiEngine', + 'IPython.kernel.multienginefc.IFCSynchronousMultiEngine' +] + +default_client_interfaces.MultiEngine.furl_file = u'ipcontroller-mec.furl' + +# Make this a dict we can pass to Config.__init__ for the default +default_client_interfaces = dict(copy.deepcopy(default_client_interfaces.items())) + + + +# The default engine interfaces for FCEngineServiceFactory.interfaces +default_engine_interfaces = Config() +default_engine_interfaces.Default.interface_chain = [ + 'IPython.kernel.enginefc.IFCControllerBase' +] + +default_engine_interfaces.Default.furl_file = u'ipcontroller-engine.furl' + +# Make this a dict we can pass to Config.__init__ for the default +default_engine_interfaces = dict(copy.deepcopy(default_engine_interfaces.items())) + + +#----------------------------------------------------------------------------- +# Service factories +#----------------------------------------------------------------------------- + + +class FCClientServiceFactory(FCServiceFactory): + """A Foolscap implementation of the client services.""" + + cert_file = Unicode(u'ipcontroller-client.pem', config=True) + interfaces = Instance(klass=Config, kw=default_client_interfaces, + allow_none=False, config=True) + + +class FCEngineServiceFactory(FCServiceFactory): + """A Foolscap implementation of the engine services.""" + + cert_file = Unicode(u'ipcontroller-engine.pem', config=True) + interfaces = Instance(klass=dict, kw=default_engine_interfaces, + allow_none=False, config=True) + + +#----------------------------------------------------------------------------- +# The main application +#----------------------------------------------------------------------------- + + +cl_args = ( + # Client config + (('--client-ip',), dict( + type=str, dest='FCClientServiceFactory.ip', default=NoConfigDefault, + help='The IP address or hostname the controller will listen on for ' + 'client connections.', + metavar='FCClientServiceFactory.ip') + ), + (('--client-port',), dict( + type=int, dest='FCClientServiceFactory.port', default=NoConfigDefault, + help='The port the controller will listen on for client connections. ' + 'The default is to use 0, which will autoselect an open port.', + metavar='FCClientServiceFactory.port') + ), + (('--client-location',), dict( + type=str, dest='FCClientServiceFactory.location', default=NoConfigDefault, + help='The hostname or IP that clients should connect to. This does ' + 'not control which interface the controller listens on. Instead, this ' + 'determines the hostname/IP that is listed in the FURL, which is how ' + 'clients know where to connect. Useful if the controller is listening ' + 'on multiple interfaces.', + metavar='FCClientServiceFactory.location') + ), + # Engine config + (('--engine-ip',), dict( + type=str, dest='FCEngineServiceFactory.ip', default=NoConfigDefault, + help='The IP address or hostname the controller will listen on for ' + 'engine connections.', + metavar='FCEngineServiceFactory.ip') + ), + (('--engine-port',), dict( + type=int, dest='FCEngineServiceFactory.port', default=NoConfigDefault, + help='The port the controller will listen on for engine connections. ' + 'The default is to use 0, which will autoselect an open port.', + metavar='FCEngineServiceFactory.port') + ), + (('--engine-location',), dict( + type=str, dest='FCEngineServiceFactory.location', default=NoConfigDefault, + help='The hostname or IP that engines should connect to. This does ' + 'not control which interface the controller listens on. Instead, this ' + 'determines the hostname/IP that is listed in the FURL, which is how ' + 'engines know where to connect. Useful if the controller is listening ' + 'on multiple interfaces.', + metavar='FCEngineServiceFactory.location') + ), + # Global config + (('--log-to-file',), dict( + action='store_true', dest='Global.log_to_file', default=NoConfigDefault, + help='Log to a file in the log directory (default is stdout)') + ), + (('-r','--reuse-furls'), dict( + action='store_true', dest='Global.reuse_furls', default=NoConfigDefault, + help='Try to reuse all FURL files. If this is not set all FURL files ' + 'are deleted before the controller starts. This must be set if ' + 'specific ports are specified by --engine-port or --client-port.') + ), + (('--no-secure',), dict( + action='store_false', dest='Global.secure', default=NoConfigDefault, + help='Turn off SSL encryption for all connections.') + ), + (('--secure',), dict( + action='store_true', dest='Global.secure', default=NoConfigDefault, + help='Turn off SSL encryption for all connections.') + ) +) + + +class IPControllerAppCLConfigLoader(AppWithClusterDirArgParseConfigLoader): + + arguments = cl_args + + +_description = """Start the IPython controller for parallel computing. + +The IPython controller provides a gateway between the IPython engines and +clients. The controller needs to be started before the engines and can be +configured using command line options or using a cluster directory. Cluster +directories contain config, log and security files and are usually located in +your .ipython directory and named as "cluster_". See the --profile +and --cluster-dir options for details. +""" + +default_config_file_name = u'ipcontroller_config.py' + + +class IPControllerApp(ApplicationWithClusterDir): + + name = u'ipcontroller' + description = _description + config_file_name = default_config_file_name + auto_create_cluster_dir = True + + def create_default_config(self): + super(IPControllerApp, self).create_default_config() + self.default_config.Global.reuse_furls = False + self.default_config.Global.secure = True + self.default_config.Global.import_statements = [] + self.default_config.Global.clean_logs = True + + def create_command_line_config(self): + """Create and return a command line config loader.""" + return IPControllerAppCLConfigLoader( + description=self.description, + version=release.version + ) + + def post_load_command_line_config(self): + # Now setup reuse_furls + c = self.command_line_config + if hasattr(c.Global, 'reuse_furls'): + c.FCClientServiceFactory.reuse_furls = c.Global.reuse_furls + c.FCEngineServiceFactory.reuse_furls = c.Global.reuse_furls + del c.Global.reuse_furls + if hasattr(c.Global, 'secure'): + c.FCClientServiceFactory.secure = c.Global.secure + c.FCEngineServiceFactory.secure = c.Global.secure + del c.Global.secure + + def construct(self): + # This is the working dir by now. + sys.path.insert(0, '') + + self.start_logging() + self.import_statements() + + # Create the service hierarchy + self.main_service = service.MultiService() + # The controller service + controller_service = controllerservice.ControllerService() + controller_service.setServiceParent(self.main_service) + # The client tub and all its refereceables + csfactory = FCClientServiceFactory(self.master_config, controller_service) + client_service = csfactory.create() + client_service.setServiceParent(self.main_service) + # The engine tub + esfactory = FCEngineServiceFactory(self.master_config, controller_service) + engine_service = esfactory.create() + engine_service.setServiceParent(self.main_service) + + def import_statements(self): + statements = self.master_config.Global.import_statements + for s in statements: + try: + log.msg("Executing statement: '%s'" % s) + exec s in globals(), locals() + except: + log.msg("Error running statement: %s" % s) + + def start_app(self): + # Start the controller service. + self.main_service.startService() + # Write the .pid file overwriting old ones. This allow multiple + # controllers to clober each other. But Windows is not cleaning + # these up properly. + self.write_pid_file(overwrite=True) + # Add a trigger to delete the .pid file upon shutting down. + reactor.addSystemEventTrigger('during','shutdown', self.remove_pid_file) + reactor.run() + + +def launch_new_instance(): + """Create and run the IPython controller""" + app = IPControllerApp() + app.start() + + +if __name__ == '__main__': + launch_new_instance() + diff --git a/IPython/kernel/ipengineapp.py b/IPython/kernel/ipengineapp.py new file mode 100644 index 0000000..9edc72b --- /dev/null +++ b/IPython/kernel/ipengineapp.py @@ -0,0 +1,248 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +The IPython controller application +""" + +#----------------------------------------------------------------------------- +# Copyright (C) 2008-2009 The IPython Development Team +# +# Distributed under the terms of the BSD License. The full license is in +# the file COPYING, distributed as part of this software. +#----------------------------------------------------------------------------- + +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- + +import os +import sys + +from twisted.application import service +from twisted.internet import reactor +from twisted.python import log + +from IPython.config.loader import NoConfigDefault + +from IPython.kernel.clusterdir import ( + ApplicationWithClusterDir, + AppWithClusterDirArgParseConfigLoader +) +from IPython.core import release + +from IPython.utils.importstring import import_item + +from IPython.kernel.engineservice import EngineService +from IPython.kernel.fcutil import Tub +from IPython.kernel.engineconnector import EngineConnector + +#----------------------------------------------------------------------------- +# The main application +#----------------------------------------------------------------------------- + + +cl_args = ( + # Controller config + (('--furl-file',), dict( + type=unicode, dest='Global.furl_file', default=NoConfigDefault, + help='The full location of the file containing the FURL of the ' + 'controller. If this is not given, the FURL file must be in the ' + 'security directory of the cluster directory. This location is ' + 'resolved using the --profile and --app-dir options.', + metavar='Global.furl_file') + ), + # MPI + (('--mpi',), dict( + type=str, dest='MPI.use', default=NoConfigDefault, + help='How to enable MPI (mpi4py, pytrilinos, or empty string to disable).', + metavar='MPI.use') + ), + # Global config + (('--log-to-file',), dict( + action='store_true', dest='Global.log_to_file', default=NoConfigDefault, + help='Log to a file in the log directory (default is stdout)') + ) +) + + +class IPEngineAppCLConfigLoader(AppWithClusterDirArgParseConfigLoader): + + arguments = cl_args + + +mpi4py_init = """from mpi4py import MPI as mpi +mpi.size = mpi.COMM_WORLD.Get_size() +mpi.rank = mpi.COMM_WORLD.Get_rank() +""" + +pytrilinos_init = """from PyTrilinos import Epetra +class SimpleStruct: +pass +mpi = SimpleStruct() +mpi.rank = 0 +mpi.size = 0 +""" + + +default_config_file_name = u'ipengine_config.py' + + +_description = """Start an IPython engine for parallel computing.\n\n + +IPython engines run in parallel and perform computations on behalf of a client +and controller. A controller needs to be started before the engines. The +engine can be configured using command line options or using a cluster +directory. Cluster directories contain config, log and security files and are +usually located in your .ipython directory and named as "cluster_". +See the --profile and --cluster-dir options for details. +""" + + +class IPEngineApp(ApplicationWithClusterDir): + + name = u'ipengine' + description = _description + config_file_name = default_config_file_name + auto_create_cluster_dir = True + + def create_default_config(self): + super(IPEngineApp, self).create_default_config() + + # The engine should not clean logs as we don't want to remove the + # active log files of other running engines. + self.default_config.Global.clean_logs = False + + # Global config attributes + self.default_config.Global.exec_lines = [] + self.default_config.Global.shell_class = 'IPython.kernel.core.interpreter.Interpreter' + + # Configuration related to the controller + # This must match the filename (path not included) that the controller + # used for the FURL file. + self.default_config.Global.furl_file_name = u'ipcontroller-engine.furl' + # If given, this is the actual location of the controller's FURL file. + # If not, this is computed using the profile, app_dir and furl_file_name + self.default_config.Global.furl_file = u'' + + # The max number of connection attemps and the initial delay between + # those attemps. + self.default_config.Global.connect_delay = 0.1 + self.default_config.Global.connect_max_tries = 15 + + # MPI related config attributes + self.default_config.MPI.use = '' + self.default_config.MPI.mpi4py = mpi4py_init + self.default_config.MPI.pytrilinos = pytrilinos_init + + def create_command_line_config(self): + """Create and return a command line config loader.""" + return IPEngineAppCLConfigLoader( + description=self.description, + version=release.version + ) + + def post_load_command_line_config(self): + pass + + def pre_construct(self): + super(IPEngineApp, self).pre_construct() + self.find_cont_furl_file() + + def find_cont_furl_file(self): + """Set the furl file. + + Here we don't try to actually see if it exists for is valid as that + is hadled by the connection logic. + """ + config = self.master_config + # Find the actual controller FURL file + if not config.Global.furl_file: + try_this = os.path.join( + config.Global.cluster_dir, + config.Global.security_dir, + config.Global.furl_file_name + ) + config.Global.furl_file = try_this + + def construct(self): + # This is the working dir by now. + sys.path.insert(0, '') + + self.start_mpi() + self.start_logging() + + # Create the underlying shell class and EngineService + shell_class = import_item(self.master_config.Global.shell_class) + self.engine_service = EngineService(shell_class, mpi=mpi) + + self.exec_lines() + + # Create the service hierarchy + self.main_service = service.MultiService() + self.engine_service.setServiceParent(self.main_service) + self.tub_service = Tub() + self.tub_service.setServiceParent(self.main_service) + # This needs to be called before the connection is initiated + self.main_service.startService() + + # This initiates the connection to the controller and calls + # register_engine to tell the controller we are ready to do work + self.engine_connector = EngineConnector(self.tub_service) + + log.msg("Using furl file: %s" % self.master_config.Global.furl_file) + + reactor.callWhenRunning(self.call_connect) + + def call_connect(self): + d = self.engine_connector.connect_to_controller( + self.engine_service, + self.master_config.Global.furl_file, + self.master_config.Global.connect_delay, + self.master_config.Global.connect_max_tries + ) + + def handle_error(f): + log.msg('Error connecting to controller. This usually means that ' + 'i) the controller was not started, ii) a firewall was blocking ' + 'the engine from connecting to the controller or iii) the engine ' + ' was not pointed at the right FURL file:') + log.msg(f.getErrorMessage()) + reactor.callLater(0.1, reactor.stop) + + d.addErrback(handle_error) + + def start_mpi(self): + global mpi + mpikey = self.master_config.MPI.use + mpi_import_statement = self.master_config.MPI.get(mpikey, None) + if mpi_import_statement is not None: + try: + self.log.info("Initializing MPI:") + self.log.info(mpi_import_statement) + exec mpi_import_statement in globals() + except: + mpi = None + else: + mpi = None + + def exec_lines(self): + for line in self.master_config.Global.exec_lines: + try: + log.msg("Executing statement: '%s'" % line) + self.engine_service.execute(line) + except: + log.msg("Error executing statement: %s" % line) + + def start_app(self): + reactor.run() + + +def launch_new_instance(): + """Create and run the IPython controller""" + app = IPEngineApp() + app.start() + + +if __name__ == '__main__': + launch_new_instance() + diff --git a/IPython/kernel/launcher.py b/IPython/kernel/launcher.py new file mode 100644 index 0000000..2f41211 --- /dev/null +++ b/IPython/kernel/launcher.py @@ -0,0 +1,869 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +Facilities for launching IPython processes asynchronously. +""" + +#----------------------------------------------------------------------------- +# Copyright (C) 2008-2009 The IPython Development Team +# +# Distributed under the terms of the BSD License. The full license is in +# the file COPYING, distributed as part of this software. +#----------------------------------------------------------------------------- + +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- + +import os +import re +import sys + +from IPython.core.component import Component +from IPython.external import Itpl +from IPython.utils.traitlets import Str, Int, List, Unicode, Enum +from IPython.utils.platutils import find_cmd +from IPython.kernel.twistedutil import gatherBoth, make_deferred, sleep_deferred +from IPython.kernel.winhpcjob import ( + WinHPCJob, WinHPCTask, + IPControllerTask, IPEngineTask, + IPControllerJob, IPEngineSetJob +) + +from twisted.internet import reactor, defer +from twisted.internet.defer import inlineCallbacks +from twisted.internet.protocol import ProcessProtocol +from twisted.internet.utils import getProcessOutput +from twisted.internet.error import ProcessDone, ProcessTerminated +from twisted.python import log +from twisted.python.failure import Failure + +#----------------------------------------------------------------------------- +# Utilities +#----------------------------------------------------------------------------- + + +def find_controller_cmd(): + """Find the command line ipcontroller program in a cross platform way.""" + if sys.platform == 'win32': + # This logic is needed because the ipcontroller script doesn't + # always get installed in the same way or in the same location. + from IPython.kernel import ipcontrollerapp + script_location = ipcontrollerapp.__file__.replace('.pyc', '.py') + # The -u option here turns on unbuffered output, which is required + # on Win32 to prevent wierd conflict and problems with Twisted. + # Also, use sys.executable to make sure we are picking up the + # right python exe. + cmd = [sys.executable, '-u', script_location] + else: + # ipcontroller has to be on the PATH in this case. + cmd = ['ipcontroller'] + return cmd + + +def find_engine_cmd(): + """Find the command line ipengine program in a cross platform way.""" + if sys.platform == 'win32': + # This logic is needed because the ipengine script doesn't + # always get installed in the same way or in the same location. + from IPython.kernel import ipengineapp + script_location = ipengineapp.__file__.replace('.pyc', '.py') + # The -u option here turns on unbuffered output, which is required + # on Win32 to prevent wierd conflict and problems with Twisted. + # Also, use sys.executable to make sure we are picking up the + # right python exe. + cmd = [sys.executable, '-u', script_location] + else: + # ipcontroller has to be on the PATH in this case. + cmd = ['ipengine'] + return cmd + + +#----------------------------------------------------------------------------- +# Base launchers and errors +#----------------------------------------------------------------------------- + + +class LauncherError(Exception): + pass + + +class ProcessStateError(LauncherError): + pass + + +class UnknownStatus(LauncherError): + pass + + +class BaseLauncher(Component): + """An asbtraction for starting, stopping and signaling a process.""" + + # In all of the launchers, the work_dir is where child processes will be + # run. This will usually be the cluster_dir, but may not be. any work_dir + # passed into the __init__ method will override the config value. + # This should not be used to set the work_dir for the actual engine + # and controller. Instead, use their own config files or the + # controller_args, engine_args attributes of the launchers to add + # the --work-dir option. + work_dir = Unicode(u'') + + def __init__(self, work_dir, parent=None, name=None, config=None): + super(BaseLauncher, self).__init__(parent, name, config) + self.work_dir = work_dir + self.state = 'before' # can be before, running, after + self.stop_deferreds = [] + self.start_data = None + self.stop_data = None + + @property + def args(self): + """A list of cmd and args that will be used to start the process. + + This is what is passed to :func:`spawnProcess` and the first element + will be the process name. + """ + return self.find_args() + + def find_args(self): + """The ``.args`` property calls this to find the args list. + + Subcommand should implement this to construct the cmd and args. + """ + raise NotImplementedError('find_args must be implemented in a subclass') + + @property + def arg_str(self): + """The string form of the program arguments.""" + return ' '.join(self.args) + + @property + def running(self): + """Am I running.""" + if self.state == 'running': + return True + else: + return False + + def start(self): + """Start the process. + + This must return a deferred that fires with information about the + process starting (like a pid, job id, etc.). + """ + return defer.fail( + Failure(NotImplementedError( + 'start must be implemented in a subclass') + ) + ) + + def stop(self): + """Stop the process and notify observers of stopping. + + This must return a deferred that fires with information about the + processing stopping, like errors that occur while the process is + attempting to be shut down. This deferred won't fire when the process + actually stops. To observe the actual process stopping, see + :func:`observe_stop`. + """ + return defer.fail( + Failure(NotImplementedError( + 'stop must be implemented in a subclass') + ) + ) + + def observe_stop(self): + """Get a deferred that will fire when the process stops. + + The deferred will fire with data that contains information about + the exit status of the process. + """ + if self.state=='after': + return defer.succeed(self.stop_data) + else: + d = defer.Deferred() + self.stop_deferreds.append(d) + return d + + def notify_start(self, data): + """Call this to trigger startup actions. + + This logs the process startup and sets the state to 'running'. It is + a pass-through so it can be used as a callback. + """ + + log.msg('Process %r started: %r' % (self.args[0], data)) + self.start_data = data + self.state = 'running' + return data + + def notify_stop(self, data): + """Call this to trigger process stop actions. + + This logs the process stopping and sets the state to 'after'. Call + this to trigger all the deferreds from :func:`observe_stop`.""" + + log.msg('Process %r stopped: %r' % (self.args[0], data)) + self.stop_data = data + self.state = 'after' + for i in range(len(self.stop_deferreds)): + d = self.stop_deferreds.pop() + d.callback(data) + return data + + def signal(self, sig): + """Signal the process. + + Return a semi-meaningless deferred after signaling the process. + + Parameters + ---------- + sig : str or int + 'KILL', 'INT', etc., or any signal number + """ + return defer.fail( + Failure(NotImplementedError( + 'signal must be implemented in a subclass') + ) + ) + + +#----------------------------------------------------------------------------- +# Local process launchers +#----------------------------------------------------------------------------- + + +class LocalProcessLauncherProtocol(ProcessProtocol): + """A ProcessProtocol to go with the LocalProcessLauncher.""" + + def __init__(self, process_launcher): + self.process_launcher = process_launcher + self.pid = None + + def connectionMade(self): + self.pid = self.transport.pid + self.process_launcher.notify_start(self.transport.pid) + + def processEnded(self, status): + value = status.value + if isinstance(value, ProcessDone): + self.process_launcher.notify_stop( + {'exit_code':0, + 'signal':None, + 'status':None, + 'pid':self.pid + } + ) + elif isinstance(value, ProcessTerminated): + self.process_launcher.notify_stop( + {'exit_code':value.exitCode, + 'signal':value.signal, + 'status':value.status, + 'pid':self.pid + } + ) + else: + raise UnknownStatus("Unknown exit status, this is probably a " + "bug in Twisted") + + def outReceived(self, data): + log.msg(data) + + def errReceived(self, data): + log.err(data) + + +class LocalProcessLauncher(BaseLauncher): + """Start and stop an external process in an asynchronous manner. + + This will launch the external process with a working directory of + ``self.work_dir``. + """ + + # This is used to to construct self.args, which is passed to + # spawnProcess. + cmd_and_args = List([]) + + def __init__(self, work_dir, parent=None, name=None, config=None): + super(LocalProcessLauncher, self).__init__( + work_dir, parent, name, config + ) + self.process_protocol = None + self.start_deferred = None + + def find_args(self): + return self.cmd_and_args + + def start(self): + if self.state == 'before': + self.process_protocol = LocalProcessLauncherProtocol(self) + self.start_deferred = defer.Deferred() + self.process_transport = reactor.spawnProcess( + self.process_protocol, + str(self.args[0]), # twisted expects these to be str, not unicode + [str(a) for a in self.args], # str expected, not unicode + env=os.environ, + path=self.work_dir # start in the work_dir + ) + return self.start_deferred + else: + s = 'The process was already started and has state: %r' % self.state + return defer.fail(ProcessStateError(s)) + + def notify_start(self, data): + super(LocalProcessLauncher, self).notify_start(data) + self.start_deferred.callback(data) + + def stop(self): + return self.interrupt_then_kill() + + @make_deferred + def signal(self, sig): + if self.state == 'running': + self.process_transport.signalProcess(sig) + + @inlineCallbacks + def interrupt_then_kill(self, delay=2.0): + """Send INT, wait a delay and then send KILL.""" + yield self.signal('INT') + yield sleep_deferred(delay) + yield self.signal('KILL') + + +class LocalControllerLauncher(LocalProcessLauncher): + """Launch a controller as a regular external process.""" + + controller_cmd = List(find_controller_cmd(), config=True) + # Command line arguments to ipcontroller. + controller_args = List(['--log-to-file','--log-level', '40'], config=True) + + def find_args(self): + return self.controller_cmd + self.controller_args + + def start(self, cluster_dir): + """Start the controller by cluster_dir.""" + self.controller_args.extend(['--cluster-dir', cluster_dir]) + self.cluster_dir = unicode(cluster_dir) + log.msg("Starting LocalControllerLauncher: %r" % self.args) + return super(LocalControllerLauncher, self).start() + + +class LocalEngineLauncher(LocalProcessLauncher): + """Launch a single engine as a regular externall process.""" + + engine_cmd = List(find_engine_cmd(), config=True) + # Command line arguments for ipengine. + engine_args = List( + ['--log-to-file','--log-level', '40'], config=True + ) + + def find_args(self): + return self.engine_cmd + self.engine_args + + def start(self, cluster_dir): + """Start the engine by cluster_dir.""" + self.engine_args.extend(['--cluster-dir', cluster_dir]) + self.cluster_dir = unicode(cluster_dir) + return super(LocalEngineLauncher, self).start() + + +class LocalEngineSetLauncher(BaseLauncher): + """Launch a set of engines as regular external processes.""" + + # Command line arguments for ipengine. + engine_args = List( + ['--log-to-file','--log-level', '40'], config=True + ) + + def __init__(self, work_dir, parent=None, name=None, config=None): + super(LocalEngineSetLauncher, self).__init__( + work_dir, parent, name, config + ) + self.launchers = [] + + def start(self, n, cluster_dir): + """Start n engines by profile or cluster_dir.""" + self.cluster_dir = unicode(cluster_dir) + dlist = [] + for i in range(n): + el = LocalEngineLauncher(self.work_dir, self) + # Copy the engine args over to each engine launcher. + import copy + el.engine_args = copy.deepcopy(self.engine_args) + d = el.start(cluster_dir) + if i==0: + log.msg("Starting LocalEngineSetLauncher: %r" % el.args) + self.launchers.append(el) + dlist.append(d) + # The consumeErrors here could be dangerous + dfinal = gatherBoth(dlist, consumeErrors=True) + dfinal.addCallback(self.notify_start) + return dfinal + + def find_args(self): + return ['engine set'] + + def signal(self, sig): + dlist = [] + for el in self.launchers: + d = el.signal(sig) + dlist.append(d) + dfinal = gatherBoth(dlist, consumeErrors=True) + return dfinal + + def interrupt_then_kill(self, delay=1.0): + dlist = [] + for el in self.launchers: + d = el.interrupt_then_kill(delay) + dlist.append(d) + dfinal = gatherBoth(dlist, consumeErrors=True) + return dfinal + + def stop(self): + return self.interrupt_then_kill() + + def observe_stop(self): + dlist = [el.observe_stop() for el in self.launchers] + dfinal = gatherBoth(dlist, consumeErrors=False) + dfinal.addCallback(self.notify_stop) + return dfinal + + +#----------------------------------------------------------------------------- +# MPIExec launchers +#----------------------------------------------------------------------------- + + +class MPIExecLauncher(LocalProcessLauncher): + """Launch an external process using mpiexec.""" + + # The mpiexec command to use in starting the process. + mpi_cmd = List(['mpiexec'], config=True) + # The command line arguments to pass to mpiexec. + mpi_args = List([], config=True) + # The program to start using mpiexec. + program = List(['date'], config=True) + # The command line argument to the program. + program_args = List([], config=True) + # The number of instances of the program to start. + n = Int(1, config=True) + + def find_args(self): + """Build self.args using all the fields.""" + return self.mpi_cmd + ['-n', self.n] + self.mpi_args + \ + self.program + self.program_args + + def start(self, n): + """Start n instances of the program using mpiexec.""" + self.n = n + return super(MPIExecLauncher, self).start() + + +class MPIExecControllerLauncher(MPIExecLauncher): + """Launch a controller using mpiexec.""" + + controller_cmd = List(find_controller_cmd(), config=True) + # Command line arguments to ipcontroller. + controller_args = List(['--log-to-file','--log-level', '40'], config=True) + n = Int(1, config=False) + + def start(self, cluster_dir): + """Start the controller by cluster_dir.""" + self.controller_args.extend(['--cluster-dir', cluster_dir]) + self.cluster_dir = unicode(cluster_dir) + log.msg("Starting MPIExecControllerLauncher: %r" % self.args) + return super(MPIExecControllerLauncher, self).start(1) + + def find_args(self): + return self.mpi_cmd + ['-n', self.n] + self.mpi_args + \ + self.controller_cmd + self.controller_args + + +class MPIExecEngineSetLauncher(MPIExecLauncher): + + engine_cmd = List(find_engine_cmd(), config=True) + # Command line arguments for ipengine. + engine_args = List( + ['--log-to-file','--log-level', '40'], config=True + ) + n = Int(1, config=True) + + def start(self, n, cluster_dir): + """Start n engines by profile or cluster_dir.""" + self.engine_args.extend(['--cluster-dir', cluster_dir]) + self.cluster_dir = unicode(cluster_dir) + self.n = n + log.msg('Starting MPIExecEngineSetLauncher: %r' % self.args) + return super(MPIExecEngineSetLauncher, self).start(n) + + def find_args(self): + return self.mpi_cmd + ['-n', self.n] + self.mpi_args + \ + self.engine_cmd + self.engine_args + + +#----------------------------------------------------------------------------- +# SSH launchers +#----------------------------------------------------------------------------- + +# TODO: Get SSH Launcher working again. + +class SSHLauncher(BaseLauncher): + """A minimal launcher for ssh. + + To be useful this will probably have to be extended to use the ``sshx`` + idea for environment variables. There could be other things this needs + as well. + """ + + ssh_cmd = List(['ssh'], config=True) + ssh_args = List([], config=True) + program = List(['date'], config=True) + program_args = List([], config=True) + hostname = Str('', config=True) + user = Str('', config=True) + location = Str('') + + def _hostname_changed(self, name, old, new): + self.location = '%s@%s' % (self.user, new) + + def _user_changed(self, name, old, new): + self.location = '%s@%s' % (new, self.hostname) + + def find_args(self): + return self.ssh_cmd + self.ssh_args + [self.location] + \ + self.program + self.program_args + + def start(self, n, hostname=None, user=None): + if hostname is not None: + self.hostname = hostname + if user is not None: + self.user = user + return super(SSHLauncher, self).start() + + +class SSHControllerLauncher(SSHLauncher): + pass + + +class SSHEngineSetLauncher(BaseLauncher): + pass + + +#----------------------------------------------------------------------------- +# Windows HPC Server 2008 scheduler launchers +#----------------------------------------------------------------------------- + + +# This is only used on Windows. +def find_job_cmd(): + if os.name=='nt': + return find_cmd('job') + else: + return 'job' + + +class WindowsHPCLauncher(BaseLauncher): + + # A regular expression used to get the job id from the output of the + # submit_command. + job_id_regexp = Str(r'\d+', config=True) + # The filename of the instantiated job script. + job_file_name = Unicode(u'ipython_job.xml', config=True) + # The full path to the instantiated job script. This gets made dynamically + # by combining the work_dir with the job_file_name. + job_file = Unicode(u'') + # The hostname of the scheduler to submit the job to + scheduler = Str('', config=True) + job_cmd = Str(find_job_cmd(), config=True) + + def __init__(self, work_dir, parent=None, name=None, config=None): + super(WindowsHPCLauncher, self).__init__( + work_dir, parent, name, config + ) + + @property + def job_file(self): + return os.path.join(self.work_dir, self.job_file_name) + + def write_job_file(self, n): + raise NotImplementedError("Implement write_job_file in a subclass.") + + def find_args(self): + return ['job.exe'] + + def parse_job_id(self, output): + """Take the output of the submit command and return the job id.""" + m = re.search(self.job_id_regexp, output) + if m is not None: + job_id = m.group() + else: + raise LauncherError("Job id couldn't be determined: %s" % output) + self.job_id = job_id + log.msg('Job started with job id: %r' % job_id) + return job_id + + @inlineCallbacks + def start(self, n): + """Start n copies of the process using the Win HPC job scheduler.""" + self.write_job_file(n) + args = [ + 'submit', + '/jobfile:%s' % self.job_file, + '/scheduler:%s' % self.scheduler + ] + log.msg("Starting Win HPC Job: %s" % (self.job_cmd + ' ' + ' '.join(args),)) + # Twisted will raise DeprecationWarnings if we try to pass unicode to this + output = yield getProcessOutput(str(self.job_cmd), + [str(a) for a in args], + env=dict((str(k),str(v)) for k,v in os.environ.items()), + path=self.work_dir + ) + job_id = self.parse_job_id(output) + self.notify_start(job_id) + defer.returnValue(job_id) + + @inlineCallbacks + def stop(self): + args = [ + 'cancel', + self.job_id, + '/scheduler:%s' % self.scheduler + ] + log.msg("Stopping Win HPC Job: %s" % (self.job_cmd + ' ' + ' '.join(args),)) + try: + # Twisted will raise DeprecationWarnings if we try to pass unicode to this + output = yield getProcessOutput(str(self.job_cmd), + [str(a) for a in args], + env=dict((str(k),str(v)) for k,v in os.environ.items()), + path=self.work_dir + ) + except: + output = 'The job already appears to be stoppped: %r' % self.job_id + self.notify_stop(output) # Pass the output of the kill cmd + defer.returnValue(output) + + +class WindowsHPCControllerLauncher(WindowsHPCLauncher): + + job_file_name = Unicode(u'ipcontroller_job.xml', config=True) + extra_args = List([], config=False) + + def write_job_file(self, n): + job = IPControllerJob(self) + + t = IPControllerTask(self) + # The tasks work directory is *not* the actual work directory of + # the controller. It is used as the base path for the stdout/stderr + # files that the scheduler redirects to. + t.work_directory = self.cluster_dir + # Add the --cluster-dir and from self.start(). + t.controller_args.extend(self.extra_args) + job.add_task(t) + + log.msg("Writing job description file: %s" % self.job_file) + job.write(self.job_file) + + @property + def job_file(self): + return os.path.join(self.cluster_dir, self.job_file_name) + + def start(self, cluster_dir): + """Start the controller by cluster_dir.""" + self.extra_args = ['--cluster-dir', cluster_dir] + self.cluster_dir = unicode(cluster_dir) + return super(WindowsHPCControllerLauncher, self).start(1) + + +class WindowsHPCEngineSetLauncher(WindowsHPCLauncher): + + job_file_name = Unicode(u'ipengineset_job.xml', config=True) + extra_args = List([], config=False) + + def write_job_file(self, n): + job = IPEngineSetJob(self) + + for i in range(n): + t = IPEngineTask(self) + # The tasks work directory is *not* the actual work directory of + # the engine. It is used as the base path for the stdout/stderr + # files that the scheduler redirects to. + t.work_directory = self.cluster_dir + # Add the --cluster-dir and from self.start(). + t.engine_args.extend(self.extra_args) + job.add_task(t) + + log.msg("Writing job description file: %s" % self.job_file) + job.write(self.job_file) + + @property + def job_file(self): + return os.path.join(self.cluster_dir, self.job_file_name) + + def start(self, n, cluster_dir): + """Start the controller by cluster_dir.""" + self.extra_args = ['--cluster-dir', cluster_dir] + self.cluster_dir = unicode(cluster_dir) + return super(WindowsHPCEngineSetLauncher, self).start(n) + + +#----------------------------------------------------------------------------- +# Batch (PBS) system launchers +#----------------------------------------------------------------------------- + +# TODO: Get PBS launcher working again. + +class BatchSystemLauncher(BaseLauncher): + """Launch an external process using a batch system. + + This class is designed to work with UNIX batch systems like PBS, LSF, + GridEngine, etc. The overall model is that there are different commands + like qsub, qdel, etc. that handle the starting and stopping of the process. + + This class also has the notion of a batch script. The ``batch_template`` + attribute can be set to a string that is a template for the batch script. + This template is instantiated using Itpl. Thus the template can use + ${n} fot the number of instances. Subclasses can add additional variables + to the template dict. + """ + + # Subclasses must fill these in. See PBSEngineSet + # The name of the command line program used to submit jobs. + submit_command = Str('', config=True) + # The name of the command line program used to delete jobs. + delete_command = Str('', config=True) + # A regular expression used to get the job id from the output of the + # submit_command. + job_id_regexp = Str('', config=True) + # The string that is the batch script template itself. + batch_template = Str('', config=True) + # The filename of the instantiated batch script. + batch_file_name = Unicode(u'batch_script', config=True) + # The full path to the instantiated batch script. + batch_file = Unicode(u'') + + def __init__(self, work_dir, parent=None, name=None, config=None): + super(BatchSystemLauncher, self).__init__( + work_dir, parent, name, config + ) + self.batch_file = os.path.join(self.work_dir, self.batch_file_name) + self.context = {} + + def parse_job_id(self, output): + """Take the output of the submit command and return the job id.""" + m = re.match(self.job_id_regexp, output) + if m is not None: + job_id = m.group() + else: + raise LauncherError("Job id couldn't be determined: %s" % output) + self.job_id = job_id + log.msg('Job started with job id: %r' % job_id) + return job_id + + def write_batch_script(self, n): + """Instantiate and write the batch script to the work_dir.""" + self.context['n'] = n + script_as_string = Itpl.itplns(self.batch_template, self.context) + log.msg('Writing instantiated batch script: %s' % self.batch_file) + f = open(self.batch_file, 'w') + f.write(script_as_string) + f.close() + + @inlineCallbacks + def start(self, n): + """Start n copies of the process using a batch system.""" + self.write_batch_script(n) + output = yield getProcessOutput(self.submit_command, + [self.batch_file], env=os.environ) + job_id = self.parse_job_id(output) + self.notify_start(job_id) + defer.returnValue(job_id) + + @inlineCallbacks + def stop(self): + output = yield getProcessOutput(self.delete_command, + [self.job_id], env=os.environ + ) + self.notify_stop(output) # Pass the output of the kill cmd + defer.returnValue(output) + + +class PBSLauncher(BatchSystemLauncher): + """A BatchSystemLauncher subclass for PBS.""" + + submit_command = Str('qsub', config=True) + delete_command = Str('qdel', config=True) + job_id_regexp = Str(r'\d+', config=True) + batch_template = Str('', config=True) + batch_file_name = Unicode(u'pbs_batch_script', config=True) + batch_file = Unicode(u'') + + +class PBSControllerLauncher(PBSLauncher): + """Launch a controller using PBS.""" + + batch_file_name = Unicode(u'pbs_batch_script_controller', config=True) + + def start(self, cluster_dir): + """Start the controller by profile or cluster_dir.""" + # Here we save profile and cluster_dir in the context so they + # can be used in the batch script template as ${profile} and + # ${cluster_dir} + self.context['cluster_dir'] = cluster_dir + self.cluster_dir = unicode(cluster_dir) + log.msg("Starting PBSControllerLauncher: %r" % self.args) + return super(PBSControllerLauncher, self).start(1) + + +class PBSEngineSetLauncher(PBSLauncher): + + batch_file_name = Unicode(u'pbs_batch_script_engines', config=True) + + def start(self, n, cluster_dir): + """Start n engines by profile or cluster_dir.""" + self.program_args.extend(['--cluster-dir', cluster_dir]) + self.cluster_dir = unicode(cluster_dir) + log.msg('Starting PBSEngineSetLauncher: %r' % self.args) + return super(PBSEngineSetLauncher, self).start(n) + + +#----------------------------------------------------------------------------- +# A launcher for ipcluster itself! +#----------------------------------------------------------------------------- + + +def find_ipcluster_cmd(): + """Find the command line ipcluster program in a cross platform way.""" + if sys.platform == 'win32': + # This logic is needed because the ipcluster script doesn't + # always get installed in the same way or in the same location. + from IPython.kernel import ipclusterapp + script_location = ipclusterapp.__file__.replace('.pyc', '.py') + # The -u option here turns on unbuffered output, which is required + # on Win32 to prevent wierd conflict and problems with Twisted. + # Also, use sys.executable to make sure we are picking up the + # right python exe. + cmd = [sys.executable, '-u', script_location] + else: + # ipcontroller has to be on the PATH in this case. + cmd = ['ipcluster'] + return cmd + + +class IPClusterLauncher(LocalProcessLauncher): + """Launch the ipcluster program in an external process.""" + + ipcluster_cmd = List(find_ipcluster_cmd(), config=True) + # Command line arguments to pass to ipcluster. + ipcluster_args = List( + ['--clean-logs', '--log-to-file', '--log-level', '40'], config=True) + ipcluster_subcommand = Str('start') + ipcluster_n = Int(2) + + def find_args(self): + return self.ipcluster_cmd + [self.ipcluster_subcommand] + \ + ['-n', repr(self.ipcluster_n)] + self.ipcluster_args + + def start(self): + log.msg("Starting ipcluster: %r" % self.args) + return super(IPClusterLauncher, self).start() + diff --git a/IPython/kernel/magic.py b/IPython/kernel/magic.py deleted file mode 100644 index 980c63b..0000000 --- a/IPython/kernel/magic.py +++ /dev/null @@ -1,171 +0,0 @@ -# encoding: utf-8 - -"""Magic command interface for interactive parallel work.""" - -__docformat__ = "restructuredtext en" - -#------------------------------------------------------------------------------- -# Copyright (C) 2008 The IPython Development Team -# -# Distributed under the terms of the BSD License. The full license is in -# the file COPYING, distributed as part of this software. -#------------------------------------------------------------------------------- - -#------------------------------------------------------------------------------- -# Imports -#------------------------------------------------------------------------------- - -import new - -from IPython.iplib import InteractiveShell -from IPython.Shell import MTInteractiveShell - -from twisted.internet.defer import Deferred - - -#------------------------------------------------------------------------------- -# Definitions of magic functions for use with IPython -#------------------------------------------------------------------------------- - -NO_ACTIVE_CONTROLLER = """ -Error: No Controller is activated -Use activate() on a RemoteController object to activate it for magics. -""" - -def magic_result(self,parameter_s=''): - """Print the result of command i on all engines of the active controller. - - To activate a controller in IPython, first create it and then call - the activate() method. - - Then you can do the following: - - >>> result # Print the latest result - Printing result... - [127.0.0.1:0] In [1]: b = 10 - [127.0.0.1:1] In [1]: b = 10 - - >>> result 0 # Print result 0 - In [14]: result 0 - Printing result... - [127.0.0.1:0] In [0]: a = 5 - [127.0.0.1:1] In [0]: a = 5 - """ - try: - activeController = __IPYTHON__.activeController - except AttributeError: - print NO_ACTIVE_CONTROLLER - else: - try: - index = int(parameter_s) - except: - index = None - result = activeController.get_result(index) - return result - -def magic_px(self,parameter_s=''): - """Executes the given python command on the active IPython Controller. - - To activate a Controller in IPython, first create it and then call - the activate() method. - - Then you can do the following: - - >>> %px a = 5 # Runs a = 5 on all nodes - """ - - try: - activeController = __IPYTHON__.activeController - except AttributeError: - print NO_ACTIVE_CONTROLLER - else: - print "Parallel execution on engines: %s" % activeController.targets - result = activeController.execute(parameter_s) - return result - -def pxrunsource(self, source, filename="", symbol="single"): - - try: - code = self.compile(source, filename, symbol) - except (OverflowError, SyntaxError, ValueError): - # Case 1 - self.showsyntaxerror(filename) - return None - - if code is None: - # Case 2 - return True - - # Case 3 - # Because autopx is enabled, we now call executeAll or disable autopx if - # %autopx or autopx has been called - if '_ip.magic("%autopx' in source or '_ip.magic("autopx' in source: - _disable_autopx(self) - return False - else: - try: - result = self.activeController.execute(source) - except: - self.showtraceback() - else: - print result.__repr__() - return False - -def magic_autopx(self, parameter_s=''): - """Toggles auto parallel mode for the active IPython Controller. - - To activate a Controller in IPython, first create it and then call - the activate() method. - - Then you can do the following: - - >>> %autopx # Now all commands are executed in parallel - Auto Parallel Enabled - Type %autopx to disable - ... - >>> %autopx # Now all commands are locally executed - Auto Parallel Disabled - """ - - if hasattr(self, 'autopx'): - if self.autopx == True: - _disable_autopx(self) - else: - _enable_autopx(self) - else: - _enable_autopx(self) - -def _enable_autopx(self): - """Enable %autopx mode by saving the original runsource and installing - pxrunsource. - """ - try: - activeController = __IPYTHON__.activeController - except AttributeError: - print "No active RemoteController found, use RemoteController.activate()." - else: - self._original_runsource = self.runsource - self.runsource = new.instancemethod(pxrunsource, self, self.__class__) - self.autopx = True - print "Auto Parallel Enabled\nType %autopx to disable" - -def _disable_autopx(self): - """Disable %autopx by restoring the original runsource.""" - if hasattr(self, 'autopx'): - if self.autopx == True: - self.runsource = self._original_runsource - self.autopx = False - print "Auto Parallel Disabled" - -# Add the new magic function to the class dict: - -InteractiveShell.magic_result = magic_result -InteractiveShell.magic_px = magic_px -InteractiveShell.magic_autopx = magic_autopx - -# And remove the global name to keep global namespace clean. Don't worry, the -# copy bound to IPython stays, we're just removing the global name. -del magic_result -del magic_px -del magic_autopx - diff --git a/IPython/kernel/map.py b/IPython/kernel/map.py index 6183176..f9ce2f8 100644 --- a/IPython/kernel/map.py +++ b/IPython/kernel/map.py @@ -21,7 +21,7 @@ __docformat__ = "restructuredtext en" import types -from IPython.genutils import flatten as genutil_flatten +from IPython.utils.genutils import flatten as genutil_flatten #------------------------------------------------------------------------------- # Figure out which array packages are present and their array types diff --git a/IPython/kernel/multiengine.py b/IPython/kernel/multiengine.py index 4d1fe6c..bdeba67 100644 --- a/IPython/kernel/multiengine.py +++ b/IPython/kernel/multiengine.py @@ -35,7 +35,7 @@ from twisted.internet import defer, reactor from twisted.python import log, components, failure from zope.interface import Interface, implements, Attribute -from IPython.tools import growl +from IPython.utils import growl from IPython.kernel.util import printer from IPython.kernel.twistedutil import gatherBoth from IPython.kernel import map as Map @@ -262,9 +262,8 @@ class MultiEngine(ControllerAdapterBase): elif targets == 'all': eList = self.engines.values() if len(eList) == 0: - msg = """There are no engines registered. - Check the logs in ~/.ipython/log if you think there should have been.""" - raise error.NoEnginesRegistered(msg) + raise error.NoEnginesRegistered("There are no engines registered. " + "Check the logs if you think there should have been.") else: return eList else: diff --git a/IPython/kernel/multiengineclient.py b/IPython/kernel/multiengineclient.py index 4281df8..ccdb51b 100644 --- a/IPython/kernel/multiengineclient.py +++ b/IPython/kernel/multiengineclient.py @@ -17,17 +17,14 @@ __docformat__ = "restructuredtext en" #------------------------------------------------------------------------------- import sys -import cPickle as pickle -from types import FunctionType import linecache import warnings -from twisted.internet import reactor -from twisted.python import components, log +from twisted.python import components from twisted.python.failure import Failure from zope.interface import Interface, implements, Attribute -from IPython.ColorANSI import TermColors +from IPython.utils.coloransi import TermColors from IPython.kernel.twistedutil import blockingCallFromThread from IPython.kernel import error @@ -37,10 +34,8 @@ from IPython.kernel.mapper import ( IMultiEngineMapperFactory, IMapper ) -from IPython.kernel import map as Map -from IPython.kernel import multiengine as me -from IPython.kernel.multiengine import (IFullMultiEngine, - IFullSynchronousMultiEngine) + +from IPython.kernel.multiengine import IFullSynchronousMultiEngine #------------------------------------------------------------------------------- @@ -91,14 +86,13 @@ class PendingResult(object): A user should not create a `PendingResult` instance by hand. - Methods - ======= + Methods: * `get_result` * `add_callback` - Properties - ========== + Properties: + * `r` """ @@ -269,10 +263,18 @@ class InteractiveMultiEngineClient(object): """ try: - __IPYTHON__.activeController = self + # This is injected into __builtins__. + ip = get_ipython() except NameError: - print "The IPython Controller magics only work within IPython." - + print "The IPython parallel magics (%result, %px, %autopx) only work within IPython." + else: + pmagic = ip.get_component('parallel_magic') + if pmagic is not None: + pmagic.active_multiengine_client = self + else: + print "You must first load the parallelmagic extension " \ + "by doing '%load_ext parallelmagic'" + def __setitem__(self, key, value): """Add a dictionary interface for pushing/pulling. @@ -311,16 +313,16 @@ class InteractiveMultiEngineClient(object): def findsource_file(self,f): linecache.checkcache() - s = findsource(f.f_code) + s = findsource(f.f_code) # findsource is not defined! lnum = f.f_lineno wsource = s[0][f.f_lineno:] return strip_whitespace(wsource) def findsource_ipython(self,f): - from IPython import ipapi + from IPython.core import ipapi self.ip = ipapi.get() wsource = [l+'\n' for l in - self.ip.IP.input_hist_raw[-1].splitlines()[1:]] + self.ip.input_hist_raw[-1].splitlines()[1:]] return strip_whitespace(wsource) def __enter__(self): diff --git a/IPython/kernel/newserialized.py b/IPython/kernel/newserialized.py index 38ba3c7..155ed0c 100644 --- a/IPython/kernel/newserialized.py +++ b/IPython/kernel/newserialized.py @@ -5,6 +5,9 @@ __docformat__ = "restructuredtext en" +# Tell nose to skip this module +__test__ = {} + #------------------------------------------------------------------------------- # Copyright (C) 2008 The IPython Development Team # @@ -18,8 +21,8 @@ __docformat__ = "restructuredtext en" import cPickle as pickle -from zope.interface import Interface, implements from twisted.python import components +from zope.interface import Interface, implements try: import numpy @@ -28,6 +31,10 @@ except ImportError: from IPython.kernel.error import SerializationError +#----------------------------------------------------------------------------- +# Classes and functions +#----------------------------------------------------------------------------- + class ISerialized(Interface): def getData(): diff --git a/IPython/kernel/pendingdeferred.py b/IPython/kernel/pendingdeferred.py index 40c890b..91abdfb 100644 --- a/IPython/kernel/pendingdeferred.py +++ b/IPython/kernel/pendingdeferred.py @@ -30,7 +30,7 @@ from zope.interface import Interface, implements, Attribute from IPython.kernel.twistedutil import gatherBoth from IPython.kernel import error from IPython.external import guid -from IPython.tools import growl +from IPython.utils import growl class PendingDeferredManager(object): """A class to track pending deferreds. diff --git a/IPython/kernel/scripts/ipcluster b/IPython/kernel/scripts/ipcluster index 362a725..b0fe66b 100755 --- a/IPython/kernel/scripts/ipcluster +++ b/IPython/kernel/scripts/ipcluster @@ -1,22 +1,18 @@ #!/usr/bin/env python # encoding: utf-8 -"""ipcluster script""" - -__docformat__ = "restructuredtext en" - -#------------------------------------------------------------------------------- -# Copyright (C) 2008 The IPython Development Team +#----------------------------------------------------------------------------- +# Copyright (C) 2008-2009 The IPython Development Team # # Distributed under the terms of the BSD License. The full license is in # the file COPYING, distributed as part of this software. -#------------------------------------------------------------------------------- +#----------------------------------------------------------------------------- -#------------------------------------------------------------------------------- +#----------------------------------------------------------------------------- # Imports -#------------------------------------------------------------------------------- +#----------------------------------------------------------------------------- + -if __name__ == '__main__': - from IPython.kernel.scripts import ipcluster - ipcluster.main() +from IPython.kernel.ipclusterapp import launch_new_instance +launch_new_instance() diff --git a/IPython/kernel/scripts/ipcluster.py b/IPython/kernel/scripts/ipcluster.py deleted file mode 100644 index 34b2d61..0000000 --- a/IPython/kernel/scripts/ipcluster.py +++ /dev/null @@ -1,813 +0,0 @@ - #!/usr/bin/env python -# encoding: utf-8 - -"""Start an IPython cluster = (controller + engines).""" - -#----------------------------------------------------------------------------- -# Copyright (C) 2008 The IPython Development Team -# -# Distributed under the terms of the BSD License. The full license is in -# the file COPYING, distributed as part of this software. -#----------------------------------------------------------------------------- - -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- - -import os -import re -import sys -import signal -import tempfile -pjoin = os.path.join - -from twisted.internet import reactor, defer -from twisted.internet.protocol import ProcessProtocol -from twisted.internet.error import ProcessDone, ProcessTerminated -from twisted.internet.utils import getProcessOutput -from twisted.python import failure, log - -from IPython.external import argparse -from IPython.external import Itpl -from IPython.genutils import ( - get_ipython_dir, - get_log_dir, - get_security_dir, - num_cpus -) -from IPython.kernel.fcutil import have_crypto - -# Create various ipython directories if they don't exist. -# This must be done before IPython.kernel.config is imported. -from IPython.iplib import user_setup -if os.name == 'posix': - rc_suffix = '' -else: - rc_suffix = '.ini' -user_setup(get_ipython_dir(), rc_suffix, mode='install', interactive=False) -get_log_dir() -get_security_dir() - -from IPython.kernel.config import config_manager as kernel_config_manager -from IPython.kernel.error import SecurityError, FileTimeoutError -from IPython.kernel.fcutil import have_crypto -from IPython.kernel.twistedutil import gatherBoth, wait_for_file -from IPython.kernel.util import printer - -#----------------------------------------------------------------------------- -# General process handling code -#----------------------------------------------------------------------------- - - -class ProcessStateError(Exception): - pass - -class UnknownStatus(Exception): - pass - -class LauncherProcessProtocol(ProcessProtocol): - """ - A ProcessProtocol to go with the ProcessLauncher. - """ - def __init__(self, process_launcher): - self.process_launcher = process_launcher - - def connectionMade(self): - self.process_launcher.fire_start_deferred(self.transport.pid) - - def processEnded(self, status): - value = status.value - if isinstance(value, ProcessDone): - self.process_launcher.fire_stop_deferred(0) - elif isinstance(value, ProcessTerminated): - self.process_launcher.fire_stop_deferred( - {'exit_code':value.exitCode, - 'signal':value.signal, - 'status':value.status - } - ) - else: - raise UnknownStatus("unknown exit status, this is probably a bug in Twisted") - - def outReceived(self, data): - log.msg(data) - - def errReceived(self, data): - log.err(data) - -class ProcessLauncher(object): - """ - Start and stop an external process in an asynchronous manner. - - Currently this uses deferreds to notify other parties of process state - changes. This is an awkward design and should be moved to using - a formal NotificationCenter. - """ - def __init__(self, cmd_and_args): - self.cmd = cmd_and_args[0] - self.args = cmd_and_args - self._reset() - - def _reset(self): - self.process_protocol = None - self.pid = None - self.start_deferred = None - self.stop_deferreds = [] - self.state = 'before' # before, running, or after - - @property - def running(self): - if self.state == 'running': - return True - else: - return False - - def fire_start_deferred(self, pid): - self.pid = pid - self.state = 'running' - log.msg('Process %r has started with pid=%i' % (self.args, pid)) - self.start_deferred.callback(pid) - - def start(self): - if self.state == 'before': - self.process_protocol = LauncherProcessProtocol(self) - self.start_deferred = defer.Deferred() - self.process_transport = reactor.spawnProcess( - self.process_protocol, - self.cmd, - self.args, - env=os.environ - ) - return self.start_deferred - else: - s = 'the process has already been started and has state: %r' % \ - self.state - return defer.fail(ProcessStateError(s)) - - def get_stop_deferred(self): - if self.state == 'running' or self.state == 'before': - d = defer.Deferred() - self.stop_deferreds.append(d) - return d - else: - s = 'this process is already complete' - return defer.fail(ProcessStateError(s)) - - def fire_stop_deferred(self, exit_code): - log.msg('Process %r has stopped with %r' % (self.args, exit_code)) - self.state = 'after' - for d in self.stop_deferreds: - d.callback(exit_code) - - def signal(self, sig): - """ - Send a signal to the process. - - The argument sig can be ('KILL','INT', etc.) or any signal number. - """ - if self.state == 'running': - self.process_transport.signalProcess(sig) - - # def __del__(self): - # self.signal('KILL') - - def interrupt_then_kill(self, delay=1.0): - self.signal('INT') - reactor.callLater(delay, self.signal, 'KILL') - - -#----------------------------------------------------------------------------- -# Code for launching controller and engines -#----------------------------------------------------------------------------- - - -class ControllerLauncher(ProcessLauncher): - - def __init__(self, extra_args=None): - if sys.platform == 'win32': - # This logic is needed because the ipcontroller script doesn't - # always get installed in the same way or in the same location. - from IPython.kernel.scripts import ipcontroller - script_location = ipcontroller.__file__.replace('.pyc', '.py') - # The -u option here turns on unbuffered output, which is required - # on Win32 to prevent wierd conflict and problems with Twisted. - # Also, use sys.executable to make sure we are picking up the - # right python exe. - args = [sys.executable, '-u', script_location] - else: - args = ['ipcontroller'] - self.extra_args = extra_args - if extra_args is not None: - args.extend(extra_args) - - ProcessLauncher.__init__(self, args) - - -class EngineLauncher(ProcessLauncher): - - def __init__(self, extra_args=None): - if sys.platform == 'win32': - # This logic is needed because the ipcontroller script doesn't - # always get installed in the same way or in the same location. - from IPython.kernel.scripts import ipengine - script_location = ipengine.__file__.replace('.pyc', '.py') - # The -u option here turns on unbuffered output, which is required - # on Win32 to prevent wierd conflict and problems with Twisted. - # Also, use sys.executable to make sure we are picking up the - # right python exe. - args = [sys.executable, '-u', script_location] - else: - args = ['ipengine'] - self.extra_args = extra_args - if extra_args is not None: - args.extend(extra_args) - - ProcessLauncher.__init__(self, args) - - -class LocalEngineSet(object): - - def __init__(self, extra_args=None): - self.extra_args = extra_args - self.launchers = [] - - def start(self, n): - dlist = [] - for i in range(n): - el = EngineLauncher(extra_args=self.extra_args) - d = el.start() - self.launchers.append(el) - dlist.append(d) - dfinal = gatherBoth(dlist, consumeErrors=True) - dfinal.addCallback(self._handle_start) - return dfinal - - def _handle_start(self, r): - log.msg('Engines started with pids: %r' % r) - return r - - def _handle_stop(self, r): - log.msg('Engines received signal: %r' % r) - return r - - def signal(self, sig): - dlist = [] - for el in self.launchers: - d = el.get_stop_deferred() - dlist.append(d) - el.signal(sig) - dfinal = gatherBoth(dlist, consumeErrors=True) - dfinal.addCallback(self._handle_stop) - return dfinal - - def interrupt_then_kill(self, delay=1.0): - dlist = [] - for el in self.launchers: - d = el.get_stop_deferred() - dlist.append(d) - el.interrupt_then_kill(delay) - dfinal = gatherBoth(dlist, consumeErrors=True) - dfinal.addCallback(self._handle_stop) - return dfinal - - -class BatchEngineSet(object): - - # Subclasses must fill these in. See PBSEngineSet - submit_command = '' - delete_command = '' - job_id_regexp = '' - - def __init__(self, template_file, **kwargs): - self.template_file = template_file - self.context = {} - self.context.update(kwargs) - self.batch_file = self.template_file+'-run' - - def parse_job_id(self, output): - m = re.match(self.job_id_regexp, output) - if m is not None: - job_id = m.group() - else: - raise Exception("job id couldn't be determined: %s" % output) - self.job_id = job_id - log.msg('Job started with job id: %r' % job_id) - return job_id - - def write_batch_script(self, n): - self.context['n'] = n - template = open(self.template_file, 'r').read() - log.msg('Using template for batch script: %s' % self.template_file) - script_as_string = Itpl.itplns(template, self.context) - log.msg('Writing instantiated batch script: %s' % self.batch_file) - f = open(self.batch_file,'w') - f.write(script_as_string) - f.close() - - def handle_error(self, f): - f.printTraceback() - f.raiseException() - - def start(self, n): - self.write_batch_script(n) - d = getProcessOutput(self.submit_command, - [self.batch_file],env=os.environ) - d.addCallback(self.parse_job_id) - d.addErrback(self.handle_error) - return d - - def kill(self): - d = getProcessOutput(self.delete_command, - [self.job_id],env=os.environ) - return d - -class PBSEngineSet(BatchEngineSet): - - submit_command = 'qsub' - delete_command = 'qdel' - job_id_regexp = '\d+' - - def __init__(self, template_file, **kwargs): - BatchEngineSet.__init__(self, template_file, **kwargs) - - -sshx_template="""#!/bin/sh -"$@" &> /dev/null & -echo $! -""" - -engine_killer_template="""#!/bin/sh -ps -fu `whoami` | grep '[i]pengine' | awk '{print $2}' | xargs kill -TERM -""" - -class SSHEngineSet(object): - sshx_template=sshx_template - engine_killer_template=engine_killer_template - - def __init__(self, engine_hosts, sshx=None, ipengine="ipengine"): - """Start a controller on localhost and engines using ssh. - - The engine_hosts argument is a dict with hostnames as keys and - the number of engine (int) as values. sshx is the name of a local - file that will be used to run remote commands. This file is used - to setup the environment properly. - """ - - self.temp_dir = tempfile.gettempdir() - if sshx is not None: - self.sshx = sshx - else: - # Write the sshx.sh file locally from our template. - self.sshx = os.path.join( - self.temp_dir, - '%s-main-sshx.sh' % os.environ['USER'] - ) - f = open(self.sshx, 'w') - f.writelines(self.sshx_template) - f.close() - self.engine_command = ipengine - self.engine_hosts = engine_hosts - # Write the engine killer script file locally from our template. - self.engine_killer = os.path.join( - self.temp_dir, - '%s-local-engine_killer.sh' % os.environ['USER'] - ) - f = open(self.engine_killer, 'w') - f.writelines(self.engine_killer_template) - f.close() - - def start(self, send_furl=False): - dlist = [] - for host in self.engine_hosts.keys(): - count = self.engine_hosts[host] - d = self._start(host, count, send_furl) - dlist.append(d) - return gatherBoth(dlist, consumeErrors=True) - - def _start(self, hostname, count=1, send_furl=False): - if send_furl: - d = self._scp_furl(hostname) - else: - d = defer.succeed(None) - d.addCallback(lambda r: self._scp_sshx(hostname)) - d.addCallback(lambda r: self._ssh_engine(hostname, count)) - return d - - def _scp_furl(self, hostname): - scp_cmd = "scp ~/.ipython/security/ipcontroller-engine.furl %s:.ipython/security/" % (hostname) - cmd_list = scp_cmd.split() - cmd_list[1] = os.path.expanduser(cmd_list[1]) - log.msg('Copying furl file: %s' % scp_cmd) - d = getProcessOutput(cmd_list[0], cmd_list[1:], env=os.environ) - return d - - def _scp_sshx(self, hostname): - scp_cmd = "scp %s %s:%s/%s-sshx.sh" % ( - self.sshx, hostname, - self.temp_dir, os.environ['USER'] - ) - print - log.msg("Copying sshx: %s" % scp_cmd) - sshx_scp = scp_cmd.split() - d = getProcessOutput(sshx_scp[0], sshx_scp[1:], env=os.environ) - return d - - def _ssh_engine(self, hostname, count): - exec_engine = "ssh %s sh %s/%s-sshx.sh %s" % ( - hostname, self.temp_dir, - os.environ['USER'], self.engine_command - ) - cmds = exec_engine.split() - dlist = [] - log.msg("about to start engines...") - for i in range(count): - log.msg('Starting engines: %s' % exec_engine) - d = getProcessOutput(cmds[0], cmds[1:], env=os.environ) - dlist.append(d) - return gatherBoth(dlist, consumeErrors=True) - - def kill(self): - dlist = [] - for host in self.engine_hosts.keys(): - d = self._killall(host) - dlist.append(d) - return gatherBoth(dlist, consumeErrors=True) - - def _killall(self, hostname): - d = self._scp_engine_killer(hostname) - d.addCallback(lambda r: self._ssh_kill(hostname)) - # d.addErrback(self._exec_err) - return d - - def _scp_engine_killer(self, hostname): - scp_cmd = "scp %s %s:%s/%s-engine_killer.sh" % ( - self.engine_killer, - hostname, - self.temp_dir, - os.environ['USER'] - ) - cmds = scp_cmd.split() - log.msg('Copying engine_killer: %s' % scp_cmd) - d = getProcessOutput(cmds[0], cmds[1:], env=os.environ) - return d - - def _ssh_kill(self, hostname): - kill_cmd = "ssh %s sh %s/%s-engine_killer.sh" % ( - hostname, - self.temp_dir, - os.environ['USER'] - ) - log.msg('Killing engine: %s' % kill_cmd) - kill_cmd = kill_cmd.split() - d = getProcessOutput(kill_cmd[0], kill_cmd[1:], env=os.environ) - return d - - def _exec_err(self, r): - log.msg(r) - -#----------------------------------------------------------------------------- -# Main functions for the different types of clusters -#----------------------------------------------------------------------------- - -# TODO: -# The logic in these codes should be moved into classes like LocalCluster -# MpirunCluster, PBSCluster, etc. This would remove alot of the duplications. -# The main functions should then just parse the command line arguments, create -# the appropriate class and call a 'start' method. - - -def check_security(args, cont_args): - """Check to see if we should run with SSL support.""" - if (not args.x or not args.y) and not have_crypto: - log.err(""" -OpenSSL/pyOpenSSL is not available, so we can't run in secure mode. -Try running ipcluster with the -xy flags: ipcluster local -xy -n 4""") - reactor.stop() - return False - if args.x: - cont_args.append('-x') - if args.y: - cont_args.append('-y') - return True - - -def check_reuse(args, cont_args): - """Check to see if we should try to resuse FURL files.""" - if args.r: - cont_args.append('-r') - if args.client_port == 0 or args.engine_port == 0: - log.err(""" -To reuse FURL files, you must also set the client and engine ports using -the --client-port and --engine-port options.""") - reactor.stop() - return False - cont_args.append('--client-port=%i' % args.client_port) - cont_args.append('--engine-port=%i' % args.engine_port) - return True - - -def _err_and_stop(f): - """Errback to log a failure and halt the reactor on a fatal error.""" - log.err(f) - reactor.stop() - - -def _delay_start(cont_pid, start_engines, furl_file, reuse): - """Wait for controller to create FURL files and the start the engines.""" - if not reuse: - if os.path.isfile(furl_file): - os.unlink(furl_file) - log.msg('Waiting for controller to finish starting...') - d = wait_for_file(furl_file, delay=0.2, max_tries=50) - d.addCallback(lambda _: log.msg('Controller started')) - d.addCallback(lambda _: start_engines(cont_pid)) - return d - - -def main_local(args): - cont_args = [] - cont_args.append('--logfile=%s' % pjoin(args.logdir,'ipcontroller')) - - # Check security settings before proceeding - if not check_security(args, cont_args): - return - - # See if we are reusing FURL files - if not check_reuse(args, cont_args): - return - - cl = ControllerLauncher(extra_args=cont_args) - dstart = cl.start() - def start_engines(cont_pid): - engine_args = [] - engine_args.append('--logfile=%s' % \ - pjoin(args.logdir,'ipengine%s-' % cont_pid)) - eset = LocalEngineSet(extra_args=engine_args) - def shutdown(signum, frame): - log.msg('Stopping local cluster') - # We are still playing with the times here, but these seem - # to be reliable in allowing everything to exit cleanly. - eset.interrupt_then_kill(0.5) - cl.interrupt_then_kill(0.5) - reactor.callLater(1.0, reactor.stop) - signal.signal(signal.SIGINT,shutdown) - d = eset.start(args.n) - return d - config = kernel_config_manager.get_config_obj() - furl_file = config['controller']['engine_furl_file'] - dstart.addCallback(_delay_start, start_engines, furl_file, args.r) - dstart.addErrback(_err_and_stop) - - -def main_mpi(args): - cont_args = [] - cont_args.append('--logfile=%s' % pjoin(args.logdir,'ipcontroller')) - - # Check security settings before proceeding - if not check_security(args, cont_args): - return - - # See if we are reusing FURL files - if not check_reuse(args, cont_args): - return - - cl = ControllerLauncher(extra_args=cont_args) - dstart = cl.start() - def start_engines(cont_pid): - raw_args = [args.cmd] - raw_args.extend(['-n',str(args.n)]) - raw_args.append('ipengine') - raw_args.append('-l') - raw_args.append(pjoin(args.logdir,'ipengine%s-' % cont_pid)) - if args.mpi: - raw_args.append('--mpi=%s' % args.mpi) - eset = ProcessLauncher(raw_args) - def shutdown(signum, frame): - log.msg('Stopping local cluster') - # We are still playing with the times here, but these seem - # to be reliable in allowing everything to exit cleanly. - eset.interrupt_then_kill(1.0) - cl.interrupt_then_kill(1.0) - reactor.callLater(2.0, reactor.stop) - signal.signal(signal.SIGINT,shutdown) - d = eset.start() - return d - config = kernel_config_manager.get_config_obj() - furl_file = config['controller']['engine_furl_file'] - dstart.addCallback(_delay_start, start_engines, furl_file, args.r) - dstart.addErrback(_err_and_stop) - - -def main_pbs(args): - cont_args = [] - cont_args.append('--logfile=%s' % pjoin(args.logdir,'ipcontroller')) - - # Check security settings before proceeding - if not check_security(args, cont_args): - return - - # See if we are reusing FURL files - if not check_reuse(args, cont_args): - return - - cl = ControllerLauncher(extra_args=cont_args) - dstart = cl.start() - def start_engines(r): - pbs_set = PBSEngineSet(args.pbsscript) - def shutdown(signum, frame): - log.msg('Stopping pbs cluster') - d = pbs_set.kill() - d.addBoth(lambda _: cl.interrupt_then_kill(1.0)) - d.addBoth(lambda _: reactor.callLater(2.0, reactor.stop)) - signal.signal(signal.SIGINT,shutdown) - d = pbs_set.start(args.n) - return d - config = kernel_config_manager.get_config_obj() - furl_file = config['controller']['engine_furl_file'] - dstart.addCallback(_delay_start, start_engines, furl_file, args.r) - dstart.addErrback(_err_and_stop) - - -def main_ssh(args): - """Start a controller on localhost and engines using ssh. - - Your clusterfile should look like:: - - send_furl = False # True, if you want - engines = { - 'engine_host1' : engine_count, - 'engine_host2' : engine_count2 - } - """ - clusterfile = {} - execfile(args.clusterfile, clusterfile) - if not clusterfile.has_key('send_furl'): - clusterfile['send_furl'] = False - - cont_args = [] - cont_args.append('--logfile=%s' % pjoin(args.logdir,'ipcontroller')) - - # Check security settings before proceeding - if not check_security(args, cont_args): - return - - # See if we are reusing FURL files - if not check_reuse(args, cont_args): - return - - cl = ControllerLauncher(extra_args=cont_args) - dstart = cl.start() - def start_engines(cont_pid): - ssh_set = SSHEngineSet(clusterfile['engines'], sshx=args.sshx) - def shutdown(signum, frame): - d = ssh_set.kill() - cl.interrupt_then_kill(1.0) - reactor.callLater(2.0, reactor.stop) - signal.signal(signal.SIGINT,shutdown) - d = ssh_set.start(clusterfile['send_furl']) - return d - config = kernel_config_manager.get_config_obj() - furl_file = config['controller']['engine_furl_file'] - dstart.addCallback(_delay_start, start_engines, furl_file, args.r) - dstart.addErrback(_err_and_stop) - - -def get_args(): - base_parser = argparse.ArgumentParser(add_help=False) - base_parser.add_argument( - '-r', - action='store_true', - dest='r', - help='try to reuse FURL files. Use with --client-port and --engine-port' - ) - base_parser.add_argument( - '--client-port', - type=int, - dest='client_port', - help='the port the controller will listen on for client connections', - default=0 - ) - base_parser.add_argument( - '--engine-port', - type=int, - dest='engine_port', - help='the port the controller will listen on for engine connections', - default=0 - ) - base_parser.add_argument( - '-x', - action='store_true', - dest='x', - help='turn off client security' - ) - base_parser.add_argument( - '-y', - action='store_true', - dest='y', - help='turn off engine security' - ) - base_parser.add_argument( - "--logdir", - type=str, - dest="logdir", - help="directory to put log files (default=$IPYTHONDIR/log)", - default=pjoin(get_ipython_dir(),'log') - ) - base_parser.add_argument( - "-n", - "--num", - type=int, - dest="n", - default=2, - help="the number of engines to start" - ) - - parser = argparse.ArgumentParser( - description='IPython cluster startup. This starts a controller and\ - engines using various approaches. Use the IPYTHONDIR environment\ - variable to change your IPython directory from the default of\ - .ipython or _ipython. The log and security subdirectories of your\ - IPython directory will be used by this script for log files and\ - security files.' - ) - subparsers = parser.add_subparsers( - help='available cluster types. For help, do "ipcluster TYPE --help"') - - parser_local = subparsers.add_parser( - 'local', - help='run a local cluster', - parents=[base_parser] - ) - parser_local.set_defaults(func=main_local) - - parser_mpirun = subparsers.add_parser( - 'mpirun', - help='run a cluster using mpirun (mpiexec also works)', - parents=[base_parser] - ) - parser_mpirun.add_argument( - "--mpi", - type=str, - dest="mpi", # Don't put a default here to allow no MPI support - help="how to call MPI_Init (default=mpi4py)" - ) - parser_mpirun.set_defaults(func=main_mpi, cmd='mpirun') - - parser_mpiexec = subparsers.add_parser( - 'mpiexec', - help='run a cluster using mpiexec (mpirun also works)', - parents=[base_parser] - ) - parser_mpiexec.add_argument( - "--mpi", - type=str, - dest="mpi", # Don't put a default here to allow no MPI support - help="how to call MPI_Init (default=mpi4py)" - ) - parser_mpiexec.set_defaults(func=main_mpi, cmd='mpiexec') - - parser_pbs = subparsers.add_parser( - 'pbs', - help='run a pbs cluster', - parents=[base_parser] - ) - parser_pbs.add_argument( - '--pbs-script', - type=str, - dest='pbsscript', - help='PBS script template', - default='pbs.template' - ) - parser_pbs.set_defaults(func=main_pbs) - - parser_ssh = subparsers.add_parser( - 'ssh', - help='run a cluster using ssh, should have ssh-keys setup', - parents=[base_parser] - ) - parser_ssh.add_argument( - '--clusterfile', - type=str, - dest='clusterfile', - help='python file describing the cluster', - default='clusterfile.py', - ) - parser_ssh.add_argument( - '--sshx', - type=str, - dest='sshx', - help='sshx launcher helper' - ) - parser_ssh.set_defaults(func=main_ssh) - - args = parser.parse_args() - return args - -def main(): - args = get_args() - reactor.callWhenRunning(args.func, args) - log.startLogging(sys.stdout) - reactor.run() - -if __name__ == '__main__': - main() diff --git a/IPython/kernel/scripts/ipcontroller b/IPython/kernel/scripts/ipcontroller index b4c86a4..5209663 100755 --- a/IPython/kernel/scripts/ipcontroller +++ b/IPython/kernel/scripts/ipcontroller @@ -1,20 +1,18 @@ #!/usr/bin/env python # encoding: utf-8 -__docformat__ = "restructuredtext en" - -#------------------------------------------------------------------------------- -# Copyright (C) 2008 The IPython Development Team +#----------------------------------------------------------------------------- +# Copyright (C) 2008-2009 The IPython Development Team # # Distributed under the terms of the BSD License. The full license is in # the file COPYING, distributed as part of this software. -#------------------------------------------------------------------------------- +#----------------------------------------------------------------------------- -#------------------------------------------------------------------------------- +#----------------------------------------------------------------------------- # Imports -#------------------------------------------------------------------------------- +#----------------------------------------------------------------------------- + -if __name__ == '__main__': - from IPython.kernel.scripts import ipcontroller - ipcontroller.main() +from IPython.kernel.ipcontrollerapp import launch_new_instance +launch_new_instance() diff --git a/IPython/kernel/scripts/ipcontroller.py b/IPython/kernel/scripts/ipcontroller.py deleted file mode 100755 index 496b139..0000000 --- a/IPython/kernel/scripts/ipcontroller.py +++ /dev/null @@ -1,416 +0,0 @@ -#!/usr/bin/env python -# encoding: utf-8 - -"""The IPython controller.""" - -__docformat__ = "restructuredtext en" - -#------------------------------------------------------------------------------- -# Copyright (C) 2008 The IPython Development Team -# -# Distributed under the terms of the BSD License. The full license is in -# the file COPYING, distributed as part of this software. -#------------------------------------------------------------------------------- - -#------------------------------------------------------------------------------- -# Imports -#------------------------------------------------------------------------------- - -# Python looks for an empty string at the beginning of sys.path to enable -# importing from the cwd. -import sys -sys.path.insert(0, '') - -from optparse import OptionParser -import os -import time -import tempfile - -from twisted.application import internet, service -from twisted.internet import reactor, error, defer -from twisted.python import log - -from IPython.kernel.fcutil import Tub, UnauthenticatedTub, have_crypto - -# from IPython.tools import growl -# growl.start("IPython1 Controller") - -from IPython.kernel.error import SecurityError -from IPython.kernel import controllerservice -from IPython.kernel.fcutil import check_furl_file_security - -# Create various ipython directories if they don't exist. -# This must be done before IPython.kernel.config is imported. -from IPython.iplib import user_setup -from IPython.genutils import get_ipython_dir, get_log_dir, get_security_dir -if os.name == 'posix': - rc_suffix = '' -else: - rc_suffix = '.ini' -user_setup(get_ipython_dir(), rc_suffix, mode='install', interactive=False) -get_log_dir() -get_security_dir() - -from IPython.kernel.config import config_manager as kernel_config_manager -from IPython.config.cutils import import_item - - -#------------------------------------------------------------------------------- -# Code -#------------------------------------------------------------------------------- - -def get_temp_furlfile(filename): - return tempfile.mktemp(dir=os.path.dirname(filename), - prefix=os.path.basename(filename)) - -def make_tub(ip, port, secure, cert_file): - """ - Create a listening tub given an ip, port, and cert_file location. - - :Parameters: - ip : str - The ip address that the tub should listen on. Empty means all - port : int - The port that the tub should listen on. A value of 0 means - pick a random port - secure: boolean - Will the connection be secure (in the foolscap sense) - cert_file: - A filename of a file to be used for theSSL certificate - """ - if secure: - if have_crypto: - tub = Tub(certFile=cert_file) - else: - raise SecurityError(""" -OpenSSL/pyOpenSSL is not available, so we can't run in secure mode. -Try running without security using 'ipcontroller -xy'. -""") - else: - tub = UnauthenticatedTub() - - # Set the strport based on the ip and port and start listening - if ip == '': - strport = "tcp:%i" % port - else: - strport = "tcp:%i:interface=%s" % (port, ip) - listener = tub.listenOn(strport) - - return tub, listener - -def make_client_service(controller_service, config): - """ - Create a service that will listen for clients. - - This service is simply a `foolscap.Tub` instance that has a set of Referenceables - registered with it. - """ - - # Now create the foolscap tub - ip = config['controller']['client_tub']['ip'] - port = config['controller']['client_tub'].as_int('port') - location = config['controller']['client_tub']['location'] - secure = config['controller']['client_tub']['secure'] - cert_file = config['controller']['client_tub']['cert_file'] - client_tub, client_listener = make_tub(ip, port, secure, cert_file) - - # Set the location in the trivial case of localhost - if ip == 'localhost' or ip == '127.0.0.1': - location = "127.0.0.1" - - if not secure: - log.msg("WARNING: you are running the controller with no client security") - - def set_location_and_register(): - """Set the location for the tub and return a deferred.""" - - def register(empty, ref, furl_file): - # We create and then move to make sure that when the file - # appears to other processes, the buffer has the flushed - # and the file has been closed - temp_furl_file = get_temp_furlfile(furl_file) - client_tub.registerReference(ref, furlFile=temp_furl_file) - os.rename(temp_furl_file, furl_file) - - if location == '': - d = client_tub.setLocationAutomatically() - else: - d = defer.maybeDeferred(client_tub.setLocation, "%s:%i" % (location, client_listener.getPortnum())) - - for ciname, ci in config['controller']['controller_interfaces'].iteritems(): - log.msg("Adapting Controller to interface: %s" % ciname) - furl_file = ci['furl_file'] - log.msg("Saving furl for interface [%s] to file: %s" % (ciname, furl_file)) - check_furl_file_security(furl_file, secure) - adapted_controller = import_item(ci['controller_interface'])(controller_service) - d.addCallback(register, import_item(ci['fc_interface'])(adapted_controller), - furl_file=ci['furl_file']) - - reactor.callWhenRunning(set_location_and_register) - return client_tub - - -def make_engine_service(controller_service, config): - """ - Create a service that will listen for engines. - - This service is simply a `foolscap.Tub` instance that has a set of Referenceables - registered with it. - """ - - # Now create the foolscap tub - ip = config['controller']['engine_tub']['ip'] - port = config['controller']['engine_tub'].as_int('port') - location = config['controller']['engine_tub']['location'] - secure = config['controller']['engine_tub']['secure'] - cert_file = config['controller']['engine_tub']['cert_file'] - engine_tub, engine_listener = make_tub(ip, port, secure, cert_file) - - # Set the location in the trivial case of localhost - if ip == 'localhost' or ip == '127.0.0.1': - location = "127.0.0.1" - - if not secure: - log.msg("WARNING: you are running the controller with no engine security") - - def set_location_and_register(): - """Set the location for the tub and return a deferred.""" - - def register(empty, ref, furl_file): - # We create and then move to make sure that when the file - # appears to other processes, the buffer has the flushed - # and the file has been closed - temp_furl_file = get_temp_furlfile(furl_file) - engine_tub.registerReference(ref, furlFile=temp_furl_file) - os.rename(temp_furl_file, furl_file) - - if location == '': - d = engine_tub.setLocationAutomatically() - else: - d = defer.maybeDeferred(engine_tub.setLocation, "%s:%i" % (location, engine_listener.getPortnum())) - - furl_file = config['controller']['engine_furl_file'] - engine_fc_interface = import_item(config['controller']['engine_fc_interface']) - log.msg("Saving furl for the engine to file: %s" % furl_file) - check_furl_file_security(furl_file, secure) - fc_controller = engine_fc_interface(controller_service) - d.addCallback(register, fc_controller, furl_file=furl_file) - - reactor.callWhenRunning(set_location_and_register) - return engine_tub - -def start_controller(): - """ - Start the controller by creating the service hierarchy and starting the reactor. - - This method does the following: - - * It starts the controller logging - * In execute an import statement for the controller - * It creates 2 `foolscap.Tub` instances for the client and the engines - and registers `foolscap.Referenceables` with the tubs to expose the - controller to engines and clients. - """ - config = kernel_config_manager.get_config_obj() - - # Start logging - logfile = config['controller']['logfile'] - if logfile: - logfile = logfile + str(os.getpid()) + '.log' - try: - openLogFile = open(logfile, 'w') - except: - openLogFile = sys.stdout - else: - openLogFile = sys.stdout - log.startLogging(openLogFile) - - # Execute any user defined import statements - cis = config['controller']['import_statement'] - if cis: - try: - exec cis in globals(), locals() - except: - log.msg("Error running import_statement: %s" % cis) - - # Delete old furl files unless the reuse_furls is set - reuse = config['controller']['reuse_furls'] - if not reuse: - paths = (config['controller']['engine_furl_file'], - config['controller']['controller_interfaces']['task']['furl_file'], - config['controller']['controller_interfaces']['multiengine']['furl_file'] - ) - for p in paths: - if os.path.isfile(p): - os.remove(p) - - # Create the service hierarchy - main_service = service.MultiService() - # The controller service - controller_service = controllerservice.ControllerService() - controller_service.setServiceParent(main_service) - # The client tub and all its refereceables - client_service = make_client_service(controller_service, config) - client_service.setServiceParent(main_service) - # The engine tub - engine_service = make_engine_service(controller_service, config) - engine_service.setServiceParent(main_service) - # Start the controller service and set things running - main_service.startService() - reactor.run() - -def init_config(): - """ - Initialize the configuration using default and command line options. - """ - - parser = OptionParser("""ipcontroller [options] - -Start an IPython controller. - -Use the IPYTHONDIR environment variable to change your IPython directory -from the default of .ipython or _ipython. The log and security -subdirectories of your IPython directory will be used by this script -for log files and security files.""") - - # Client related options - parser.add_option( - "--client-ip", - type="string", - dest="client_ip", - help="the IP address or hostname the controller will listen on for client connections" - ) - parser.add_option( - "--client-port", - type="int", - dest="client_port", - help="the port the controller will listen on for client connections" - ) - parser.add_option( - '--client-location', - type="string", - dest="client_location", - help="hostname or ip for clients to connect to" - ) - parser.add_option( - "-x", - action="store_false", - dest="client_secure", - help="turn off all client security" - ) - parser.add_option( - '--client-cert-file', - type="string", - dest="client_cert_file", - help="file to store the client SSL certificate" - ) - parser.add_option( - '--task-furl-file', - type="string", - dest="task_furl_file", - help="file to store the FURL for task clients to connect with" - ) - parser.add_option( - '--multiengine-furl-file', - type="string", - dest="multiengine_furl_file", - help="file to store the FURL for multiengine clients to connect with" - ) - # Engine related options - parser.add_option( - "--engine-ip", - type="string", - dest="engine_ip", - help="the IP address or hostname the controller will listen on for engine connections" - ) - parser.add_option( - "--engine-port", - type="int", - dest="engine_port", - help="the port the controller will listen on for engine connections" - ) - parser.add_option( - '--engine-location', - type="string", - dest="engine_location", - help="hostname or ip for engines to connect to" - ) - parser.add_option( - "-y", - action="store_false", - dest="engine_secure", - help="turn off all engine security" - ) - parser.add_option( - '--engine-cert-file', - type="string", - dest="engine_cert_file", - help="file to store the engine SSL certificate" - ) - parser.add_option( - '--engine-furl-file', - type="string", - dest="engine_furl_file", - help="file to store the FURL for engines to connect with" - ) - parser.add_option( - "-l", "--logfile", - type="string", - dest="logfile", - help="log file name (default is stdout)" - ) - parser.add_option( - "-r", - action="store_true", - dest="reuse_furls", - help="try to reuse all furl files" - ) - - (options, args) = parser.parse_args() - - config = kernel_config_manager.get_config_obj() - - # Update with command line options - if options.client_ip is not None: - config['controller']['client_tub']['ip'] = options.client_ip - if options.client_port is not None: - config['controller']['client_tub']['port'] = options.client_port - if options.client_location is not None: - config['controller']['client_tub']['location'] = options.client_location - if options.client_secure is not None: - config['controller']['client_tub']['secure'] = options.client_secure - if options.client_cert_file is not None: - config['controller']['client_tub']['cert_file'] = options.client_cert_file - if options.task_furl_file is not None: - config['controller']['controller_interfaces']['task']['furl_file'] = options.task_furl_file - if options.multiengine_furl_file is not None: - config['controller']['controller_interfaces']['multiengine']['furl_file'] = options.multiengine_furl_file - if options.engine_ip is not None: - config['controller']['engine_tub']['ip'] = options.engine_ip - if options.engine_port is not None: - config['controller']['engine_tub']['port'] = options.engine_port - if options.engine_location is not None: - config['controller']['engine_tub']['location'] = options.engine_location - if options.engine_secure is not None: - config['controller']['engine_tub']['secure'] = options.engine_secure - if options.engine_cert_file is not None: - config['controller']['engine_tub']['cert_file'] = options.engine_cert_file - if options.engine_furl_file is not None: - config['controller']['engine_furl_file'] = options.engine_furl_file - if options.reuse_furls is not None: - config['controller']['reuse_furls'] = options.reuse_furls - - if options.logfile is not None: - config['controller']['logfile'] = options.logfile - - kernel_config_manager.update_config_obj(config) - -def main(): - """ - After creating the configuration information, start the controller. - """ - init_config() - start_controller() - -if __name__ == "__main__": - main() diff --git a/IPython/kernel/scripts/ipengine b/IPython/kernel/scripts/ipengine index 92eab1c..2a4e04a 100755 --- a/IPython/kernel/scripts/ipengine +++ b/IPython/kernel/scripts/ipengine @@ -1,20 +1,20 @@ #!/usr/bin/env python # encoding: utf-8 -__docformat__ = "restructuredtext en" - -#------------------------------------------------------------------------------- -# Copyright (C) 2008 The IPython Development Team +#----------------------------------------------------------------------------- +# Copyright (C) 2008-2009 The IPython Development Team # # Distributed under the terms of the BSD License. The full license is in # the file COPYING, distributed as part of this software. -#------------------------------------------------------------------------------- +#----------------------------------------------------------------------------- -#------------------------------------------------------------------------------- +#----------------------------------------------------------------------------- # Imports -#------------------------------------------------------------------------------- +#----------------------------------------------------------------------------- + + +from IPython.kernel.ipengineapp import launch_new_instance + +launch_new_instance() -if __name__ == '__main__': - from IPython.kernel.scripts import ipengine - ipengine.main() diff --git a/IPython/kernel/scripts/ipengine.py b/IPython/kernel/scripts/ipengine.py deleted file mode 100755 index a70ec6a..0000000 --- a/IPython/kernel/scripts/ipengine.py +++ /dev/null @@ -1,193 +0,0 @@ -#!/usr/bin/env python -# encoding: utf-8 - -"""Start the IPython Engine.""" - -__docformat__ = "restructuredtext en" - -#------------------------------------------------------------------------------- -# Copyright (C) 2008 The IPython Development Team -# -# Distributed under the terms of the BSD License. The full license is in -# the file COPYING, distributed as part of this software. -#------------------------------------------------------------------------------- - -#------------------------------------------------------------------------------- -# Imports -#------------------------------------------------------------------------------- - -# Python looks for an empty string at the beginning of sys.path to enable -# importing from the cwd. -import sys -sys.path.insert(0, '') - -from optparse import OptionParser -import os - -from twisted.application import service -from twisted.internet import reactor -from twisted.python import log - -from IPython.kernel.fcutil import Tub, UnauthenticatedTub - -from IPython.kernel.core.config import config_manager as core_config_manager -from IPython.config.cutils import import_item -from IPython.kernel.engineservice import EngineService - -# Create various ipython directories if they don't exist. -# This must be done before IPython.kernel.config is imported. -from IPython.iplib import user_setup -from IPython.genutils import get_ipython_dir, get_log_dir, get_security_dir -if os.name == 'posix': - rc_suffix = '' -else: - rc_suffix = '.ini' -user_setup(get_ipython_dir(), rc_suffix, mode='install', interactive=False) -get_log_dir() -get_security_dir() - -from IPython.kernel.config import config_manager as kernel_config_manager -from IPython.kernel.engineconnector import EngineConnector - - -#------------------------------------------------------------------------------- -# Code -#------------------------------------------------------------------------------- - -def start_engine(): - """ - Start the engine, by creating it and starting the Twisted reactor. - - This method does: - - * If it exists, runs the `mpi_import_statement` to call `MPI_Init` - * Starts the engine logging - * Creates an IPython shell and wraps it in an `EngineService` - * Creates a `foolscap.Tub` to use in connecting to a controller. - * Uses the tub and the `EngineService` along with a Foolscap URL - (or FURL) to connect to the controller and register the engine - with the controller - """ - kernel_config = kernel_config_manager.get_config_obj() - core_config = core_config_manager.get_config_obj() - - - # Execute the mpi import statement that needs to call MPI_Init - global mpi - mpikey = kernel_config['mpi']['default'] - mpi_import_statement = kernel_config['mpi'].get(mpikey, None) - if mpi_import_statement is not None: - try: - exec mpi_import_statement in globals() - except: - mpi = None - else: - mpi = None - - # Start logging - logfile = kernel_config['engine']['logfile'] - if logfile: - logfile = logfile + str(os.getpid()) + '.log' - try: - openLogFile = open(logfile, 'w') - except: - openLogFile = sys.stdout - else: - openLogFile = sys.stdout - log.startLogging(openLogFile) - - # Create the underlying shell class and EngineService - shell_class = import_item(core_config['shell']['shell_class']) - engine_service = EngineService(shell_class, mpi=mpi) - shell_import_statement = core_config['shell']['import_statement'] - if shell_import_statement: - try: - engine_service.execute(shell_import_statement) - except: - log.msg("Error running import_statement: %s" % shell_import_statement) - - # Create the service hierarchy - main_service = service.MultiService() - engine_service.setServiceParent(main_service) - tub_service = Tub() - tub_service.setServiceParent(main_service) - # This needs to be called before the connection is initiated - main_service.startService() - - # This initiates the connection to the controller and calls - # register_engine to tell the controller we are ready to do work - engine_connector = EngineConnector(tub_service) - furl_file = kernel_config['engine']['furl_file'] - log.msg("Using furl file: %s" % furl_file) - - def call_connect(engine_service, furl_file): - d = engine_connector.connect_to_controller(engine_service, furl_file) - def handle_error(f): - # If this print statement is replaced by a log.err(f) I get - # an unhandled error, which makes no sense. I shouldn't have - # to use a print statement here. My only thought is that - # at the beginning of the process the logging is still starting up - print "error connecting to controller:", f.getErrorMessage() - reactor.callLater(0.1, reactor.stop) - d.addErrback(handle_error) - - reactor.callWhenRunning(call_connect, engine_service, furl_file) - reactor.run() - - -def init_config(): - """ - Initialize the configuration using default and command line options. - """ - - parser = OptionParser("""ipengine [options] - -Start an IPython engine. - -Use the IPYTHONDIR environment variable to change your IPython directory -from the default of .ipython or _ipython. The log and security -subdirectories of your IPython directory will be used by this script -for log files and security files.""") - - parser.add_option( - "--furl-file", - type="string", - dest="furl_file", - help="The filename containing the FURL of the controller" - ) - parser.add_option( - "--mpi", - type="string", - dest="mpi", - help="How to enable MPI (mpi4py, pytrilinos, or empty string to disable)" - ) - parser.add_option( - "-l", - "--logfile", - type="string", - dest="logfile", - help="log file name (default is stdout)" - ) - - (options, args) = parser.parse_args() - - kernel_config = kernel_config_manager.get_config_obj() - # Now override with command line options - if options.furl_file is not None: - kernel_config['engine']['furl_file'] = options.furl_file - if options.logfile is not None: - kernel_config['engine']['logfile'] = options.logfile - if options.mpi is not None: - kernel_config['mpi']['default'] = options.mpi - - -def main(): - """ - After creating the configuration information, start the engine. - """ - init_config() - start_engine() - - -if __name__ == "__main__": - main() diff --git a/IPython/kernel/task.py b/IPython/kernel/task.py index 79a691b..924d052 100644 --- a/IPython/kernel/task.py +++ b/IPython/kernel/task.py @@ -414,7 +414,7 @@ class ResultNS(object): This can be a bad idea, as it may corrupt standard behavior of the ns object. - Example + Examples -------- >>> ns = ResultNS({'a':17,'foo':range(3)}) diff --git a/IPython/kernel/tests/engineservicetest.py b/IPython/kernel/tests/engineservicetest.py index ebb0587..a107f13 100644 --- a/IPython/kernel/tests/engineservicetest.py +++ b/IPython/kernel/tests/engineservicetest.py @@ -359,12 +359,15 @@ class IEnginePropertiesTestCase(object): return d def testStrictDict(self): - s = """from IPython.kernel.engineservice import get_engine -p = get_engine(%s).properties"""%self.engine.id + s = """from IPython.kernel.engineservice import get_engine; p = get_engine(%s).properties"""%self.engine.id d = self.engine.execute(s) + # These 3 lines cause a weird testing error on some platforms (OS X). + # I am leaving them here in case they are masking some really + # weird reactor issue. For now I will just keep my eye on this. d.addCallback(lambda r: self.engine.execute("p['a'] = lambda _:None")) d.addErrback(lambda f: self.assertRaises(error.InvalidProperty, f.raiseException)) + # Below here seems to be fine d.addCallback(lambda r: self.engine.execute("p['a'] = range(5)")) d.addCallback(lambda r: self.engine.execute("p['a'].append(5)")) d.addCallback(lambda r: self.engine.get_properties('a')) diff --git a/IPython/kernel/twistedutil.py b/IPython/kernel/twistedutil.py index 33dc429..712a83b 100644 --- a/IPython/kernel/twistedutil.py +++ b/IPython/kernel/twistedutil.py @@ -3,18 +3,16 @@ """Things directly related to all of twisted.""" -__docformat__ = "restructuredtext en" - -#------------------------------------------------------------------------------- -# Copyright (C) 2008 The IPython Development Team +#----------------------------------------------------------------------------- +# Copyright (C) 2008-2009 The IPython Development Team # # Distributed under the terms of the BSD License. The full license is in # the file COPYING, distributed as part of this software. -#------------------------------------------------------------------------------- +#----------------------------------------------------------------------------- -#------------------------------------------------------------------------------- +#----------------------------------------------------------------------------- # Imports -#------------------------------------------------------------------------------- +#----------------------------------------------------------------------------- import os, sys import threading, Queue, atexit @@ -25,9 +23,9 @@ from twisted.python import log, failure from IPython.kernel.error import FileTimeoutError -#------------------------------------------------------------------------------- +#----------------------------------------------------------------------------- # Classes related to twisted and threads -#------------------------------------------------------------------------------- +#----------------------------------------------------------------------------- class ReactorInThread(threading.Thread): @@ -42,6 +40,15 @@ class ReactorInThread(threading.Thread): """ def run(self): + """Run the twisted reactor in a thread. + + This runs the reactor with installSignalHandlers=0, which prevents + twisted from installing any of its own signal handlers. This needs to + be disabled because signal.signal can't be called in a thread. The + only problem with this is that SIGCHLD events won't be detected so + spawnProcess won't detect that its processes have been killed by + an external factor. + """ reactor.run(installSignalHandlers=0) # self.join() @@ -247,3 +254,21 @@ def wait_for_file(filename, delay=0.1, max_tries=10): _test_for_file(filename) return d + + +def sleep_deferred(seconds): + """Sleep without blocking the event loop.""" + d = defer.Deferred() + reactor.callLater(seconds, d.callback, seconds) + return d + + +def make_deferred(func): + """A decorator that calls a function with :func`maybeDeferred`.""" + + def _wrapper(*args, **kwargs): + return defer.maybeDeferred(func, *args, **kwargs) + + return _wrapper + + diff --git a/IPython/kernel/winhpcjob.py b/IPython/kernel/winhpcjob.py new file mode 100644 index 0000000..de1680d --- /dev/null +++ b/IPython/kernel/winhpcjob.py @@ -0,0 +1,318 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +Job and task components for writing .xml files that the Windows HPC Server +2008 can use to start jobs. +""" + +#----------------------------------------------------------------------------- +# Copyright (C) 2008-2009 The IPython Development Team +# +# Distributed under the terms of the BSD License. The full license is in +# the file COPYING, distributed as part of this software. +#----------------------------------------------------------------------------- + +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- + +from __future__ import with_statement + +import os +import re +import uuid + +from xml.etree import ElementTree as ET +from xml.dom import minidom + +from IPython.core.component import Component +from IPython.external import Itpl +from IPython.utils.traitlets import ( + Str, Int, List, Unicode, Instance, + Enum, Bool, CStr +) + +#----------------------------------------------------------------------------- +# Job and Task Component +#----------------------------------------------------------------------------- + + +def as_str(value): + if isinstance(value, str): + return value + elif isinstance(value, bool): + if value: + return 'true' + else: + return 'false' + elif isinstance(value, (int, float)): + return repr(value) + else: + return value + + +def indent(elem, level=0): + i = "\n" + level*" " + if len(elem): + if not elem.text or not elem.text.strip(): + elem.text = i + " " + if not elem.tail or not elem.tail.strip(): + elem.tail = i + for elem in elem: + indent(elem, level+1) + if not elem.tail or not elem.tail.strip(): + elem.tail = i + else: + if level and (not elem.tail or not elem.tail.strip()): + elem.tail = i + + +def find_username(): + domain = os.environ.get('USERDOMAIN') + username = os.environ.get('USERNAME','') + if domain is None: + return username + else: + return '%s\\%s' % (domain, username) + + +class WinHPCJob(Component): + + job_id = Str('') + job_name = Str('MyJob', config=True) + min_cores = Int(1, config=True) + max_cores = Int(1, config=True) + min_sockets = Int(1, config=True) + max_sockets = Int(1, config=True) + min_nodes = Int(1, config=True) + max_nodes = Int(1, config=True) + unit_type = Str("Core", config=True) + auto_calculate_min = Bool(True, config=True) + auto_calculate_max = Bool(True, config=True) + run_until_canceled = Bool(False, config=True) + is_exclusive = Bool(False, config=True) + username = Str(find_username(), config=True) + job_type = Str('Batch', config=True) + priority = Enum(('Lowest','BelowNormal','Normal','AboveNormal','Highest'), + default_value='Highest', config=True) + requested_nodes = Str('', config=True) + project = Str('IPython', config=True) + xmlns = Str('http://schemas.microsoft.com/HPCS2008/scheduler/') + version = Str("2.000") + tasks = List([]) + + @property + def owner(self): + return self.username + + def _write_attr(self, root, attr, key): + s = as_str(getattr(self, attr, '')) + if s: + root.set(key, s) + + def as_element(self): + # We have to add _A_ type things to get the right order than + # the MSFT XML parser expects. + root = ET.Element('Job') + self._write_attr(root, 'version', '_A_Version') + self._write_attr(root, 'job_name', '_B_Name') + self._write_attr(root, 'unit_type', '_C_UnitType') + self._write_attr(root, 'min_cores', '_D_MinCores') + self._write_attr(root, 'max_cores', '_E_MaxCores') + self._write_attr(root, 'min_sockets', '_F_MinSockets') + self._write_attr(root, 'max_sockets', '_G_MaxSockets') + self._write_attr(root, 'min_nodes', '_H_MinNodes') + self._write_attr(root, 'max_nodes', '_I_MaxNodes') + self._write_attr(root, 'run_until_canceled', '_J_RunUntilCanceled') + self._write_attr(root, 'is_exclusive', '_K_IsExclusive') + self._write_attr(root, 'username', '_L_UserName') + self._write_attr(root, 'job_type', '_M_JobType') + self._write_attr(root, 'priority', '_N_Priority') + self._write_attr(root, 'requested_nodes', '_O_RequestedNodes') + self._write_attr(root, 'auto_calculate_max', '_P_AutoCalculateMax') + self._write_attr(root, 'auto_calculate_min', '_Q_AutoCalculateMin') + self._write_attr(root, 'project', '_R_Project') + self._write_attr(root, 'owner', '_S_Owner') + self._write_attr(root, 'xmlns', '_T_xmlns') + dependencies = ET.SubElement(root, "Dependencies") + etasks = ET.SubElement(root, "Tasks") + for t in self.tasks: + etasks.append(t.as_element()) + return root + + def tostring(self): + """Return the string representation of the job description XML.""" + root = self.as_element() + indent(root) + txt = ET.tostring(root, encoding="utf-8") + # Now remove the tokens used to order the attributes. + txt = re.sub(r'_[A-Z]_','',txt) + txt = '\n' + txt + return txt + + def write(self, filename): + """Write the XML job description to a file.""" + txt = self.tostring() + with open(filename, 'w') as f: + f.write(txt) + + def add_task(self, task): + """Add a task to the job. + + Parameters + ---------- + task : :class:`WinHPCTask` + The task object to add. + """ + self.tasks.append(task) + + +class WinHPCTask(Component): + + task_id = Str('') + task_name = Str('') + version = Str("2.000") + min_cores = Int(1, config=True) + max_cores = Int(1, config=True) + min_sockets = Int(1, config=True) + max_sockets = Int(1, config=True) + min_nodes = Int(1, config=True) + max_nodes = Int(1, config=True) + unit_type = Str("Core", config=True) + command_line = CStr('', config=True) + work_directory = CStr('', config=True) + is_rerunnaable = Bool(True, config=True) + std_out_file_path = CStr('', config=True) + std_err_file_path = CStr('', config=True) + is_parametric = Bool(False, config=True) + environment_variables = Instance(dict, args=(), config=True) + + def _write_attr(self, root, attr, key): + s = as_str(getattr(self, attr, '')) + if s: + root.set(key, s) + + def as_element(self): + root = ET.Element('Task') + self._write_attr(root, 'version', '_A_Version') + self._write_attr(root, 'task_name', '_B_Name') + self._write_attr(root, 'min_cores', '_C_MinCores') + self._write_attr(root, 'max_cores', '_D_MaxCores') + self._write_attr(root, 'min_sockets', '_E_MinSockets') + self._write_attr(root, 'max_sockets', '_F_MaxSockets') + self._write_attr(root, 'min_nodes', '_G_MinNodes') + self._write_attr(root, 'max_nodes', '_H_MaxNodes') + self._write_attr(root, 'command_line', '_I_CommandLine') + self._write_attr(root, 'work_directory', '_J_WorkDirectory') + self._write_attr(root, 'is_rerunnaable', '_K_IsRerunnable') + self._write_attr(root, 'std_out_file_path', '_L_StdOutFilePath') + self._write_attr(root, 'std_err_file_path', '_M_StdErrFilePath') + self._write_attr(root, 'is_parametric', '_N_IsParametric') + self._write_attr(root, 'unit_type', '_O_UnitType') + root.append(self.get_env_vars()) + return root + + def get_env_vars(self): + env_vars = ET.Element('EnvironmentVariables') + for k, v in self.environment_variables.items(): + variable = ET.SubElement(env_vars, "Variable") + name = ET.SubElement(variable, "Name") + name.text = k + value = ET.SubElement(variable, "Value") + value.text = v + return env_vars + + + +# By declaring these, we can configure the controller and engine separately! + +class IPControllerJob(WinHPCJob): + job_name = Str('IPController', config=False) + is_exclusive = Bool(False, config=True) + username = Str(find_username(), config=True) + priority = Enum(('Lowest','BelowNormal','Normal','AboveNormal','Highest'), + default_value='Highest', config=True) + requested_nodes = Str('', config=True) + project = Str('IPython', config=True) + + +class IPEngineSetJob(WinHPCJob): + job_name = Str('IPEngineSet', config=False) + is_exclusive = Bool(False, config=True) + username = Str(find_username(), config=True) + priority = Enum(('Lowest','BelowNormal','Normal','AboveNormal','Highest'), + default_value='Highest', config=True) + requested_nodes = Str('', config=True) + project = Str('IPython', config=True) + + +class IPControllerTask(WinHPCTask): + + task_name = Str('IPController', config=True) + controller_cmd = List(['ipcontroller.exe'], config=True) + controller_args = List(['--log-to-file', '--log-level', '40'], config=True) + # I don't want these to be configurable + std_out_file_path = CStr('', config=False) + std_err_file_path = CStr('', config=False) + min_cores = Int(1, config=False) + max_cores = Int(1, config=False) + min_sockets = Int(1, config=False) + max_sockets = Int(1, config=False) + min_nodes = Int(1, config=False) + max_nodes = Int(1, config=False) + unit_type = Str("Core", config=False) + work_directory = CStr('', config=False) + + def __init__(self, parent, name=None, config=None): + super(IPControllerTask, self).__init__(parent, name, config) + the_uuid = uuid.uuid1() + self.std_out_file_path = os.path.join('log','ipcontroller-%s.out' % the_uuid) + self.std_err_file_path = os.path.join('log','ipcontroller-%s.err' % the_uuid) + + @property + def command_line(self): + return ' '.join(self.controller_cmd + self.controller_args) + + +class IPEngineTask(WinHPCTask): + + task_name = Str('IPEngine', config=True) + engine_cmd = List(['ipengine.exe'], config=True) + engine_args = List(['--log-to-file', '--log-level', '40'], config=True) + # I don't want these to be configurable + std_out_file_path = CStr('', config=False) + std_err_file_path = CStr('', config=False) + min_cores = Int(1, config=False) + max_cores = Int(1, config=False) + min_sockets = Int(1, config=False) + max_sockets = Int(1, config=False) + min_nodes = Int(1, config=False) + max_nodes = Int(1, config=False) + unit_type = Str("Core", config=False) + work_directory = CStr('', config=False) + + def __init__(self, parent, name=None, config=None): + super(IPEngineTask,self).__init__(parent, name, config) + the_uuid = uuid.uuid1() + self.std_out_file_path = os.path.join('log','ipengine-%s.out' % the_uuid) + self.std_err_file_path = os.path.join('log','ipengine-%s.err' % the_uuid) + + @property + def command_line(self): + return ' '.join(self.engine_cmd + self.engine_args) + + +# j = WinHPCJob(None) +# j.job_name = 'IPCluster' +# j.username = 'GNET\\bgranger' +# j.requested_nodes = 'GREEN' +# +# t = WinHPCTask(None) +# t.task_name = 'Controller' +# t.command_line = r"\\blue\domainusers$\bgranger\Python\Python25\Scripts\ipcontroller.exe --log-to-file -p default --log-level 10" +# t.work_directory = r"\\blue\domainusers$\bgranger\.ipython\cluster_default" +# t.std_out_file_path = 'controller-out.txt' +# t.std_err_file_path = 'controller-err.txt' +# t.environment_variables['PYTHONPATH'] = r"\\blue\domainusers$\bgranger\Python\Python25\Lib\site-packages" +# j.add_task(t) + diff --git a/IPython/lib/__init__.py b/IPython/lib/__init__.py new file mode 100644 index 0000000..647be0f --- /dev/null +++ b/IPython/lib/__init__.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +Extra capabilities for IPython +""" + +#----------------------------------------------------------------------------- +# Copyright (C) 2008-2009 The IPython Development Team +# +# Distributed under the terms of the BSD License. The full license is in +# the file COPYING, distributed as part of this software. +#----------------------------------------------------------------------------- + +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- + +from IPython.lib.inputhook import ( + enable_wx, disable_wx, + enable_gtk, disable_gtk, + enable_qt4, disable_qt4, + enable_tk, disable_tk, + set_inputhook, clear_inputhook, + current_gui, spin, + appstart_qt4, appstart_wx, + appstart_gtk, appstart_tk +) + +#----------------------------------------------------------------------------- +# Code +#----------------------------------------------------------------------------- \ No newline at end of file diff --git a/IPython/background_jobs.py b/IPython/lib/backgroundjobs.py similarity index 98% rename from IPython/background_jobs.py rename to IPython/lib/backgroundjobs.py index c5998c3..6f49522 100644 --- a/IPython/background_jobs.py +++ b/IPython/lib/backgroundjobs.py @@ -30,8 +30,8 @@ separate implementation). import sys import threading -from IPython.ultraTB import AutoFormattedTB -from IPython.genutils import warn,error +from IPython.core.ultratb import AutoFormattedTB +from IPython.utils.genutils import warn,error class BackgroundJobManager: """Class to manage a pool of backgrounded threaded jobs. diff --git a/IPython/clipboard.py b/IPython/lib/clipboard.py similarity index 97% rename from IPython/clipboard.py rename to IPython/lib/clipboard.py index 6607f6e..f42b93b 100644 --- a/IPython/clipboard.py +++ b/IPython/lib/clipboard.py @@ -4,7 +4,7 @@ import subprocess import sys -from IPython.ipapi import TryNext +from IPython.core.error import TryNext def win32_clipboard_get(): diff --git a/IPython/deep_reload.py b/IPython/lib/deepreload.py similarity index 96% rename from IPython/deep_reload.py rename to IPython/lib/deepreload.py index c8685ba..f2ea5f7 100644 --- a/IPython/deep_reload.py +++ b/IPython/lib/deepreload.py @@ -2,14 +2,14 @@ """ A module to change reload() so that it acts recursively. To enable it type: - >>> import __builtin__, deep_reload - >>> __builtin__.reload = deep_reload.reload + >>> import __builtin__, deepreload + >>> __builtin__.reload = deepreload.reload You can then disable it with: - >>> __builtin__.reload = deep_reload.original_reload + >>> __builtin__.reload = deepreload.original_reload Alternatively, you can add a dreload builtin alongside normal reload with: - >>> __builtin__.dreload = deep_reload.reload + >>> __builtin__.dreload = deepreload.reload This code is almost entirely based on knee.py from the standard library. """ diff --git a/IPython/demo.py b/IPython/lib/demo.py similarity index 98% rename from IPython/demo.py rename to IPython/lib/demo.py index e889502..d5989e6 100644 --- a/IPython/demo.py +++ b/IPython/lib/demo.py @@ -175,8 +175,8 @@ import re import shlex import sys -from IPython.PyColorize import Parser -from IPython.genutils import marquee, file_read, file_readlines, Term +from IPython.utils.PyColorize import Parser +from IPython.utils.genutils import marquee, file_read, file_readlines, Term __all__ = ['Demo','IPythonDemo','LineDemo','IPythonLineDemo','DemoError'] @@ -543,8 +543,8 @@ class ClearMixin(object): """Method called before executing each block. This one simply clears the screen.""" - import IPython.platutils - IPython.platutils.term_clear() + from IPython.utils.platutils import term_clear + term_clear() class ClearDemo(ClearMixin,Demo): pass diff --git a/IPython/lib/inputhook.py b/IPython/lib/inputhook.py new file mode 100755 index 0000000..0043379 --- /dev/null +++ b/IPython/lib/inputhook.py @@ -0,0 +1,525 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +Inputhook management for GUI event loop integration. +""" + +#----------------------------------------------------------------------------- +# Copyright (C) 2008-2009 The IPython Development Team +# +# Distributed under the terms of the BSD License. The full license is in +# the file COPYING, distributed as part of this software. +#----------------------------------------------------------------------------- + +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- + +import ctypes +import sys + +#----------------------------------------------------------------------------- +# Constants +#----------------------------------------------------------------------------- + +# Constants for identifying the GUI toolkits. +GUI_WX = 'wx' +GUI_QT4 = 'qt4' +GUI_GTK = 'gtk' +GUI_TK = 'tk' + +#----------------------------------------------------------------------------- +# Utility classes +#----------------------------------------------------------------------------- + + +class _DummyMainloop(object): + """A special manager to hijack GUI mainloops that is mostly a no-op. + + We are not using this class currently as it breaks GUI code that calls + a mainloop function after the app has started to process pending events. + """ + def __init__(self, ml, ihm, gui_type): + self.ml = ml + self.ihm = ihm + self.gui_type = gui_type + + def __call__(self, *args, **kw): + if self.ihm.current_gui() == self.gui_type: + pass + else: + self.ml(*args, **kw) + + +#----------------------------------------------------------------------------- +# Appstart and spin functions +#----------------------------------------------------------------------------- + + +def appstart_qt4(app): + """Start the qt4 event loop in a way that plays with IPython. + + When a qt4 app is run interactively in IPython, the event loop should + not be started. This function checks to see if IPython's qt4 integration + is activated and if so, it passes. If not, it will call the :meth:`exec_` + method of the main qt4 app. + + This function should be used by users who want their qt4 scripts to work + both at the command line and in IPython. These users should put the + following logic at the bottom on their script, after they create a + :class:`QApplication` instance (called ``app`` here):: + + try: + from IPython.lib.inputhook import appstart_qt4 + appstart_qt4(app) + except ImportError: + app.exec_() + """ + from PyQt4 import QtCore, QtGui + + assert isinstance(app, QtCore.QCoreApplication) + if app is not None: + if current_gui() == GUI_QT4: + pass + else: + app.exec_() + + +def appstart_wx(app): + """Start the wx event loop in a way that plays with IPython. + + When a wx app is run interactively in IPython, the event loop should + not be started. This function checks to see if IPython's wx integration + is activated and if so, it passes. If not, it will call the + :meth:`MainLoop` method of the main qt4 app. + + This function should be used by users who want their wx scripts to work + both at the command line and in IPython. These users should put the + following logic at the bottom on their script, after they create a + :class:`App` instance (called ``app`` here):: + + try: + from IPython.lib.inputhook import appstart_wx + appstart_wx(app) + except ImportError: + app.MainLoop() + """ + import wx + + assert isinstance(app, wx.App) + if app is not None: + if current_gui() == GUI_WX: + pass + else: + app.MainLoop() + + +def appstart_tk(app): + """Start the tk event loop in a way that plays with IPython. + + When a tk app is run interactively in IPython, the event loop should + not be started. This function checks to see if IPython's tk integration + is activated and if so, it passes. If not, it will call the + :meth:`mainloop` method of the tk object passed to this method. + + This function should be used by users who want their tk scripts to work + both at the command line and in IPython. These users should put the + following logic at the bottom on their script, after they create a + :class:`Tk` instance (called ``app`` here):: + + try: + from IPython.lib.inputhook import appstart_tk + appstart_tk(app) + except ImportError: + app.mainloop() + """ + if app is not None: + if current_gui() == GUI_TK: + pass + else: + app.mainloop() + +def appstart_gtk(): + """Start the gtk event loop in a way that plays with IPython. + + When a gtk app is run interactively in IPython, the event loop should + not be started. This function checks to see if IPython's gtk integration + is activated and if so, it passes. If not, it will call + :func:`gtk.main`. Unlike the other appstart implementations, this does + not take an ``app`` argument. + + This function should be used by users who want their gtk scripts to work + both at the command line and in IPython. These users should put the + following logic at the bottom on their script:: + + try: + from IPython.lib.inputhook import appstart_gtk + appstart_gtk() + except ImportError: + gtk.main() + """ + import gtk + if current_gui() == GUI_GTK: + pass + else: + gtk.main() + +#----------------------------------------------------------------------------- +# Main InputHookManager class +#----------------------------------------------------------------------------- + + +class InputHookManager(object): + """Manage PyOS_InputHook for different GUI toolkits. + + This class installs various hooks under ``PyOSInputHook`` to handle + GUI event loop integration. + """ + + def __init__(self): + self.PYFUNC = ctypes.PYFUNCTYPE(ctypes.c_int) + self._apps = {} + self._spinner_dict = { + GUI_QT4 : self._spin_qt4, + GUI_WX : self._spin_wx, + GUI_GTK : self._spin_gtk, + GUI_TK : self._spin_tk} + self._reset() + + def _reset(self): + self._callback_pyfunctype = None + self._callback = None + self._installed = False + self._current_gui = None + + def _hijack_wx(self): + """Hijack the wx mainloop so a user calling it won't cause badness. + + We are not currently using this as it breaks GUI code that calls a + mainloop at anytime but startup. + """ + import wx + if hasattr(wx, '_core_'): core = getattr(wx, '_core_') + elif hasattr(wx, '_core'): core = getattr(wx, '_core') + else: raise AttributeError('Could not find wx core module') + orig_mainloop = core.PyApp_MainLoop + core.PyApp_MainLoop = _DummyMainloop + return orig_mainloop + + def _hijack_qt4(self): + """Hijack the qt4 mainloop so a user calling it won't cause badness. + + We are not currently using this as it breaks GUI code that calls a + mainloop at anytime but startup. + """ + from PyQt4 import QtGui, QtCore + orig_mainloop = QtGui.qApp.exec_ + dumb_ml = _DummyMainloop(orig_mainloop, self, GUI_QT4) + QtGui.qApp.exec_ = dumb_ml + QtGui.QApplication.exec_ = dumb_ml + QtCore.QCoreApplication.exec_ = dumb_ml + return orig_mainloop + + def _hijack_gtk(self): + """Hijack the gtk mainloop so a user calling it won't cause badness. + + We are not currently using this as it breaks GUI code that calls a + mainloop at anytime but startup. + """ + import gtk + orig_mainloop = gtk.main + dumb_ml = _DummyMainloop(orig_mainloop, self, GUI_GTK) + gtk.mainloop = dumb_ml + gtk.main = dumb_ml + return orig_mainloop + + def _hijack_tk(self): + """Hijack the tk mainloop so a user calling it won't cause badness. + + We are not currently using this as it breaks GUI code that calls a + mainloop at anytime but startup. + """ + import Tkinter + orig_mainloop = gtk.main + dumb_ml = _DummyMainloop(orig_mainloop, self, GUI_TK) + Tkinter.Misc.mainloop = dumb_ml + Tkinter.mainloop = dumb_ml + + def _spin_qt4(self): + """Process all pending events in the qt4 event loop. + + This is for internal IPython use only and user code should not call this. + Instead, they should issue the raw GUI calls themselves. + """ + from PyQt4 import QtCore, QtGui + + app = QtCore.QCoreApplication.instance() + if app is not None: + QtCore.QCoreApplication.processEvents(QtCore.QEventLoop.AllEvents) + + def _spin_wx(self): + """Process all pending events in the wx event loop. + + This is for internal IPython use only and user code should not call this. + Instead, they should issue the raw GUI calls themselves. + """ + import wx + app = wx.GetApp() + if app is not None and wx.Thread_IsMain(): + evtloop = wx.EventLoop() + ea = wx.EventLoopActivator(evtloop) + while evtloop.Pending(): + evtloop.Dispatch() + app.ProcessIdle() + del ea + + def _spin_gtk(self): + """Process all pending events in the gtk event loop. + + This is for internal IPython use only and user code should not call this. + Instead, they should issue the raw GUI calls themselves. + """ + import gtk + gtk.gdk.threads_enter() + while gtk.events_pending(): + gtk.main_iteration(False) + gtk.gdk.flush() + gtk.gdk.threads_leave() + + def _spin_tk(self): + """Process all pending events in the tk event loop. + + This is for internal IPython use only and user code should not call this. + Instead, they should issue the raw GUI calls themselves. + """ + app = self._apps.get(GUI_TK) + if app is not None: + app.update() + + def spin(self): + """Process pending events in the current gui. + + This method is just provided for IPython to use internally if needed + for things like testing. Third party projects should not call this + method, but instead should call the underlying GUI toolkit methods + that we are calling. + """ + spinner = self._spinner_dict.get(self._current_gui, lambda: None) + spinner() + + def get_pyos_inputhook(self): + """Return the current PyOS_InputHook as a ctypes.c_void_p.""" + return ctypes.c_void_p.in_dll(ctypes.pythonapi,"PyOS_InputHook") + + def get_pyos_inputhook_as_func(self): + """Return the current PyOS_InputHook as a ctypes.PYFUNCYPE.""" + return self.PYFUNC.in_dll(ctypes.pythonapi,"PyOS_InputHook") + + def set_inputhook(self, callback): + """Set PyOS_InputHook to callback and return the previous one.""" + self._callback = callback + self._callback_pyfunctype = self.PYFUNC(callback) + pyos_inputhook_ptr = self.get_pyos_inputhook() + original = self.get_pyos_inputhook_as_func() + pyos_inputhook_ptr.value = \ + ctypes.cast(self._callback_pyfunctype, ctypes.c_void_p).value + self._installed = True + return original + + def clear_inputhook(self): + """Set PyOS_InputHook to NULL and return the previous one.""" + pyos_inputhook_ptr = self.get_pyos_inputhook() + original = self.get_pyos_inputhook_as_func() + pyos_inputhook_ptr.value = ctypes.c_void_p(None).value + self._reset() + return original + + def clear_app_refs(self, gui=None): + """Clear IPython's internal reference to an application instance. + + Whenever we create an app for a user on qt4 or wx, we hold a + reference to the app. This is needed because in some cases bad things + can happen if a user doesn't hold a reference themselves. This + method is provided to clear the references we are holding. + + Parameters + ---------- + gui : None or str + If None, clear all app references. If ('wx', 'qt4') clear + the app for that toolkit. References are not held for gtk or tk + as those toolkits don't have the notion of an app. + """ + if gui is None: + self._apps = {} + elif self._apps.has_key(gui): + del self._apps[gui] + + def enable_wx(self, app=False): + """Enable event loop integration with wxPython. + + Parameters + ---------- + app : bool + Create a running application object or not. + + Notes + ----- + This methods sets the ``PyOS_InputHook`` for wxPython, which allows + the wxPython to integrate with terminal based applications like + IPython. + + If ``app`` is True, we create an :class:`wx.App` as follows:: + + import wx + app = wx.App(redirect=False, clearSigInt=False) + + Both options this constructor are important for things to work + properly in an interactive context. + + But, we first check to see if an application has already been + created. If so, we simply return that instance. + """ + from IPython.lib.inputhookwx import inputhook_wx + self.set_inputhook(inputhook_wx) + self._current_gui = GUI_WX + if app: + import wx + app = wx.GetApp() + if app is None: + app = wx.App(redirect=False, clearSigInt=False) + self._apps[GUI_WX] = app + return app + + def disable_wx(self): + """Disable event loop integration with wxPython. + + This merely sets PyOS_InputHook to NULL. + """ + self.clear_inputhook() + + def enable_qt4(self, app=False): + """Enable event loop integration with PyQt4. + + Parameters + ---------- + app : bool + Create a running application object or not. + + Notes + ----- + This methods sets the PyOS_InputHook for PyQt4, which allows + the PyQt4 to integrate with terminal based applications like + IPython. + + If ``app`` is True, we create an :class:`QApplication` as follows:: + + from PyQt4 import QtCore + app = QtGui.QApplication(sys.argv) + + But, we first check to see if an application has already been + created. If so, we simply return that instance. + """ + from PyQt4 import QtCore + # PyQt4 has had this since 4.3.1. In version 4.2, PyOS_InputHook + # was set when QtCore was imported, but if it ever got removed, + # you couldn't reset it. For earlier versions we can + # probably implement a ctypes version. + try: + QtCore.pyqtRestoreInputHook() + except AttributeError: + pass + self._current_gui = GUI_QT4 + if app: + from PyQt4 import QtGui + app = QtCore.QCoreApplication.instance() + if app is None: + app = QtGui.QApplication(sys.argv) + self._apps[GUI_QT4] = app + return app + + def disable_qt4(self): + """Disable event loop integration with PyQt4. + + This merely sets PyOS_InputHook to NULL. + """ + self.clear_inputhook() + + def enable_gtk(self, app=False): + """Enable event loop integration with PyGTK. + + Parameters + ---------- + app : bool + Create a running application object or not. Because gtk does't + have an app class, this does nothing. + + Notes + ----- + This methods sets the PyOS_InputHook for PyGTK, which allows + the PyGTK to integrate with terminal based applications like + IPython. + """ + import gtk + try: + gtk.set_interactive(True) + self._current_gui = GUI_GTK + except AttributeError: + # For older versions of gtk, use our own ctypes version + from IPython.lib.inputhookgtk import inputhook_gtk + self.set_inputhook(inputhook_gtk) + self._current_gui = GUI_GTK + + def disable_gtk(self): + """Disable event loop integration with PyGTK. + + This merely sets PyOS_InputHook to NULL. + """ + self.clear_inputhook() + + def enable_tk(self, app=False): + """Enable event loop integration with Tk. + + Parameters + ---------- + app : bool + Create a running application object or not. + + Notes + ----- + Currently this is a no-op as creating a :class:`Tkinter.Tk` object + sets ``PyOS_InputHook``. + """ + self._current_gui = GUI_TK + if app: + import Tkinter + app = Tkinter.Tk() + app.withdraw() + self._apps[GUI_TK] = app + return app + + def disable_tk(self): + """Disable event loop integration with Tkinter. + + This merely sets PyOS_InputHook to NULL. + """ + self.clear_inputhook() + + def current_gui(self): + """Return a string indicating the currently active GUI or None.""" + return self._current_gui + +inputhook_manager = InputHookManager() + +enable_wx = inputhook_manager.enable_wx +disable_wx = inputhook_manager.disable_wx +enable_qt4 = inputhook_manager.enable_qt4 +disable_qt4 = inputhook_manager.disable_qt4 +enable_gtk = inputhook_manager.enable_gtk +disable_gtk = inputhook_manager.disable_gtk +enable_tk = inputhook_manager.enable_tk +disable_tk = inputhook_manager.disable_tk +clear_inputhook = inputhook_manager.clear_inputhook +set_inputhook = inputhook_manager.set_inputhook +current_gui = inputhook_manager.current_gui +clear_app_refs = inputhook_manager.clear_app_refs +spin = inputhook_manager.spin diff --git a/IPython/lib/inputhookgtk.py b/IPython/lib/inputhookgtk.py new file mode 100644 index 0000000..782a65c --- /dev/null +++ b/IPython/lib/inputhookgtk.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +Enable pygtk to be used interacive by setting PyOS_InputHook. + +Authors: Brian Granger +""" + +#----------------------------------------------------------------------------- +# Copyright (C) 2008-2009 The IPython Development Team +# +# Distributed under the terms of the BSD License. The full license is in +# the file COPYING, distributed as part of this software. +#----------------------------------------------------------------------------- + +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- + +import sys +import gtk, gobject + +#----------------------------------------------------------------------------- +# Code +#----------------------------------------------------------------------------- + + +def _main_quit(*args, **kwargs): + gtk.main_quit() + return False + +def inputhook_gtk(): + gobject.io_add_watch(sys.stdin, gobject.IO_IN, _main_quit) + gtk.main() + return 0 + diff --git a/IPython/lib/inputhookwx.py b/IPython/lib/inputhookwx.py new file mode 100644 index 0000000..e79b3ce --- /dev/null +++ b/IPython/lib/inputhookwx.py @@ -0,0 +1,179 @@ +#!/usr/bin/env python +# encoding: utf-8 + +""" +Enable wxPython to be used interacive by setting PyOS_InputHook. + +Authors: Robin Dunn, Brian Granger, Ondrej Certik +""" + +#----------------------------------------------------------------------------- +# Copyright (C) 2008-2009 The IPython Development Team +# +# Distributed under the terms of the BSD License. The full license is in +# the file COPYING, distributed as part of this software. +#----------------------------------------------------------------------------- + +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- + +import os +import signal +import sys +import time +from timeit import default_timer as clock +import wx + +if os.name == 'posix': + import select +elif sys.platform == 'win32': + import msvcrt + +#----------------------------------------------------------------------------- +# Code +#----------------------------------------------------------------------------- + +def stdin_ready(): + if os.name == 'posix': + infds, outfds, erfds = select.select([sys.stdin],[],[],0) + if infds: + return True + else: + return False + elif sys.platform == 'win32': + return msvcrt.kbhit() + + +def inputhook_wx1(): + """Run the wx event loop by processing pending events only. + + This approach seems to work, but its performance is not great as it + relies on having PyOS_InputHook called regularly. + """ + try: + app = wx.GetApp() + if app is not None: + assert wx.Thread_IsMain() + + # Make a temporary event loop and process system events until + # there are no more waiting, then allow idle events (which + # will also deal with pending or posted wx events.) + evtloop = wx.EventLoop() + ea = wx.EventLoopActivator(evtloop) + while evtloop.Pending(): + evtloop.Dispatch() + app.ProcessIdle() + del ea + except KeyboardInterrupt: + pass + return 0 + +class EventLoopTimer(wx.Timer): + + def __init__(self, func): + self.func = func + wx.Timer.__init__(self) + + def Notify(self): + self.func() + +class EventLoopRunner(object): + + def Run(self, time): + self.evtloop = wx.EventLoop() + self.timer = EventLoopTimer(self.check_stdin) + self.timer.Start(time) + self.evtloop.Run() + + def check_stdin(self): + if stdin_ready(): + self.timer.Stop() + self.evtloop.Exit() + +def inputhook_wx2(): + """Run the wx event loop, polling for stdin. + + This version runs the wx eventloop for an undetermined amount of time, + during which it periodically checks to see if anything is ready on + stdin. If anything is ready on stdin, the event loop exits. + + The argument to elr.Run controls how often the event loop looks at stdin. + This determines the responsiveness at the keyboard. A setting of 1000 + enables a user to type at most 1 char per second. I have found that a + setting of 10 gives good keyboard response. We can shorten it further, + but eventually performance would suffer from calling select/kbhit too + often. + """ + try: + app = wx.GetApp() + if app is not None: + assert wx.Thread_IsMain() + elr = EventLoopRunner() + # As this time is made shorter, keyboard response improves, but idle + # CPU load goes up. 10 ms seems like a good compromise. + elr.Run(time=10) # CHANGE time here to control polling interval + except KeyboardInterrupt: + pass + return 0 + +def inputhook_wx3(): + """Run the wx event loop by processing pending events only. + + This is like inputhook_wx1, but it keeps processing pending events + until stdin is ready. After processing all pending events, a call to + time.sleep is inserted. This is needed, otherwise, CPU usage is at 100%. + This sleep time should be tuned though for best performance. + """ + # We need to protect against a user pressing Control-C when IPython is + # idle and this is running. We trap KeyboardInterrupt and pass. + try: + app = wx.GetApp() + if app is not None: + assert wx.Thread_IsMain() + + # The import of wx on Linux sets the handler for signal.SIGINT + # to 0. This is a bug in wx or gtk. We fix by just setting it + # back to the Python default. + if not callable(signal.getsignal(signal.SIGINT)): + signal.signal(signal.SIGINT, signal.default_int_handler) + + evtloop = wx.EventLoop() + ea = wx.EventLoopActivator(evtloop) + t = clock() + while not stdin_ready(): + while evtloop.Pending(): + t = clock() + evtloop.Dispatch() + app.ProcessIdle() + # We need to sleep at this point to keep the idle CPU load + # low. However, if sleep to long, GUI response is poor. As + # a compromise, we watch how often GUI events are being processed + # and switch between a short and long sleep time. Here are some + # stats useful in helping to tune this. + # time CPU load + # 0.001 13% + # 0.005 3% + # 0.01 1.5% + # 0.05 0.5% + used_time = clock() - t + if used_time > 5*60.0: + # print 'Sleep for 5 s' # dbg + time.sleep(5.0) + elif used_time > 10.0: + # print 'Sleep for 1 s' # dbg + time.sleep(1.0) + elif used_time > 0.1: + # Few GUI events coming in, so we can sleep longer + # print 'Sleep for 0.05 s' # dbg + time.sleep(0.05) + else: + # Many GUI events coming in, so sleep only very little + time.sleep(0.001) + del ea + except KeyboardInterrupt: + pass + return 0 + +# This is our default implementation +inputhook_wx = inputhook_wx3 diff --git a/IPython/irunner.py b/IPython/lib/irunner.py similarity index 99% rename from IPython/irunner.py rename to IPython/lib/irunner.py index 7539802..5b8162f 100644 --- a/IPython/irunner.py +++ b/IPython/lib/irunner.py @@ -222,7 +222,7 @@ class InteractiveRunner(object): write(cmd) continue - #write('AFTER: '+c.after) # dbg + # write('AFTER: '+c.after) # dbg write(c.after) c.send(cmd) try: @@ -303,11 +303,11 @@ class IPythonRunner(InteractiveRunner): def __init__(self,program = 'ipython',args=None,out=sys.stdout,echo=True): """New runner, optionally passing the ipython command to use.""" - args0 = ['-colors','NoColor', + args0 = ['--colors','NoColor', '-pi1','In [\\#]: ', '-pi2',' .\\D.: ', - '-noterm_title', - '-noautoindent'] + '--noterm-title', + '--no-auto-indent'] if args is None: args = args0 else: args = args0 + args prompts = [r'In \[\d+\]: ',r' \.*: '] diff --git a/IPython/lib/tests/__init__.py b/IPython/lib/tests/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/IPython/lib/tests/__init__.py diff --git a/IPython/lib/tests/test_imports.py b/IPython/lib/tests/test_imports.py new file mode 100644 index 0000000..e0e9e9e --- /dev/null +++ b/IPython/lib/tests/test_imports.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python +# encoding: utf-8 + +def test_import_backgroundjobs(): + from IPython.lib import backgroundjobs + +def test_import_deepreload(): + from IPython.lib import deepreload + +def test_import_demo(): + from IPython.lib import demo + +def test_import_irunner(): + from IPython.lib import demo diff --git a/IPython/prefilter.py b/IPython/prefilter.py deleted file mode 100644 index 44ab9c7..0000000 --- a/IPython/prefilter.py +++ /dev/null @@ -1,320 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Classes and functions for prefiltering (transforming) a line of user input. -This module is responsible, primarily, for breaking the line up into useful -pieces and triggering the appropriate handlers in iplib to do the actual -transforming work. -""" -__docformat__ = "restructuredtext en" - -import re -import IPython.ipapi - -class LineInfo(object): - """A single line of input and associated info. - - Includes the following as properties: - - line - The original, raw line - - continue_prompt - Is this line a continuation in a sequence of multiline input? - - pre - The initial esc character or whitespace. - - preChar - The escape character(s) in pre or the empty string if there isn't one. - Note that '!!' is a possible value for preChar. Otherwise it will - always be a single character. - - preWhitespace - The leading whitespace from pre if it exists. If there is a preChar, - this is just ''. - - iFun - The 'function part', which is basically the maximal initial sequence - of valid python identifiers and the '.' character. This is what is - checked for alias and magic transformations, used for auto-calling, - etc. - - theRest - Everything else on the line. - """ - def __init__(self, line, continue_prompt): - self.line = line - self.continue_prompt = continue_prompt - self.pre, self.iFun, self.theRest = splitUserInput(line) - - self.preChar = self.pre.strip() - if self.preChar: - self.preWhitespace = '' # No whitespace allowd before esc chars - else: - self.preWhitespace = self.pre - - self._oinfo = None - - def ofind(self, ip): - """Do a full, attribute-walking lookup of the iFun in the various - namespaces for the given IPython InteractiveShell instance. - - Return a dict with keys: found,obj,ospace,ismagic - - Note: can cause state changes because of calling getattr, but should - only be run if autocall is on and if the line hasn't matched any - other, less dangerous handlers. - - Does cache the results of the call, so can be called multiple times - without worrying about *further* damaging state. - """ - if not self._oinfo: - self._oinfo = ip._ofind(self.iFun) - return self._oinfo - def __str__(self): - return "Lineinfo [%s|%s|%s]" %(self.pre,self.iFun,self.theRest) - -def splitUserInput(line, pattern=None): - """Split user input into pre-char/whitespace, function part and rest. - - Mostly internal to this module, but also used by iplib.expand_aliases, - which passes in a shell pattern. - """ - # It seems to me that the shell splitting should be a separate method. - - if not pattern: - pattern = line_split - match = pattern.match(line) - if not match: - #print "match failed for line '%s'" % line - try: - iFun,theRest = line.split(None,1) - except ValueError: - #print "split failed for line '%s'" % line - iFun,theRest = line,'' - pre = re.match('^(\s*)(.*)',line).groups()[0] - else: - pre,iFun,theRest = match.groups() - - # iFun has to be a valid python identifier, so it better be only pure - # ascii, no unicode: - try: - iFun = iFun.encode('ascii') - except UnicodeEncodeError: - theRest = iFun + u' ' + theRest - iFun = u'' - - #print 'line:<%s>' % line # dbg - #print 'pre <%s> iFun <%s> rest <%s>' % (pre,iFun.strip(),theRest) # dbg - return pre,iFun.strip(),theRest.lstrip() - - -# RegExp for splitting line contents into pre-char//first word-method//rest. -# For clarity, each group in on one line. - -# WARNING: update the regexp if the escapes in iplib are changed, as they -# are hardwired in. - -# Although it's not solely driven by the regex, note that: -# ,;/% only trigger if they are the first character on the line -# ! and !! trigger if they are first char(s) *or* follow an indent -# ? triggers as first or last char. - -# The three parts of the regex are: -# 1) pre: pre_char *or* initial whitespace -# 2) iFun: first word/method (mix of \w and '.') -# 3) theRest: rest of line (separated from iFun by space if non-empty) -line_split = re.compile(r'^([,;/%?]|!!?|\s*)' - r'\s*([\w\.]+)' - r'(\s+.*$|$)') - -shell_line_split = re.compile(r'^(\s*)(\S*\s*)(.*$)') - -def prefilter(line_info, ip): - """Call one of the passed-in InteractiveShell's handler preprocessors, - depending on the form of the line. Return the results, which must be a - value, even if it's a blank ('').""" - # Note: the order of these checks does matter. - for check in [ checkEmacs, - checkShellEscape, - checkIPyAutocall, - checkMultiLineMagic, - checkEscChars, - checkAssignment, - checkAutomagic, - checkAlias, - checkPythonOps, - checkAutocall, - ]: - handler = check(line_info, ip) - if handler: - return handler(line_info) - - return ip.handle_normal(line_info) - -# Handler checks -# -# All have the same interface: they take a LineInfo object and a ref to the -# iplib.InteractiveShell object. They check the line to see if a particular -# handler should be called, and return either a handler or None. The -# handlers which they return are *bound* methods of the InteractiveShell -# object. -# -# In general, these checks should only take responsibility for their 'own' -# handler. If it doesn't get triggered, they should just return None and -# let the rest of the check sequence run. - -def checkShellEscape(l_info,ip): - if l_info.line.lstrip().startswith(ip.ESC_SHELL): - return ip.handle_shell_escape - -def checkEmacs(l_info,ip): - "Emacs ipython-mode tags certain input lines." - if l_info.line.endswith('# PYTHON-MODE'): - return ip.handle_emacs - else: - return None - -def checkIPyAutocall(l_info,ip): - "Instances of IPyAutocall in user_ns get autocalled immediately" - obj = ip.user_ns.get(l_info.iFun, None) - if isinstance(obj, IPython.ipapi.IPyAutocall): - obj.set_ip(ip.api) - return ip.handle_auto - else: - return None - - -def checkMultiLineMagic(l_info,ip): - "Allow ! and !! in multi-line statements if multi_line_specials is on" - # Note that this one of the only places we check the first character of - # iFun and *not* the preChar. Also note that the below test matches - # both ! and !!. - if l_info.continue_prompt \ - and ip.rc.multi_line_specials: - if l_info.iFun.startswith(ip.ESC_MAGIC): - return ip.handle_magic - else: - return None - -def checkEscChars(l_info,ip): - """Check for escape character and return either a handler to handle it, - or None if there is no escape char.""" - if l_info.line[-1] == ip.ESC_HELP \ - and l_info.preChar != ip.ESC_SHELL \ - and l_info.preChar != ip.ESC_SH_CAP: - # the ? can be at the end, but *not* for either kind of shell escape, - # because a ? can be a vaild final char in a shell cmd - return ip.handle_help - elif l_info.preChar in ip.esc_handlers: - return ip.esc_handlers[l_info.preChar] - else: - return None - - -def checkAssignment(l_info,ip): - """Check to see if user is assigning to a var for the first time, in - which case we want to avoid any sort of automagic / autocall games. - - This allows users to assign to either alias or magic names true python - variables (the magic/alias systems always take second seat to true - python code). E.g. ls='hi', or ls,that=1,2""" - if l_info.theRest and l_info.theRest[0] in '=,': - return ip.handle_normal - else: - return None - - -def checkAutomagic(l_info,ip): - """If the iFun is magic, and automagic is on, run it. Note: normal, - non-auto magic would already have been triggered via '%' in - check_esc_chars. This just checks for automagic. Also, before - triggering the magic handler, make sure that there is nothing in the - user namespace which could shadow it.""" - if not ip.rc.automagic or not hasattr(ip,'magic_'+l_info.iFun): - return None - - # We have a likely magic method. Make sure we should actually call it. - if l_info.continue_prompt and not ip.rc.multi_line_specials: - return None - - head = l_info.iFun.split('.',1)[0] - if isShadowed(head,ip): - return None - - return ip.handle_magic - - -def checkAlias(l_info,ip): - "Check if the initital identifier on the line is an alias." - # Note: aliases can not contain '.' - head = l_info.iFun.split('.',1)[0] - - if l_info.iFun not in ip.alias_table \ - or head not in ip.alias_table \ - or isShadowed(head,ip): - return None - - return ip.handle_alias - - -def checkPythonOps(l_info,ip): - """If the 'rest' of the line begins with a function call or pretty much - any python operator, we should simply execute the line (regardless of - whether or not there's a possible autocall expansion). This avoids - spurious (and very confusing) geattr() accesses.""" - if l_info.theRest and l_info.theRest[0] in '!=()<>,+*/%^&|': - return ip.handle_normal - else: - return None - - -def checkAutocall(l_info,ip): - "Check if the initial word/function is callable and autocall is on." - if not ip.rc.autocall: - return None - - oinfo = l_info.ofind(ip) # This can mutate state via getattr - if not oinfo['found']: - return None - - if callable(oinfo['obj']) \ - and (not re_exclude_auto.match(l_info.theRest)) \ - and re_fun_name.match(l_info.iFun): - #print 'going auto' # dbg - return ip.handle_auto - else: - #print 'was callable?', callable(l_info.oinfo['obj']) # dbg - return None - -# RegExp to identify potential function names -re_fun_name = re.compile(r'[a-zA-Z_]([a-zA-Z0-9_.]*) *$') - -# RegExp to exclude strings with this start from autocalling. In -# particular, all binary operators should be excluded, so that if foo is -# callable, foo OP bar doesn't become foo(OP bar), which is invalid. The -# characters '!=()' don't need to be checked for, as the checkPythonChars -# routine explicitely does so, to catch direct calls and rebindings of -# existing names. - -# Warning: the '-' HAS TO BE AT THE END of the first group, otherwise -# it affects the rest of the group in square brackets. -re_exclude_auto = re.compile(r'^[,&^\|\*/\+-]' - r'|^is |^not |^in |^and |^or ') - -# try to catch also methods for stuff in lists/tuples/dicts: off -# (experimental). For this to work, the line_split regexp would need -# to be modified so it wouldn't break things at '['. That line is -# nasty enough that I shouldn't change it until I can test it _well_. -#self.re_fun_name = re.compile (r'[a-zA-Z_]([a-zA-Z0-9_.\[\]]*) ?$') - -# Handler Check Utilities -def isShadowed(identifier,ip): - """Is the given identifier defined in one of the namespaces which shadow - the alias and magic namespaces? Note that an identifier is different - than iFun, because it can not contain a '.' character.""" - # This is much safer than calling ofind, which can change state - return (identifier in ip.user_ns \ - or identifier in ip.internal_ns \ - or identifier in ip.ns_table['builtin']) - diff --git a/IPython/Extensions/InterpreterExec.py b/IPython/quarantine/InterpreterExec.py similarity index 89% rename from IPython/Extensions/InterpreterExec.py rename to IPython/quarantine/InterpreterExec.py index cca7c24..70bebac 100644 --- a/IPython/Extensions/InterpreterExec.py +++ b/IPython/quarantine/InterpreterExec.py @@ -14,7 +14,7 @@ executed. # the file COPYING, distributed as part of this software. #***************************************************************************** -# TODO: deprecated + def prefilter_shell(self,line,continuation): """Alternate prefilter, modified for shell-like functionality. @@ -43,14 +43,14 @@ def prefilter_shell(self,line,continuation): return self._prefilter(line,continuation) # Rebind this to be the new IPython prefilter: -from IPython.iplib import InteractiveShell +from IPython.core.iplib import InteractiveShell InteractiveShell.prefilter = prefilter_shell # Clean up the namespace. del InteractiveShell,prefilter_shell # Provide pysh and further shell-oriented services import os,sys,shutil -from IPython.genutils import system,shell,getoutput,getoutputerror +from IPython.utils.genutils import system,shell,getoutput,getoutputerror # Short aliases for getting shell output as a string and a list sout = getoutput diff --git a/IPython/Extensions/InterpreterPasteInput.py b/IPython/quarantine/InterpreterPasteInput.py similarity index 98% rename from IPython/Extensions/InterpreterPasteInput.py rename to IPython/quarantine/InterpreterPasteInput.py index d16c911..3948b63 100644 --- a/IPython/Extensions/InterpreterPasteInput.py +++ b/IPython/quarantine/InterpreterPasteInput.py @@ -85,7 +85,7 @@ Authors import re -from IPython.iplib import InteractiveShell +from IPython.core.iplib import InteractiveShell PROMPT_RE = re.compile(r'(^[ \t]*>>> |^[ \t]*\.\.\. )') diff --git a/IPython/quarantine/__init__.py b/IPython/quarantine/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/IPython/quarantine/__init__.py diff --git a/IPython/Extensions/clearcmd.py b/IPython/quarantine/clearcmd.py similarity index 95% rename from IPython/Extensions/clearcmd.py rename to IPython/quarantine/clearcmd.py index 76cfbf7..a87edf7 100644 --- a/IPython/Extensions/clearcmd.py +++ b/IPython/quarantine/clearcmd.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- """ IPython extension: add %clear magic """ -import IPython.ipapi +from IPython.core import ipapi import gc -ip = IPython.ipapi.get() +ip = ipapi.get() def clear_f(self,arg): """ Clear various data (e.g. stored history data) @@ -81,7 +81,7 @@ def clear_f(self,arg): gc.collect() # Activate the extension -ip.expose_magic("clear",clear_f) +ip.define_magic("clear",clear_f) import ipy_completers ipy_completers.quick_completer( '%clear','in out shadow_nuke shadow_compress dhist') diff --git a/IPython/Extensions/envpersist.py b/IPython/quarantine/envpersist.py similarity index 93% rename from IPython/Extensions/envpersist.py rename to IPython/quarantine/envpersist.py index ee4c139..2144c78 100644 --- a/IPython/Extensions/envpersist.py +++ b/IPython/quarantine/envpersist.py @@ -2,8 +2,10 @@ """ %env magic command for storing environment variables persistently """ -import IPython.ipapi -ip = IPython.ipapi.get() +from IPython.core import ipapi +from IPython.core.error import TryNext + +ip = ipapi.get() import os,sys @@ -16,7 +18,7 @@ def restore_env(self): os.environ[k] = os.environ.get(k,"") + v for k,v in env['pre']: os.environ[k] = v + os.environ.get(k,"") - raise IPython.ipapi.TryNext + raise TryNext ip.set_hook('late_startup_hook', restore_env) @@ -85,6 +87,6 @@ def env_completer(self,event): """ Custom completer that lists all env vars """ return os.environ.keys() -ip.expose_magic('env', persist_env) +ip.define_magic('env', persist_env) ip.set_hook('complete_command',env_completer, str_key = '%env') diff --git a/IPython/Extensions/ext_rescapture.py b/IPython/quarantine/ext_rescapture.py similarity index 75% rename from IPython/Extensions/ext_rescapture.py rename to IPython/quarantine/ext_rescapture.py index e02b232..6f5731a 100644 --- a/IPython/Extensions/ext_rescapture.py +++ b/IPython/quarantine/ext_rescapture.py @@ -8,32 +8,27 @@ var = %magic blah blah var = !ls """ -import IPython.ipapi -from IPython.genutils import * +from IPython.core import ipapi +from IPython.core.error import TryNext +from IPython.utils.genutils import * -ip = IPython.ipapi.get() +ip = ipapi.get() import re def hnd_magic(line,mo): """ Handle a = %mymagic blah blah """ - #cmd = genutils.make_quoted_expr(mo.group('syscmd')) - #mag = 'ipmagic - #return "%s = %s" var = mo.group('varname') cmd = mo.group('cmd') expr = make_quoted_expr(cmd) - return itpl('$var = _ip.magic($expr)') + return itpl('$var = get_ipython().magic($expr)') def hnd_syscmd(line,mo): """ Handle a = !ls """ - #cmd = genutils.make_quoted_expr(mo.group('syscmd')) - #mag = 'ipmagic - #return "%s = %s" var = mo.group('varname') cmd = mo.group('cmd') expr = make_quoted_expr(itpl("sc -l =$cmd")) - return itpl('$var = _ip.magic($expr)') + return itpl('$var = get_ipython().magic($expr)') def install_re_handler(pat, hnd): ip.meta.re_prefilters.append((re.compile(pat), hnd)) @@ -52,12 +47,12 @@ def init_handlers(): init_handlers() -def regex_prefilter_f(self,line): +def regex_prefilter_f(self,line): for pat, handler in ip.meta.re_prefilters: mo = pat.match(line) if mo: return handler(line,mo) - raise IPython.ipapi.TryNext + raise TryNext ip.set_hook('input_prefilter', regex_prefilter_f) diff --git a/IPython/Extensions/ipy_app_completers.py b/IPython/quarantine/ipy_app_completers.py similarity index 75% rename from IPython/Extensions/ipy_app_completers.py rename to IPython/quarantine/ipy_app_completers.py index 629ef79..3a89b54 100644 --- a/IPython/Extensions/ipy_app_completers.py +++ b/IPython/quarantine/ipy_app_completers.py @@ -2,12 +2,12 @@ IPython extension that installs the completers related to external apps. -The actual implementations are in Extensions/ipy_completers.py +The actual implementations are in extensions/ipy_completers.py """ -import IPython.ipapi +from IPython.core import ipapi -ip = IPython.ipapi.get() +ip = ipapi.get() from ipy_completers import * diff --git a/IPython/Extensions/ipy_autoreload.py b/IPython/quarantine/ipy_autoreload.py similarity index 98% rename from IPython/Extensions/ipy_autoreload.py rename to IPython/quarantine/ipy_autoreload.py index 230f575..3976551 100644 --- a/IPython/Extensions/ipy_autoreload.py +++ b/IPython/quarantine/ipy_autoreload.py @@ -224,15 +224,16 @@ reloader = ModuleReloader() #------------------------------------------------------------------------------ # IPython connectivity #------------------------------------------------------------------------------ -import IPython.ipapi +from IPython.core import ipapi +from IPython.core.error import TryNext -ip = IPython.ipapi.get() +ip = ipapi.get() autoreload_enabled = False def runcode_hook(self): if not autoreload_enabled: - raise IPython.ipapi.TryNext + raise TryNext try: reloader.check() except: @@ -339,11 +340,11 @@ def aimport_f(self, parameter_s=''): __import__(modname) basename = modname.split('.')[0] mod = sys.modules[basename] - ip.to_user_ns({basename: mod}) + ip.push({basename: mod}) def init(): - ip.expose_magic('autoreload', autoreload_f) - ip.expose_magic('aimport', aimport_f) + ip.define_magic('autoreload', autoreload_f) + ip.define_magic('aimport', aimport_f) ip.set_hook('pre_runcode_hook', runcode_hook) init() diff --git a/IPython/Extensions/ipy_bzr.py b/IPython/quarantine/ipy_bzr.py similarity index 100% rename from IPython/Extensions/ipy_bzr.py rename to IPython/quarantine/ipy_bzr.py diff --git a/IPython/Extensions/ipy_completers.py b/IPython/quarantine/ipy_completers.py similarity index 92% rename from IPython/Extensions/ipy_completers.py rename to IPython/quarantine/ipy_completers.py index e8c5eb5..f4d9f6e 100644 --- a/IPython/Extensions/ipy_completers.py +++ b/IPython/quarantine/ipy_completers.py @@ -1,18 +1,20 @@ """ Implementations for various useful completers -See Extensions/ipy_stock_completers.py on examples of how to enable a completer, +See extensions/ipy_stock_completers.py on examples of how to enable a completer, but the basic idea is to do: ip.set_hook('complete_command', svn_completer, str_key = 'svn') """ -import IPython.ipapi +from IPython.core import ipapi +from IPython.core.error import TryNext + import glob,os,shlex,sys import inspect from time import time from zipimport import zipimporter -ip = IPython.ipapi.get() +ip = ipapi.get() try: set @@ -169,7 +171,7 @@ def vcs_completer(commands, event): if len(cmd_param) == 2 or 'help' in cmd_param: return commands.split() - return ip.IP.Completer.file_matches(event.symbol) + return ip.Completer.file_matches(event.symbol) pkg_cache = None @@ -245,7 +247,7 @@ def bzr_completer(self,event): return [] else: # the rest are probably file names - return ip.IP.Completer.file_matches(event.symbol) + return ip.Completer.file_matches(event.symbol) return bzr_commands() @@ -329,7 +331,7 @@ def cd_completer(self, event): if ' ' in d: # we don't want to deal with any of that, complex code # for this is elsewhere - raise IPython.ipapi.TryNext + raise TryNext found.append( d ) if not found: @@ -341,7 +343,7 @@ def cd_completer(self, event): if bkmatches: return bkmatches - raise IPython.ipapi.TryNext + raise TryNext def single_dir_expand(matches): diff --git a/IPython/Extensions/ipy_editors.py b/IPython/quarantine/ipy_editors.py similarity index 92% rename from IPython/Extensions/ipy_editors.py rename to IPython/quarantine/ipy_editors.py index 88e064a..5ff34a2 100644 --- a/IPython/Extensions/ipy_editors.py +++ b/IPython/quarantine/ipy_editors.py @@ -5,10 +5,11 @@ They should honor the line number argument, at least. Contributions are *very* welcome. """ -import IPython.ipapi -ip = IPython.ipapi.get() +from IPython.core import ipapi +from IPython.core.error import TryNext +ip = ipapi.get() -from IPython.Itpl import itplns +from IPython.external.Itpl import itplns import os def install_editor(run_template, wait = False): @@ -29,7 +30,7 @@ def install_editor(run_template, wait = False): cmd = itplns(run_template, locals()) print ">",cmd if os.system(cmd) != 0: - raise IPython.ipapi.TryNext() + raise TryNext() if wait: raw_input("Press Enter when done editing:") diff --git a/IPython/Extensions/ipy_exportdb.py b/IPython/quarantine/ipy_exportdb.py similarity index 88% rename from IPython/Extensions/ipy_exportdb.py rename to IPython/quarantine/ipy_exportdb.py index d19cfc0..b6b3e6a 100644 --- a/IPython/Extensions/ipy_exportdb.py +++ b/IPython/quarantine/ipy_exportdb.py @@ -1,11 +1,12 @@ -import IPython.ipapi -ip = IPython.ipapi.get() +from IPython.core import ipapi +from IPython.core import macro +ip = ipapi.get() import os,pprint def export(filename = None): - lines = ['import IPython.ipapi', 'ip = IPython.ipapi.get()',''] + lines = ['import IPython.core.ipapi', 'ip = IPython.core.ipapi.get()',''] vars = ip.db.keys('autorestore/*') vars.sort() @@ -21,7 +22,7 @@ def export(filename = None): if k.startswith('_'): continue - if isinstance(v, IPython.macro.Macro): + if isinstance(v, macro.Macro): macros.append((k,v)) if type(v) in [int, str, float]: variables.append((k,v)) @@ -42,7 +43,7 @@ def export(filename = None): varstomove.append(k) lines.append('%s = %s' % (k,repr(v))) - lines.append('ip.to_user_ns("%s")' % (' '.join(varstomove))) + lines.append('ip.push("%s")' % (' '.join(varstomove))) bkms = ip.db.get('bookmarks',{}) @@ -56,7 +57,7 @@ def export(filename = None): lines.extend(['','# === Alias definitions ===','']) for k,v in aliases.items(): try: - lines.append("ip.defalias('%s', %s)" % (k, repr(v[1]))) + lines.append("ip.define_alias('%s', %s)" % (k, repr(v[1]))) except (AttributeError, TypeError): pass diff --git a/IPython/Extensions/ipy_extutil.py b/IPython/quarantine/ipy_extutil.py similarity index 86% rename from IPython/Extensions/ipy_extutil.py rename to IPython/quarantine/ipy_extutil.py index c35cbc3..e899e55 100644 --- a/IPython/Extensions/ipy_extutil.py +++ b/IPython/quarantine/ipy_extutil.py @@ -7,8 +7,9 @@ to. # for the purposes of this module, every module that has the name 'ip' globally # installed as below is an IPython extension -import IPython.ipapi -ip = IPython.ipapi.get() +from IPython.core import ipapi +ip = ipapi.get() +from IPython.core.iplib import InteractiveShell import sys,textwrap,inspect @@ -34,10 +35,10 @@ class ExtUtil: act = [] for mname,m in sys.modules.items(): o = getattr(m, 'ip', None) - if isinstance(o, IPython.ipapi.IPApi): + if isinstance(o, InteractiveShell): act.append((mname,m)) act.sort() return act extutil = ExtUtil() -ip.to_user_ns('extutil') +ip.push('extutil') diff --git a/IPython/Extensions/ipy_fsops.py b/IPython/quarantine/ipy_fsops.py similarity index 90% rename from IPython/Extensions/ipy_fsops.py rename to IPython/quarantine/ipy_fsops.py index a8cd2eb..9121a58 100644 --- a/IPython/Extensions/ipy_fsops.py +++ b/IPython/quarantine/ipy_fsops.py @@ -15,13 +15,15 @@ do some useful tricks on their own, though (like use 'mglob' patterns). Not to be confused with ipipe commands (ils etc.) that also start with i. """ -import IPython.ipapi -ip = IPython.ipapi.get() +from IPython.core import ipapi +from IPython.core.error import TryNext +ip = ipapi.get() import shutil,os,shlex from IPython.external import mglob from IPython.external.path import path -from IPython.ipapi import UsageError +from IPython.core.error import UsageError +import IPython.utils.generics def parse_args(args): """ Given arg string 'CMD files... target', return ([files], target) """ @@ -58,7 +60,7 @@ def icp(ip,arg): else: shutil.copy2(f,targetdir) return fs -ip.defalias("icp",icp) +ip.define_alias("icp",icp) def imv(ip,arg): """ imv src tgt @@ -72,7 +74,7 @@ def imv(ip,arg): for f in fs: shutil.move(f, target) return fs -ip.defalias("imv",imv) +ip.define_alias("imv",imv) def irm(ip,arg): """ irm path[s]... @@ -91,7 +93,7 @@ def irm(ip,arg): else: os.remove(p) -ip.defalias("irm",irm) +ip.define_alias("irm",irm) def imkdir(ip,arg): """ imkdir path @@ -102,7 +104,7 @@ def imkdir(ip,arg): targetdir = arg.split(None,1)[1] distutils.dir_util.mkpath(targetdir,verbose =1) -ip.defalias("imkdir",imkdir) +ip.define_alias("imkdir",imkdir) def igrep(ip,arg): """ igrep PAT files... @@ -128,7 +130,7 @@ def igrep(ip,arg): print l.rstrip() return res -ip.defalias("igrep",igrep) +ip.define_alias("igrep",igrep) def collect(ip,arg): """ collect foo/a.txt rec:bar=*.py @@ -139,7 +141,7 @@ def collect(ip,arg): Without args, try to open ~/_ipython/collect dir (in win32 at least). """ from IPython.external.path import path - basedir = path(ip.options.ipythondir + '/collect') + basedir = path(ip.ipython_dir + '/collect') try: fs = mglob.expand(arg.split(None,1)[1]) except IndexError: @@ -158,7 +160,7 @@ def collect(ip,arg): print f,"=>",trg shutil.copy2(f,trg) -ip.defalias("collect",collect) +ip.define_alias("collect",collect) def inote(ip,arg): """ inote Hello world @@ -168,15 +170,15 @@ def inote(ip,arg): Without args, opens notes.txt for editing. """ import time - fname = ip.options.ipythondir + '/notes.txt' + fname = ip.ipython_dir + '/notes.txt' try: entry = " === " + time.asctime() + ': ===\n' + arg.split(None,1)[1] + '\n' f= open(fname, 'a').write(entry) except IndexError: - ip.IP.hooks.editor(fname) + ip.hooks.editor(fname) -ip.defalias("inote",inote) +ip.define_alias("inote",inote) def pathobj_mangle(p): return p.replace(' ', '__').replace('.','DOT') @@ -228,9 +230,9 @@ def complete_pathobj(obj, prev_completions): if res: return res # just return normal attributes of 'path' object if the dir is empty - raise IPython.ipapi.TryNext + raise TryNext -complete_pathobj = IPython.generics.complete_object.when_type(PathObj)(complete_pathobj) +complete_pathobj = IPython.utils.generics.complete_object.when_type(PathObj)(complete_pathobj) def test_pathobj(): #p = PathObj('c:/prj') @@ -239,6 +241,6 @@ def test_pathobj(): rootdir = PathObj("/") startmenu = PathObj("d:/Documents and Settings/All Users/Start Menu/Programs") cwd = PathObj('.') - ip.to_user_ns("rootdir startmenu cwd") + ip.push("rootdir startmenu cwd") #test_pathobj() \ No newline at end of file diff --git a/IPython/Extensions/ipy_gnuglobal.py b/IPython/quarantine/ipy_gnuglobal.py similarity index 88% rename from IPython/Extensions/ipy_gnuglobal.py rename to IPython/quarantine/ipy_gnuglobal.py index d03babc..93e27bc 100644 --- a/IPython/Extensions/ipy_gnuglobal.py +++ b/IPython/quarantine/ipy_gnuglobal.py @@ -8,8 +8,8 @@ http://www.gnu.org/software/global/ """ -import IPython.ipapi -ip = IPython.ipapi.get() +from IPython.core import ipapi +ip = ipapi.get() import os # alter to your liking @@ -28,7 +28,7 @@ def global_f(self,cmdline): lines = ['%s [%s]\n%s' % (p[2].rjust(70),p[1],p[3].rstrip()) for p in parts] print "\n".join(lines) -ip.expose_magic('global', global_f) +ip.define_magic('global', global_f) def global_completer(self,event): compl = [l.rstrip() for l in os.popen(global_bin + ' -c ' + event.symbol).readlines()] diff --git a/IPython/Extensions/ipy_greedycompleter.py b/IPython/quarantine/ipy_greedycompleter.py similarity index 88% rename from IPython/Extensions/ipy_greedycompleter.py rename to IPython/quarantine/ipy_greedycompleter.py index 37142d7..f928e45 100644 --- a/IPython/Extensions/ipy_greedycompleter.py +++ b/IPython/quarantine/ipy_greedycompleter.py @@ -9,8 +9,10 @@ only whitespace as completer delimiter. If this works well, we will do the same in default completer. """ -from IPython import generics,ipapi -from IPython.genutils import dir2 +from IPython.core import ipapi +from IPython.core.error import TryNext +from IPython.utils import generics +from IPython.utils.genutils import dir2 def attr_matches(self, text): """Compute matches when text contains a dot. @@ -58,7 +60,7 @@ def attr_matches(self, text): try: words = generics.complete_object(obj, words) - except ipapi.TryNext: + except TryNext: pass # Build match list to return n = len(attr) @@ -66,10 +68,10 @@ def attr_matches(self, text): return res def main(): - import IPython.rlineimpl as readline + import IPython.utils.rlineimpl as readline readline.set_completer_delims(" \n\t") # monkeypatch - the code will be folded to normal completer later on - import IPython.completer - IPython.completer.Completer.attr_matches = attr_matches + import IPython.core.completer + IPython.core.completer.Completer.attr_matches = attr_matches main() \ No newline at end of file diff --git a/IPython/Extensions/ipy_jot.py b/IPython/quarantine/ipy_jot.py similarity index 96% rename from IPython/Extensions/ipy_jot.py rename to IPython/quarantine/ipy_jot.py index dd71967..5b2e65e 100644 --- a/IPython/Extensions/ipy_jot.py +++ b/IPython/quarantine/ipy_jot.py @@ -8,14 +8,14 @@ Stores variables in Struct with some notes in PicleShare database """ from datetime import datetime -import IPython.ipapi -ip = IPython.ipapi.get() +from IPython.core import ipapi +ip = ipapi.get() import pickleshare import inspect,pickle,os,sys,textwrap -from IPython.FakeModule import FakeModule -from IPython.ipstruct import Struct +from IPython.core.fakemodule import FakeModule +from IPython.utils.ipstruct import Struct def refresh_variables(ip, key=None): @@ -116,14 +116,14 @@ def jot_obj(self, obj, name, comment=''): uname = 'jot/'+name+suffix # which one works better? - #all = ip.IP.shadowhist.all() - all = ip.IP.shell.input_hist + #all = ip.shadowhist.all() + all = ip.shell.input_hist # We may actually want to make snapshot of files that are run-ned. # get the comment try: - comment = ip.IP.magic_edit('-x').strip() + comment = ip.magic_edit('-x').strip() except: print "No comment is recorded." comment = '' @@ -307,5 +307,5 @@ def magic_read(self, parameter_s=''): return read_variables(ip, toret) -ip.expose_magic('jot',magic_jot) -ip.expose_magic('read',magic_read) +ip.define_magic('jot',magic_jot) +ip.define_magic('read',magic_read) diff --git a/IPython/Extensions/ipy_lookfor.py b/IPython/quarantine/ipy_lookfor.py similarity index 98% rename from IPython/Extensions/ipy_lookfor.py rename to IPython/quarantine/ipy_lookfor.py index eb56f3d..98738c3 100644 --- a/IPython/Extensions/ipy_lookfor.py +++ b/IPython/quarantine/ipy_lookfor.py @@ -201,8 +201,8 @@ def _lookfor_generate_cache(module, import_modules, regenerate): # IPython connectivity #------------------------------------------------------------------------------ -import IPython.ipapi -ip = IPython.ipapi.get() +from IPython.core import ipapi +ip = ipapi.get() _lookfor_modules = ['numpy', 'scipy'] @@ -229,6 +229,6 @@ def lookfor_modules_f(self, arg=''): else: _lookfor_modules = arg.split() -ip.expose_magic('lookfor', lookfor_f) -ip.expose_magic('lookfor_modules', lookfor_modules_f) +ip.define_magic('lookfor', lookfor_f) +ip.define_magic('lookfor_modules', lookfor_modules_f) diff --git a/IPython/Extensions/ipy_profile_doctest.py b/IPython/quarantine/ipy_profile_doctest.py similarity index 90% rename from IPython/Extensions/ipy_profile_doctest.py rename to IPython/quarantine/ipy_profile_doctest.py index b35074e..d1e4f75 100644 --- a/IPython/Extensions/ipy_profile_doctest.py +++ b/IPython/quarantine/ipy_profile_doctest.py @@ -14,9 +14,9 @@ similar as possible to the default Python ones, for inclusion in doctests.""" # get various stuff that are there for historical / familiarity reasons import ipy_legacy -from IPython import ipapi +from IPython.core import ipapi -from IPython.Extensions import InterpreterPasteInput +from IPython.extensions import InterpreterPasteInput def main(): ip = ipapi.get() @@ -41,6 +41,6 @@ def main(): o.xmode = 'plain' # Store the activity flag in the metadata bag from the running shell - ip.IP.meta.doctest_mode = True + ip.meta.doctest_mode = True main() diff --git a/IPython/Extensions/ipy_pydb.py b/IPython/quarantine/ipy_pydb.py similarity index 61% rename from IPython/Extensions/ipy_pydb.py rename to IPython/quarantine/ipy_pydb.py index ee30523..278a336 100644 --- a/IPython/Extensions/ipy_pydb.py +++ b/IPython/quarantine/ipy_pydb.py @@ -1,9 +1,9 @@ import inspect -import IPython.ipapi -from IPython.genutils import arg_split -ip = IPython.ipapi.get() +from IPython.core import ipapi +from IPython.utils.genutils import arg_split +ip = ipapi.get() -from IPython import Debugger +from IPython.core import debugger def call_pydb(self, args): """Invoke pydb with the supplied parameters.""" @@ -18,13 +18,13 @@ def call_pydb(self, args): argl = arg_split(args) # print argl # dbg if len(inspect.getargspec(pydb.runv)[0]) == 2: - pdb = Debugger.Pdb(color_scheme=self.rc.colors) - ip.IP.history_saving_wrapper( lambda : pydb.runv(argl, pdb) )() + pdb = debugger.Pdb(color_scheme=self.colors) + ip.history_saving_wrapper( lambda : pydb.runv(argl, pdb) )() else: - ip.IP.history_saving_wrapper( lambda : pydb.runv(argl) )() + ip.history_saving_wrapper( lambda : pydb.runv(argl) )() -ip.expose_magic("pydb",call_pydb) +ip.define_magic("pydb",call_pydb) diff --git a/IPython/Extensions/ipy_rehashdir.py b/IPython/quarantine/ipy_rehashdir.py similarity index 95% rename from IPython/Extensions/ipy_rehashdir.py rename to IPython/quarantine/ipy_rehashdir.py index 15abcec..7250470 100644 --- a/IPython/Extensions/ipy_rehashdir.py +++ b/IPython/quarantine/ipy_rehashdir.py @@ -15,8 +15,8 @@ extensions are allowed to do that). """ -import IPython.ipapi -ip = IPython.ipapi.get() +from IPython.core import ipapi +ip = ipapi.get() import os,re,fnmatch,sys @@ -42,7 +42,7 @@ class PyLauncher: """ Invoke selflanucher on the specified script This is mostly useful for associating with scripts using:: - _ip.defalias('foo',PyLauncher('foo_script.py')) + _ip.define_alias('foo',PyLauncher('foo_script.py')) """ def __init__(self,script): @@ -88,7 +88,7 @@ def rehashdir_f(self,arg): if not arg: arg = '.' path = map(os.path.abspath,arg.split(';')) - alias_table = self.shell.alias_table + alias_table = self.shell.alias_manager.alias_table if os.name == 'posix': isexec = lambda fname:os.path.isfile(fname) and \ @@ -137,4 +137,4 @@ def rehashdir_f(self,arg): os.chdir(savedir) return created -ip.expose_magic("rehashdir",rehashdir_f) +ip.define_magic("rehashdir",rehashdir_f) diff --git a/IPython/Extensions/ipy_render.py b/IPython/quarantine/ipy_render.py similarity index 91% rename from IPython/Extensions/ipy_render.py rename to IPython/quarantine/ipy_render.py index 3946a87..8aa1b72 100644 --- a/IPython/Extensions/ipy_render.py +++ b/IPython/quarantine/ipy_render.py @@ -2,14 +2,14 @@ """ IPython extension: Render templates from variables and paste to clipbard """ -import IPython.ipapi +from IPython.core import ipapi -ip = IPython.ipapi.get() +ip = ipapi.get() from string import Template import sys,os -from IPython.Itpl import itplns +from IPython.external.Itpl import itplns def toclip_w32(s): """ Places contents of s to clipboard @@ -64,5 +64,5 @@ def render(tmpl): toclip(res) return res -ip.to_user_ns('render') +ip.push('render') \ No newline at end of file diff --git a/IPython/Extensions/ipy_server.py b/IPython/quarantine/ipy_server.py similarity index 92% rename from IPython/Extensions/ipy_server.py rename to IPython/quarantine/ipy_server.py index cd4c55b..a409942 100644 --- a/IPython/Extensions/ipy_server.py +++ b/IPython/quarantine/ipy_server.py @@ -13,8 +13,8 @@ This is a bit like 'M-x server-start" or gnuserv in the emacs world. """ -import IPython.ipapi -ip = IPython.ipapi.get() +from IPython.core import ipapi +ip = ipapi.get() import SocketServer diff --git a/IPython/Extensions/ipy_signals.py b/IPython/quarantine/ipy_signals.py similarity index 93% rename from IPython/Extensions/ipy_signals.py rename to IPython/quarantine/ipy_signals.py index 8debc62..3d7f072 100644 --- a/IPython/Extensions/ipy_signals.py +++ b/IPython/quarantine/ipy_signals.py @@ -10,10 +10,10 @@ If _ip.options.verbose is true, show exit status if nonzero """ import signal,os,sys -import IPython.ipapi +from IPython.core import ipapi import subprocess -ip = IPython.ipapi.get() +ip = ipapi.get() def new_ipsystem_posix(cmd): """ ctrl+c ignoring replacement for system() command in iplib. @@ -55,7 +55,7 @@ def init(): o.allow_new_attr (True ) o.verbose = 0 - ip.IP.system = (sys.platform == 'win32' and new_ipsystem_win32 or + ip.system = (sys.platform == 'win32' and new_ipsystem_win32 or new_ipsystem_posix) init() diff --git a/IPython/Extensions/ipy_stock_completers.py b/IPython/quarantine/ipy_stock_completers.py similarity index 72% rename from IPython/Extensions/ipy_stock_completers.py rename to IPython/quarantine/ipy_stock_completers.py index 2714e0b..98ce4f3 100644 --- a/IPython/Extensions/ipy_stock_completers.py +++ b/IPython/quarantine/ipy_stock_completers.py @@ -2,12 +2,12 @@ IPython extension that installs completers related to core ipython behaviour. -The actual implementations are in Extensions/ipy_completers.py +The actual implementations are in extensions/ipy_completers.py """ -import IPython.ipapi +from IPython.core import ipapi -ip = IPython.ipapi.get() +ip = ipapi.get() from ipy_completers import * diff --git a/IPython/Extensions/ipy_synchronize_with.py b/IPython/quarantine/ipy_synchronize_with.py similarity index 98% rename from IPython/Extensions/ipy_synchronize_with.py rename to IPython/quarantine/ipy_synchronize_with.py index d6222b6..1eb0d16 100644 --- a/IPython/Extensions/ipy_synchronize_with.py +++ b/IPython/quarantine/ipy_synchronize_with.py @@ -1,5 +1,5 @@ -import IPython.ipapi -ip = IPython.ipapi.get() +from IPython.core import ipapi +ip = ipapi.get() import win32api import win32ui diff --git a/IPython/Extensions/ipy_system_conf.py b/IPython/quarantine/ipy_system_conf.py similarity index 80% rename from IPython/Extensions/ipy_system_conf.py rename to IPython/quarantine/ipy_system_conf.py index 027594d..539650d 100644 --- a/IPython/Extensions/ipy_system_conf.py +++ b/IPython/quarantine/ipy_system_conf.py @@ -7,8 +7,8 @@ should reside there. """ -import IPython.ipapi -ip = IPython.ipapi.get() +from IPython.core import ipapi +ip = ipapi.get() # add system wide configuration information, import extensions etc. here. # nothing here is essential @@ -21,4 +21,4 @@ import clearcmd # %clear import ipy_stock_completers -ip.load('IPython.history') +ip.load('IPython.core.history') diff --git a/IPython/Extensions/ipy_which.py b/IPython/quarantine/ipy_which.py similarity index 93% rename from IPython/Extensions/ipy_which.py rename to IPython/quarantine/ipy_which.py index 44bfae1..efe7449 100644 --- a/IPython/Extensions/ipy_which.py +++ b/IPython/quarantine/ipy_which.py @@ -4,8 +4,8 @@ r""" %which magic command """ -import IPython.ipapi -ip = IPython.ipapi.get() +from IPython.core import ipapi +ip = ipapi.get() import os,sys from fnmatch import fnmatch @@ -23,7 +23,7 @@ def which(fname): return def which_alias(fname): - for al, tgt in ip.IP.alias_table.items(): + for al, tgt in ip.alias_table.items(): if not (al == fname or fnmatch(al, fname)): continue if callable(tgt): @@ -72,5 +72,5 @@ def which_f(self, arg): for e in which(arg): print e -ip.expose_magic("which",which_f) +ip.define_magic("which",which_f) diff --git a/IPython/Extensions/ipy_winpdb.py b/IPython/quarantine/ipy_winpdb.py similarity index 92% rename from IPython/Extensions/ipy_winpdb.py rename to IPython/quarantine/ipy_winpdb.py index 6c2d64b..2150b17 100644 --- a/IPython/Extensions/ipy_winpdb.py +++ b/IPython/quarantine/ipy_winpdb.py @@ -21,10 +21,11 @@ For more details: https://bugs.launchpad.net/ipython/+bug/249036 import os -import IPython.ipapi +from IPython.core import ipapi +from IPython.core.error import UsageError import rpdb2 -ip = IPython.ipapi.get() +ip = ipapi.get() rpdb_started = False @@ -58,7 +59,7 @@ def wdb_f(self, arg): path = os.path.abspath(arg) if not os.path.isfile(path): - raise IPython.ipapi.UsageError("%%wdb: file %s does not exist" % path) + raise UsageError("%%wdb: file %s does not exist" % path) if not rpdb_started: passwd = ip.db.get('winpdb_pass', None) if passwd is None: @@ -80,4 +81,4 @@ def wdb_f(self, arg): ip.magic('%run ' + arg) -ip.expose_magic('wdb', wdb_f) +ip.define_magic('wdb', wdb_f) diff --git a/IPython/Extensions/ipy_workdir.py b/IPython/quarantine/ipy_workdir.py similarity index 91% rename from IPython/Extensions/ipy_workdir.py rename to IPython/quarantine/ipy_workdir.py index e27b476..53d9105 100644 --- a/IPython/Extensions/ipy_workdir.py +++ b/IPython/quarantine/ipy_workdir.py @@ -1,7 +1,7 @@ #!/usr/bin/env python -import IPython.ipapi -ip = IPython.ipapi.get() +from IPython.core import ipapi +ip = ipapi.get() import os, subprocess @@ -40,4 +40,4 @@ def workdir_f(ip,line): finally: os.chdir(olddir) -ip.defalias("workdir",workdir_f) +ip.define_alias("workdir",workdir_f) diff --git a/IPython/Extensions/jobctrl.py b/IPython/quarantine/jobctrl.py similarity index 87% rename from IPython/Extensions/jobctrl.py rename to IPython/quarantine/jobctrl.py index 3d7b948..9a38ce5 100644 --- a/IPython/Extensions/jobctrl.py +++ b/IPython/quarantine/jobctrl.py @@ -45,9 +45,10 @@ from subprocess import * import os,shlex,sys,time import threading,Queue -from IPython import genutils +from IPython.utils import genutils -import IPython.ipapi +from IPython.core import ipapi +from IPython.core.error import TryNext if os.name == 'nt': def kill_process(pid): @@ -123,12 +124,12 @@ def jobctrl_prefilter_f(self,line): if line.startswith('&'): pre,fn,rest = self.split_user_input(line[1:]) - line = ip.IP.expand_aliases(fn,rest) + line = ip.expand_aliases(fn,rest) if not _jobq: - return '_ip.startjob(%s)' % genutils.make_quoted_expr(line) - return '_ip.jobq(%s)' % genutils.make_quoted_expr(line) + return 'get_ipython().startjob(%s)' % genutils.make_quoted_expr(line) + return 'get_ipython().jobq(%s)' % genutils.make_quoted_expr(line) - raise IPython.ipapi.TryNext + raise TryNext def jobq_output_hook(self): if not _jobq: @@ -230,13 +231,13 @@ def jobctrl_shellcmd(ip,cmd): def install(): global ip - ip = IPython.ipapi.get() + ip = ipapi.get() # needed to make startjob visible as _ip.startjob('blah') ip.startjob = startjob ip.set_hook('input_prefilter', jobctrl_prefilter_f) ip.set_hook('shell_hook', jobctrl_shellcmd) - ip.expose_magic('kill',magic_kill) - ip.expose_magic('tasks',magic_tasks) - ip.expose_magic('jobqueue',jobqueue_f) + ip.define_magic('kill',magic_kill) + ip.define_magic('tasks',magic_tasks) + ip.define_magic('jobqueue',jobqueue_f) ip.set_hook('pre_prompt_hook', jobq_output_hook) install() diff --git a/IPython/Extensions/ledit.py b/IPython/quarantine/ledit.py similarity index 96% rename from IPython/Extensions/ledit.py rename to IPython/quarantine/ledit.py index 87683b4..fa309b1 100644 --- a/IPython/Extensions/ledit.py +++ b/IPython/quarantine/ledit.py @@ -43,9 +43,9 @@ l.__class__ l.__hash__ l.__repr__ l.expandtabs l.lj ... (completions for string variable shown ) ... """ -import IPython.ipapi +from IPython.core import ipapi import pprint -ip = IPython.ipapi.get() +ip = ipapi.get() curdata = [] @@ -95,4 +95,4 @@ def line_edit_complete_f(self,event): ip.set_hook('complete_command', line_edit_complete_f , str_key = '%led') -ip.expose_magic('led', line_edit_f) \ No newline at end of file +ip.define_magic('led', line_edit_f) \ No newline at end of file diff --git a/IPython/Extensions/pspersistence.py b/IPython/quarantine/pspersistence.py similarity index 94% rename from IPython/Extensions/pspersistence.py rename to IPython/quarantine/pspersistence.py index 563308d..dbba916 100644 --- a/IPython/Extensions/pspersistence.py +++ b/IPython/quarantine/pspersistence.py @@ -5,14 +5,14 @@ Stores variables, aliases etc. in PickleShare database. """ -import IPython.ipapi -from IPython.ipapi import UsageError -ip = IPython.ipapi.get() +from IPython.core import ipapi +from IPython.core.error import TryNext, UsageError +ip = ipapi.get() import pickleshare import inspect,pickle,os,sys,textwrap -from IPython.FakeModule import FakeModule +from IPython.core.fakemodule import FakeModule def restore_aliases(self): ip = self.getapi() @@ -20,7 +20,7 @@ def restore_aliases(self): for k,v in staliases.items(): #print "restore alias",k,v # dbg #self.alias_table[k] = v - ip.defalias(k,v) + ip.define_alias(k,v) def refresh_variables(ip): @@ -47,7 +47,7 @@ def restore_data(self): refresh_variables(ip) restore_aliases(self) restore_dhist(self) - raise IPython.ipapi.TryNext + raise TryNext ip.set_hook('late_startup_hook', restore_data) @@ -157,6 +157,7 @@ def magic_store(self, parameter_s=''): obj = ip.user_ns[args[0]] except KeyError: # it might be an alias + # This needs to be refactored to use the new AliasManager stuff. if args[0] in self.alias_table: staliases = db.get('stored_aliases',{}) staliases[ args[0] ] = self.alias_table[ args[0] ] @@ -179,4 +180,4 @@ def magic_store(self, parameter_s=''): self.db[ 'autorestore/' + args[0] ] = obj print "Stored '%s' (%s)" % (args[0], obj.__class__.__name__) -ip.expose_magic('store',magic_store) +ip.define_magic('store',magic_store) diff --git a/IPython/quarantine/tests/__init__.py b/IPython/quarantine/tests/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/IPython/quarantine/tests/__init__.py diff --git a/IPython/Extensions/win32clip.py b/IPython/quarantine/win32clip.py similarity index 91% rename from IPython/Extensions/win32clip.py rename to IPython/quarantine/win32clip.py index f2030e0..8c836a0 100644 --- a/IPython/Extensions/win32clip.py +++ b/IPython/quarantine/win32clip.py @@ -1,6 +1,6 @@ -import IPython.ipapi +from IPython.core import ipapi -ip = IPython.ipapi.get() +ip = ipapi.get() def clip_f( self, parameter_s = '' ): """Save a set of lines to the clipboard. @@ -42,4 +42,4 @@ def clip_f( self, parameter_s = '' ): print 'The following text was written to the clipboard' print val -ip.expose_magic( "clip", clip_f ) +ip.define_magic( "clip", clip_f ) diff --git a/IPython/scripts/__init__.py b/IPython/scripts/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/IPython/scripts/__init__.py diff --git a/scripts/iptest b/IPython/scripts/iptest similarity index 100% rename from scripts/iptest rename to IPython/scripts/iptest diff --git a/IPython/scripts/ipython b/IPython/scripts/ipython new file mode 100755 index 0000000..65fec55 --- /dev/null +++ b/IPython/scripts/ipython @@ -0,0 +1,6 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from IPython.core.ipapp import launch_new_instance + +launch_new_instance() diff --git a/scripts/ipython-wx b/IPython/scripts/ipython-wx similarity index 100% rename from scripts/ipython-wx rename to IPython/scripts/ipython-wx diff --git a/scripts/ipythonx b/IPython/scripts/ipythonx similarity index 100% rename from scripts/ipythonx rename to IPython/scripts/ipythonx diff --git a/scripts/irunner b/IPython/scripts/irunner similarity index 80% rename from scripts/irunner rename to IPython/scripts/irunner index c350d39..9406e27 100755 --- a/scripts/irunner +++ b/IPython/scripts/irunner @@ -4,6 +4,6 @@ Run with --help for details, or see the irunner source.""" -from IPython import irunner +from IPython.lib import irunner irunner.main() diff --git a/scripts/pycolor b/IPython/scripts/pycolor similarity index 60% rename from scripts/pycolor rename to IPython/scripts/pycolor index 9006717..8ad2267 100755 --- a/scripts/pycolor +++ b/IPython/scripts/pycolor @@ -2,5 +2,5 @@ # -*- coding: utf-8 -*- """Simple wrapper around PyColorize, which colorizes python sources.""" -import IPython.PyColorize -IPython.PyColorize.main() +import IPython.utils.PyColorize +IPython.utils.PyColorize.main() diff --git a/IPython/shellglobals.py b/IPython/shellglobals.py deleted file mode 100644 index 34ce467..0000000 --- a/IPython/shellglobals.py +++ /dev/null @@ -1,101 +0,0 @@ -"""Some globals used by the main Shell classes. -""" - -#----------------------------------------------------------------------------- -# Module imports -#----------------------------------------------------------------------------- - -# stdlib -import inspect -import thread - -try: - import ctypes - HAS_CTYPES = True -except ImportError: - HAS_CTYPES = False - -# our own -from IPython.genutils import Term,warn,error,flag_calls, ask_yes_no - -#----------------------------------------------------------------------------- -# Globals -#----------------------------------------------------------------------------- -# global flag to pass around information about Ctrl-C without exceptions -KBINT = False - -# global flag to turn on/off Tk support. -USE_TK = False - -# ID for the main thread, used for cross-thread exceptions -MAIN_THREAD_ID = thread.get_ident() - -# Tag when runcode() is active, for exception handling -CODE_RUN = None - -#----------------------------------------------------------------------------- -# This class is trivial now, but I want to have it in to publish a clean -# interface. Later when the internals are reorganized, code that uses this -# shouldn't have to change. - -if HAS_CTYPES: - # Add async exception support. Trick taken from: - # http://sebulba.wikispaces.com/recipe+thread2 - def _async_raise(tid, exctype): - """raises the exception, performs cleanup if needed""" - if not inspect.isclass(exctype): - raise TypeError("Only types can be raised (not instances)") - res = ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, - ctypes.py_object(exctype)) - if res == 0: - raise ValueError("invalid thread id") - elif res != 1: - # """if it returns a number greater than one, you're in trouble, - # and you should call it again with exc=NULL to revert the effect""" - ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, 0) - raise SystemError("PyThreadState_SetAsyncExc failed") - - def sigint_handler (signum,stack_frame): - """Sigint handler for threaded apps. - - This is a horrible hack to pass information about SIGINT _without_ - using exceptions, since I haven't been able to properly manage - cross-thread exceptions in GTK/WX. In fact, I don't think it can be - done (or at least that's my understanding from a c.l.py thread where - this was discussed).""" - - global KBINT - - if CODE_RUN: - _async_raise(MAIN_THREAD_ID,KeyboardInterrupt) - else: - KBINT = True - print '\nKeyboardInterrupt - Press to continue.', - Term.cout.flush() - -else: - def sigint_handler (signum,stack_frame): - """Sigint handler for threaded apps. - - This is a horrible hack to pass information about SIGINT _without_ - using exceptions, since I haven't been able to properly manage - cross-thread exceptions in GTK/WX. In fact, I don't think it can be - done (or at least that's my understanding from a c.l.py thread where - this was discussed).""" - - global KBINT - - print '\nKeyboardInterrupt - Press to continue.', - Term.cout.flush() - # Set global flag so that runsource can know that Ctrl-C was hit - KBINT = True - - -def run_in_frontend(src): - """ Check if source snippet can be run in the REPL thread, as opposed to - GUI mainloop (to prevent unnecessary hanging of mainloop). - """ - - if src.startswith('_ip.system(') and not '\n' in src: - return True - return False diff --git a/IPython/testing/attic/ipdoctest.py b/IPython/testing/attic/ipdoctest.py deleted file mode 100755 index ff91f92..0000000 --- a/IPython/testing/attic/ipdoctest.py +++ /dev/null @@ -1,800 +0,0 @@ -#!/usr/bin/env python -"""IPython-enhanced doctest module with unittest integration. - -This module is heavily based on the standard library's doctest module, but -enhances it with IPython support. This enables docstrings to contain -unmodified IPython input and output pasted from real IPython sessions. - -It should be possible to use this module as a drop-in replacement for doctest -whenever you wish to use IPython input. - -Since the module absorbs all normal doctest functionality, you can use a mix of -both plain Python and IPython examples in any given module, though not in the -same docstring. - -See a simple example at the bottom of this code which serves as self-test and -demonstration code. Simply run this file (use -v for details) to run the -tests. - -This module also contains routines to ease the integration of doctests with -regular unittest-based testing. In particular, see the DocTestLoader class and -the makeTestSuite utility function. - - -Limitations: - - - When generating examples for use as doctests, make sure that you have - pretty-printing OFF. This can be done either by starting ipython with the - flag '--nopprint', by setting pprint to 0 in your ipythonrc file, or by - interactively disabling it with %Pprint. This is required so that IPython - output matches that of normal Python, which is used by doctest for internal - execution. - - - Do not rely on specific prompt numbers for results (such as using - '_34==True', for example). For IPython tests run via an external process - the prompt numbers may be different, and IPython tests run as normal python - code won't even have these special _NN variables set at all. - - - IPython functions that produce output as a side-effect of calling a system - process (e.g. 'ls') can be doc-tested, but they must be handled in an - external IPython process. Such doctests must be tagged with: - - # ipdoctest: EXTERNAL - - so that the testing machinery handles them differently. Since these are run - via pexpect in an external process, they can't deal with exceptions or other - fancy featurs of regular doctests. You must limit such tests to simple - matching of the output. For this reason, I recommend you limit these kinds - of doctests to features that truly require a separate process, and use the - normal IPython ones (which have all the features of normal doctests) for - everything else. See the examples at the bottom of this file for a - comparison of what can be done with both types. -""" - -# Standard library imports -import __builtin__ -import doctest -import inspect -import os -import re -import sys -import unittest - -from doctest import * - -# Our own imports -from IPython.tools import utils - -########################################################################### -# -# We must start our own ipython object and heavily muck with it so that all the -# modifications IPython makes to system behavior don't send the doctest -# machinery into a fit. This code should be considered a gross hack, but it -# gets the job done. - -import IPython - -# Hack to restore __main__, which ipython modifies upon startup -_main = sys.modules.get('__main__') -ipython = IPython.Shell.IPShell(['--classic','--noterm_title']).IP -sys.modules['__main__'] = _main - -# Deactivate the various python system hooks added by ipython for -# interactive convenience so we don't confuse the doctest system -sys.displayhook = sys.__displayhook__ -sys.excepthook = sys.__excepthook__ - -# So that ipython magics and aliases can be doctested -__builtin__._ip = IPython.ipapi.get() - -# for debugging only!!! -#from IPython.Shell import IPShellEmbed;ipshell=IPShellEmbed(['--noterm_title']) # dbg - - -# runner -from IPython.irunner import IPythonRunner -iprunner = IPythonRunner(echo=False) - -########################################################################### - -# A simple subclassing of the original with a different class name, so we can -# distinguish and treat differently IPython examples from pure python ones. -class IPExample(doctest.Example): pass - -class IPExternalExample(doctest.Example): - """Doctest examples to be run in an external process.""" - - def __init__(self, source, want, exc_msg=None, lineno=0, indent=0, - options=None): - # Parent constructor - doctest.Example.__init__(self,source,want,exc_msg,lineno,indent,options) - - # An EXTRA newline is needed to prevent pexpect hangs - self.source += '\n' - -class IPDocTestParser(doctest.DocTestParser): - """ - A class used to parse strings containing doctest examples. - - Note: This is a version modified to properly recognize IPython input and - convert any IPython examples into valid Python ones. - """ - # This regular expression is used to find doctest examples in a - # string. It defines three groups: `source` is the source code - # (including leading indentation and prompts); `indent` is the - # indentation of the first (PS1) line of the source code; and - # `want` is the expected output (including leading indentation). - - # Classic Python prompts or default IPython ones - _PS1_PY = r'>>>' - _PS2_PY = r'\.\.\.' - - _PS1_IP = r'In\ \[\d+\]:' - _PS2_IP = r'\ \ \ \.\.\.+:' - - _RE_TPL = r''' - # Source consists of a PS1 line followed by zero or more PS2 lines. - (?P - (?:^(?P [ ]*) (?P %s) .*) # PS1 line - (?:\n [ ]* (?P %s) .*)*) # PS2 lines - \n? # a newline - # Want consists of any non-blank lines that do not start with PS1. - (?P (?:(?![ ]*$) # Not a blank line - (?![ ]*%s) # Not a line starting with PS1 - (?![ ]*%s) # Not a line starting with PS2 - .*$\n? # But any other line - )*) - ''' - - _EXAMPLE_RE_PY = re.compile( _RE_TPL % (_PS1_PY,_PS2_PY,_PS1_PY,_PS2_PY), - re.MULTILINE | re.VERBOSE) - - _EXAMPLE_RE_IP = re.compile( _RE_TPL % (_PS1_IP,_PS2_IP,_PS1_IP,_PS2_IP), - re.MULTILINE | re.VERBOSE) - - def ip2py(self,source): - """Convert input IPython source into valid Python.""" - out = [] - newline = out.append - for line in source.splitlines(): - newline(ipython.prefilter(line,True)) - newline('') # ensure a closing newline, needed by doctest - return '\n'.join(out) - - def parse(self, string, name=''): - """ - Divide the given string into examples and intervening text, - and return them as a list of alternating Examples and strings. - Line numbers for the Examples are 0-based. The optional - argument `name` is a name identifying this string, and is only - used for error messages. - """ - string = string.expandtabs() - # If all lines begin with the same indentation, then strip it. - min_indent = self._min_indent(string) - if min_indent > 0: - string = '\n'.join([l[min_indent:] for l in string.split('\n')]) - - output = [] - charno, lineno = 0, 0 - - # Whether to convert the input from ipython to python syntax - ip2py = False - # Find all doctest examples in the string. First, try them as Python - # examples, then as IPython ones - terms = list(self._EXAMPLE_RE_PY.finditer(string)) - if terms: - # Normal Python example - Example = doctest.Example - else: - # It's an ipython example. Note that IPExamples are run - # in-process, so their syntax must be turned into valid python. - # IPExternalExamples are run out-of-process (via pexpect) so they - # don't need any filtering (a real ipython will be executing them). - terms = list(self._EXAMPLE_RE_IP.finditer(string)) - if re.search(r'#\s*ipdoctest:\s*EXTERNAL',string): - #print '-'*70 # dbg - #print 'IPExternalExample, Source:\n',string # dbg - #print '-'*70 # dbg - Example = IPExternalExample - else: - #print '-'*70 # dbg - #print 'IPExample, Source:\n',string # dbg - #print '-'*70 # dbg - Example = IPExample - ip2py = True - - for m in terms: - # Add the pre-example text to `output`. - output.append(string[charno:m.start()]) - # Update lineno (lines before this example) - lineno += string.count('\n', charno, m.start()) - # Extract info from the regexp match. - (source, options, want, exc_msg) = \ - self._parse_example(m, name, lineno,ip2py) - if Example is IPExternalExample: - options[doctest.NORMALIZE_WHITESPACE] = True - # Create an Example, and add it to the list. - if not self._IS_BLANK_OR_COMMENT(source): - output.append(Example(source, want, exc_msg, - lineno=lineno, - indent=min_indent+len(m.group('indent')), - options=options)) - # Update lineno (lines inside this example) - lineno += string.count('\n', m.start(), m.end()) - # Update charno. - charno = m.end() - # Add any remaining post-example text to `output`. - output.append(string[charno:]) - - return output - - def _parse_example(self, m, name, lineno,ip2py=False): - """ - Given a regular expression match from `_EXAMPLE_RE` (`m`), - return a pair `(source, want)`, where `source` is the matched - example's source code (with prompts and indentation stripped); - and `want` is the example's expected output (with indentation - stripped). - - `name` is the string's name, and `lineno` is the line number - where the example starts; both are used for error messages. - - Optional: - `ip2py`: if true, filter the input via IPython to convert the syntax - into valid python. - """ - - # Get the example's indentation level. - indent = len(m.group('indent')) - - # Divide source into lines; check that they're properly - # indented; and then strip their indentation & prompts. - source_lines = m.group('source').split('\n') - - # We're using variable-length input prompts - ps1 = m.group('ps1') - ps2 = m.group('ps2') - ps1_len = len(ps1) - - self._check_prompt_blank(source_lines, indent, name, lineno,ps1_len) - if ps2: - self._check_prefix(source_lines[1:], ' '*indent + ps2, name, lineno) - - source = '\n'.join([sl[indent+ps1_len+1:] for sl in source_lines]) - - if ip2py: - # Convert source input from IPython into valid Python syntax - source = self.ip2py(source) - - # Divide want into lines; check that it's properly indented; and - # then strip the indentation. Spaces before the last newline should - # be preserved, so plain rstrip() isn't good enough. - want = m.group('want') - want_lines = want.split('\n') - if len(want_lines) > 1 and re.match(r' *$', want_lines[-1]): - del want_lines[-1] # forget final newline & spaces after it - self._check_prefix(want_lines, ' '*indent, name, - lineno + len(source_lines)) - - # Remove ipython output prompt that might be present in the first line - want_lines[0] = re.sub(r'Out\[\d+\]: \s*?\n?','',want_lines[0]) - - want = '\n'.join([wl[indent:] for wl in want_lines]) - - # If `want` contains a traceback message, then extract it. - m = self._EXCEPTION_RE.match(want) - if m: - exc_msg = m.group('msg') - else: - exc_msg = None - - # Extract options from the source. - options = self._find_options(source, name, lineno) - - return source, options, want, exc_msg - - def _check_prompt_blank(self, lines, indent, name, lineno, ps1_len): - """ - Given the lines of a source string (including prompts and - leading indentation), check to make sure that every prompt is - followed by a space character. If any line is not followed by - a space character, then raise ValueError. - - Note: IPython-modified version which takes the input prompt length as a - parameter, so that prompts of variable length can be dealt with. - """ - space_idx = indent+ps1_len - min_len = space_idx+1 - for i, line in enumerate(lines): - if len(line) >= min_len and line[space_idx] != ' ': - raise ValueError('line %r of the docstring for %s ' - 'lacks blank after %s: %r' % - (lineno+i+1, name, - line[indent:space_idx], line)) - - -SKIP = register_optionflag('SKIP') - -class IPDocTestRunner(doctest.DocTestRunner): - """Modified DocTestRunner which can also run IPython tests. - - This runner is capable of handling IPython doctests that require - out-of-process output capture (such as system calls via !cmd or aliases). - Note however that because these tests are run in a separate process, many - of doctest's fancier capabilities (such as detailed exception analysis) are - not available. So try to limit such tests to simple cases of matching - actual output. - """ - - #///////////////////////////////////////////////////////////////// - # DocTest Running - #///////////////////////////////////////////////////////////////// - - def _run_iptest(self, test, out): - """ - Run the examples in `test`. Write the outcome of each example with one - of the `DocTestRunner.report_*` methods, using the writer function - `out`. Return a tuple `(f, t)`, where `t` is the number of examples - tried, and `f` is the number of examples that failed. The examples are - run in the namespace `test.globs`. - - IPython note: this is a modified version of the original __run() - private method to handle out-of-process examples. - """ - - if out is None: - out = sys.stdout.write - - # Keep track of the number of failures and tries. - failures = tries = 0 - - # Save the option flags (since option directives can be used - # to modify them). - original_optionflags = self.optionflags - - SUCCESS, FAILURE, BOOM = range(3) # `outcome` state - - check = self._checker.check_output - - # Process each example. - for examplenum, example in enumerate(test.examples): - - # If REPORT_ONLY_FIRST_FAILURE is set, then supress - # reporting after the first failure. - quiet = (self.optionflags & REPORT_ONLY_FIRST_FAILURE and - failures > 0) - - # Merge in the example's options. - self.optionflags = original_optionflags - if example.options: - for (optionflag, val) in example.options.items(): - if val: - self.optionflags |= optionflag - else: - self.optionflags &= ~optionflag - - # If 'SKIP' is set, then skip this example. - if self.optionflags & SKIP: - continue - - # Record that we started this example. - tries += 1 - if not quiet: - self.report_start(out, test, example) - - # Run the example in the given context (globs), and record - # any exception that gets raised. (But don't intercept - # keyboard interrupts.) - try: - # Don't blink! This is where the user's code gets run. - got = '' - # The code is run in an external process - got = iprunner.run_source(example.source,get_output=True) - except KeyboardInterrupt: - raise - except: - self.debugger.set_continue() # ==== Example Finished ==== - - outcome = FAILURE # guilty until proved innocent or insane - - if check(example.want, got, self.optionflags): - outcome = SUCCESS - - # Report the outcome. - if outcome is SUCCESS: - if not quiet: - self.report_success(out, test, example, got) - elif outcome is FAILURE: - if not quiet: - self.report_failure(out, test, example, got) - failures += 1 - elif outcome is BOOM: - if not quiet: - self.report_unexpected_exception(out, test, example, - exc_info) - failures += 1 - else: - assert False, ("unknown outcome", outcome) - - # Restore the option flags (in case they were modified) - self.optionflags = original_optionflags - - # Record and return the number of failures and tries. - - # Hack to access a parent private method by working around Python's - # name mangling (which is fortunately simple). - doctest.DocTestRunner._DocTestRunner__record_outcome(self,test, - failures, tries) - return failures, tries - - def run(self, test, compileflags=None, out=None, clear_globs=True): - """Run examples in `test`. - - This method will defer to the parent for normal Python examples, but it - will run IPython ones via pexpect. - """ - if not test.examples: - return - - if isinstance(test.examples[0],IPExternalExample): - self._run_iptest(test,out) - else: - DocTestRunner.run(self,test,compileflags,out,clear_globs) - - -class IPDebugRunner(IPDocTestRunner,doctest.DebugRunner): - """IPython-modified DebugRunner, see the original class for details.""" - - def run(self, test, compileflags=None, out=None, clear_globs=True): - r = IPDocTestRunner.run(self, test, compileflags, out, False) - if clear_globs: - test.globs.clear() - return r - - -class IPDocTestLoader(unittest.TestLoader): - """A test loader with IPython-enhanced doctest support. - - Instances of this loader will automatically add doctests found in a module - to the test suite returned by the loadTestsFromModule method. In - addition, at initialization time a string of doctests can be given to the - loader, enabling it to add doctests to a module which didn't have them in - its docstring, coming from an external source.""" - - - def __init__(self,dt_files=None,dt_modules=None,test_finder=None): - """Initialize the test loader. - - :Keywords: - - dt_files : list (None) - List of names of files to be executed as doctests. - - dt_modules : list (None) - List of module names to be scanned for doctests in their - docstrings. - - test_finder : instance (None) - Instance of a testfinder (see doctest for details). - """ - - if dt_files is None: dt_files = [] - if dt_modules is None: dt_modules = [] - self.dt_files = utils.list_strings(dt_files) - self.dt_modules = utils.list_strings(dt_modules) - if test_finder is None: - test_finder = doctest.DocTestFinder(parser=IPDocTestParser()) - self.test_finder = test_finder - - def loadTestsFromModule(self, module): - """Return a suite of all tests cases contained in the given module. - - If the loader was initialized with a doctests argument, then this - string is assigned as the module's docstring.""" - - # Start by loading any tests in the called module itself - suite = super(self.__class__,self).loadTestsFromModule(module) - - # Now, load also tests referenced at construction time as companion - # doctests that reside in standalone files - for fname in self.dt_files: - #print 'mod:',module # dbg - #print 'fname:',fname # dbg - #suite.addTest(doctest.DocFileSuite(fname)) - suite.addTest(doctest.DocFileSuite(fname,module_relative=False)) - # Add docstring tests from module, if given at construction time - for mod in self.dt_modules: - suite.addTest(doctest.DocTestSuite(mod, - test_finder=self.test_finder)) - - #ipshell() # dbg - return suite - -def my_import(name): - """Module importer - taken from the python documentation. - - This function allows importing names with dots in them.""" - - mod = __import__(name) - components = name.split('.') - for comp in components[1:]: - mod = getattr(mod, comp) - return mod - -def makeTestSuite(module_name,dt_files=None,dt_modules=None,idt=True): - """Make a TestSuite object for a given module, specified by name. - - This extracts all the doctests associated with a module using a - DocTestLoader object. - - :Parameters: - - - module_name: a string containing the name of a module with unittests. - - :Keywords: - - dt_files : list of strings - List of names of plain text files to be treated as doctests. - - dt_modules : list of strings - List of names of modules to be scanned for doctests in docstrings. - - idt : bool (True) - If True, return integrated doctests. This means that each filename - listed in dt_files is turned into a *single* unittest, suitable for - running via unittest's runner or Twisted's Trial runner. If false, the - dt_files parameter is returned unmodified, so that other test runners - (such as oilrun) can run the doctests with finer granularity. - """ - - mod = my_import(module_name) - if idt: - suite = IPDocTestLoader(dt_files,dt_modules).loadTestsFromModule(mod) - else: - suite = IPDocTestLoader(None,dt_modules).loadTestsFromModule(mod) - - if idt: - return suite - else: - return suite,dt_files - -# Copied from doctest in py2.5 and modified for our purposes (since they don't -# parametrize what we need) - -# For backward compatibility, a global instance of a DocTestRunner -# class, updated by testmod. -master = None - -def testmod(m=None, name=None, globs=None, verbose=None, - report=True, optionflags=0, extraglobs=None, - raise_on_error=False, exclude_empty=False): - """m=None, name=None, globs=None, verbose=None, report=True, - optionflags=0, extraglobs=None, raise_on_error=False, - exclude_empty=False - - Note: IPython-modified version which loads test finder and runners that - recognize IPython syntax in doctests. - - Test examples in docstrings in functions and classes reachable - from module m (or the current module if m is not supplied), starting - with m.__doc__. - - Also test examples reachable from dict m.__test__ if it exists and is - not None. m.__test__ maps names to functions, classes and strings; - function and class docstrings are tested even if the name is private; - strings are tested directly, as if they were docstrings. - - Return (#failures, #tests). - - See doctest.__doc__ for an overview. - - Optional keyword arg "name" gives the name of the module; by default - use m.__name__. - - Optional keyword arg "globs" gives a dict to be used as the globals - when executing examples; by default, use m.__dict__. A copy of this - dict is actually used for each docstring, so that each docstring's - examples start with a clean slate. - - Optional keyword arg "extraglobs" gives a dictionary that should be - merged into the globals that are used to execute examples. By - default, no extra globals are used. This is new in 2.4. - - Optional keyword arg "verbose" prints lots of stuff if true, prints - only failures if false; by default, it's true iff "-v" is in sys.argv. - - Optional keyword arg "report" prints a summary at the end when true, - else prints nothing at the end. In verbose mode, the summary is - detailed, else very brief (in fact, empty if all tests passed). - - Optional keyword arg "optionflags" or's together module constants, - and defaults to 0. This is new in 2.3. Possible values (see the - docs for details): - - DONT_ACCEPT_TRUE_FOR_1 - DONT_ACCEPT_BLANKLINE - NORMALIZE_WHITESPACE - ELLIPSIS - SKIP - IGNORE_EXCEPTION_DETAIL - REPORT_UDIFF - REPORT_CDIFF - REPORT_NDIFF - REPORT_ONLY_FIRST_FAILURE - - Optional keyword arg "raise_on_error" raises an exception on the - first unexpected exception or failure. This allows failures to be - post-mortem debugged. - - Advanced tomfoolery: testmod runs methods of a local instance of - class doctest.Tester, then merges the results into (or creates) - global Tester instance doctest.master. Methods of doctest.master - can be called directly too, if you want to do something unusual. - Passing report=0 to testmod is especially useful then, to delay - displaying a summary. Invoke doctest.master.summarize(verbose) - when you're done fiddling. - """ - global master - - # If no module was given, then use __main__. - if m is None: - # DWA - m will still be None if this wasn't invoked from the command - # line, in which case the following TypeError is about as good an error - # as we should expect - m = sys.modules.get('__main__') - - # Check that we were actually given a module. - if not inspect.ismodule(m): - raise TypeError("testmod: module required; %r" % (m,)) - - # If no name was given, then use the module's name. - if name is None: - name = m.__name__ - - #---------------------------------------------------------------------- - # fperez - make IPython finder and runner: - # Find, parse, and run all tests in the given module. - finder = DocTestFinder(exclude_empty=exclude_empty, - parser=IPDocTestParser()) - - if raise_on_error: - runner = IPDebugRunner(verbose=verbose, optionflags=optionflags) - else: - runner = IPDocTestRunner(verbose=verbose, optionflags=optionflags, - #checker=IPOutputChecker() # dbg - ) - - # /fperez - end of ipython changes - #---------------------------------------------------------------------- - - for test in finder.find(m, name, globs=globs, extraglobs=extraglobs): - runner.run(test) - - if report: - runner.summarize() - - if master is None: - master = runner - else: - master.merge(runner) - - return runner.failures, runner.tries - - -# Simple testing and example code -if __name__ == "__main__": - - def ipfunc(): - """ - Some ipython tests... - - In [1]: import os - - In [2]: cd / - / - - In [3]: 2+3 - Out[3]: 5 - - In [26]: for i in range(3): - ....: print i, - ....: print i+1, - ....: - 0 1 1 2 2 3 - - - Examples that access the operating system work: - - In [19]: cd /tmp - /tmp - - In [20]: mkdir foo_ipython - - In [21]: cd foo_ipython - /tmp/foo_ipython - - In [23]: !touch bar baz - - # We unfortunately can't just call 'ls' because its output is not - # seen by doctest, since it happens in a separate process - - In [24]: os.listdir('.') - Out[24]: ['bar', 'baz'] - - In [25]: cd /tmp - /tmp - - In [26]: rm -rf foo_ipython - - - It's OK to use '_' for the last result, but do NOT try to use IPython's - numbered history of _NN outputs, since those won't exist under the - doctest environment: - - In [7]: 3+4 - Out[7]: 7 - - In [8]: _+3 - Out[8]: 10 - """ - - def ipfunc_external(): - """ - Tests that must be run in an external process - - - # ipdoctest: EXTERNAL - - In [11]: for i in range(10): - ....: print i, - ....: print i+1, - ....: - 0 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9 10 - - - In [1]: import os - - In [1]: print "hello" - hello - - In [19]: cd /tmp - /tmp - - In [20]: mkdir foo_ipython2 - - In [21]: cd foo_ipython2 - /tmp/foo_ipython2 - - In [23]: !touch bar baz - - In [24]: ls - bar baz - - In [24]: !ls - bar baz - - In [25]: cd /tmp - /tmp - - In [26]: rm -rf foo_ipython2 - """ - - def pyfunc(): - """ - Some pure python tests... - - >>> import os - - >>> 2+3 - 5 - - >>> for i in range(3): - ... print i, - ... print i+1, - ... - 0 1 1 2 2 3 - """ - - # Call the global testmod() just like you would with normal doctest - testmod() diff --git a/IPython/testing/attic/tcommon.py b/IPython/testing/attic/tcommon.py deleted file mode 100644 index 73f7362..0000000 --- a/IPython/testing/attic/tcommon.py +++ /dev/null @@ -1,36 +0,0 @@ -"""Common utilities for testing IPython. - -This file is meant to be used as - -from IPython.testing.tcommon import * - -by any test code. - -While a bit ugly, this helps us keep all testing facilities in one place, and -start coding standalone test scripts easily, which can then be pasted into the -larger test suites without any modifications required. -""" - -# Required modules and packages - -# Standard Python lib -import cPickle as pickle -import doctest -import math -import os -import sys -import unittest - -from pprint import pformat, pprint - -# From the IPython test lib -import tutils -from tutils import fullPath - -try: - import pexpect -except ImportError: - pexpect = None -else: - from IPython.testing.ipdoctest import IPDocTestLoader,makeTestSuite - diff --git a/IPython/testing/attic/testTEMPLATE.py b/IPython/testing/attic/testTEMPLATE.py deleted file mode 100755 index 2484a13..0000000 --- a/IPython/testing/attic/testTEMPLATE.py +++ /dev/null @@ -1,66 +0,0 @@ -#!/usr/bin/env python -# encoding: utf-8 -"""Simple template for unit tests. - -This file should be renamed to - -test_FEATURE.py - -so that it is recognized by the overall test driver (Twisted's 'trial'), which -looks for all test_*.py files in the current directory to extract tests from -them. -""" -__docformat__ = "restructuredtext en" - -#------------------------------------------------------------------------------- -# Copyright (C) 2005 Fernando Perez -# Brian E Granger -# Benjamin Ragan-Kelley -# -# Distributed under the terms of the BSD License. The full license is in -# the file COPYING, distributed as part of this software. -#------------------------------------------------------------------------------- - -#------------------------------------------------------------------------------- -# Imports -#------------------------------------------------------------------------------- - -from IPython.testing import tcommon -from IPython.testing.tcommon import * - -#------------------------------------------------------------------------------- -# Setup for inline and standalone doctests -#------------------------------------------------------------------------------- - - -# If you have standalone doctests in a separate file, set their names in the -# dt_files variable (as a single string or a list thereof). The mkPath call -# forms an absolute path based on the current file, it is not needed if you -# provide the full pahts. -dt_files = fullPath(__file__,[]) - - -# If you have any modules whose docstrings should be scanned for embedded tests -# as examples accorging to standard doctest practice, set them here (as a -# single string or a list thereof): -dt_modules = [] - -#------------------------------------------------------------------------------- -# Regular Unittests -#------------------------------------------------------------------------------- - -class FooTestCase(unittest.TestCase): - def test_foo(self): - pass - -#------------------------------------------------------------------------------- -# Regular Unittests -#------------------------------------------------------------------------------- - -# This ensures that the code will run either standalone as a script, or that it -# can be picked up by Twisted's `trial` test wrapper to run all the tests. -if tcommon.pexpect is not None: - if __name__ == '__main__': - unittest.main(testLoader=IPDocTestLoader(dt_files,dt_modules)) - else: - testSuite = lambda : makeTestSuite(__name__,dt_files,dt_modules) diff --git a/IPython/testing/attic/tstTEMPLATE_doctest.py b/IPython/testing/attic/tstTEMPLATE_doctest.py deleted file mode 100644 index a01b7f5..0000000 --- a/IPython/testing/attic/tstTEMPLATE_doctest.py +++ /dev/null @@ -1,16 +0,0 @@ -"""Run this file with - - irunner --python filename - -to generate valid doctest input. - -NOTE: make sure to ALWAYS have a blank line before comments, otherwise doctest -gets confused.""" - -#--------------------------------------------------------------------------- - -# Setup - all imports are done in tcommon -import tcommon; reload(tcommon) # for interactive use -from IPython.testing.tcommon import * - -# Doctest code begins here diff --git a/IPython/testing/attic/tstTEMPLATE_doctest.txt b/IPython/testing/attic/tstTEMPLATE_doctest.txt deleted file mode 100644 index 9f7d7e5..0000000 --- a/IPython/testing/attic/tstTEMPLATE_doctest.txt +++ /dev/null @@ -1,24 +0,0 @@ -Doctests for the ``XXX`` module -===================================== - -The way doctest loads these, the entire document is applied as a single test -rather than multiple individual ones, unfortunately. - - -Auto-generated tests --------------------- - -The tests below are generated from the companion file -test_toeplitz_doctest.py, which is run via IPython's irunner script to create -valid doctest input automatically. - -# Setup - all imports are done in tcommon ->>> from IPython.testing.tcommon import * - -# Rest of doctest goes here... - - -Manually generated tests ------------------------- - -These are one-off tests written by hand, copied from an interactive prompt. diff --git a/IPython/testing/decorator_msim.py b/IPython/testing/decorator_msim.py deleted file mode 100644 index 8915cf0..0000000 --- a/IPython/testing/decorator_msim.py +++ /dev/null @@ -1,146 +0,0 @@ -## The basic trick is to generate the source code for the decorated function -## with the right signature and to evaluate it. -## Uncomment the statement 'print >> sys.stderr, func_src' in _decorate -## to understand what is going on. - -__all__ = ["decorator", "update_wrapper", "getinfo"] - -import inspect, sys - -def getinfo(func): - """ - Returns an info dictionary containing: - - name (the name of the function : str) - - argnames (the names of the arguments : list) - - defaults (the values of the default arguments : tuple) - - signature (the signature : str) - - doc (the docstring : str) - - module (the module name : str) - - dict (the function __dict__ : str) - - >>> def f(self, x=1, y=2, *args, **kw): pass - - >>> info = getinfo(f) - - >>> info["name"] - 'f' - >>> info["argnames"] - ['self', 'x', 'y', 'args', 'kw'] - - >>> info["defaults"] - (1, 2) - - >>> info["signature"] - 'self, x, y, *args, **kw' - """ - assert inspect.ismethod(func) or inspect.isfunction(func) - regargs, varargs, varkwargs, defaults = inspect.getargspec(func) - argnames = list(regargs) - if varargs: - argnames.append(varargs) - if varkwargs: - argnames.append(varkwargs) - signature = inspect.formatargspec(regargs, varargs, varkwargs, defaults, - formatvalue=lambda value: "")[1:-1] - return dict(name=func.__name__, argnames=argnames, signature=signature, - defaults = func.func_defaults, doc=func.__doc__, - module=func.__module__, dict=func.__dict__, - globals=func.func_globals, closure=func.func_closure) - -def update_wrapper(wrapper, wrapped, create=False): - """ - An improvement over functools.update_wrapper. By default it works the - same, but if the 'create' flag is set, generates a copy of the wrapper - with the right signature and update the copy, not the original. - Moreovoer, 'wrapped' can be a dictionary with keys 'name', 'doc', 'module', - 'dict', 'defaults'. - """ - if isinstance(wrapped, dict): - infodict = wrapped - else: # assume wrapped is a function - infodict = getinfo(wrapped) - assert not '_wrapper_' in infodict["argnames"], \ - '"_wrapper_" is a reserved argument name!' - if create: # create a brand new wrapper with the right signature - src = "lambda %(signature)s: _wrapper_(%(signature)s)" % infodict - # import sys; print >> sys.stderr, src # for debugging purposes - wrapper = eval(src, dict(_wrapper_=wrapper)) - try: - wrapper.__name__ = infodict['name'] - except: # Python version < 2.4 - pass - wrapper.__doc__ = infodict['doc'] - wrapper.__module__ = infodict['module'] - wrapper.__dict__.update(infodict['dict']) - wrapper.func_defaults = infodict['defaults'] - return wrapper - -# the real meat is here -def _decorator(caller, func): - infodict = getinfo(func) - argnames = infodict['argnames'] - assert not ('_call_' in argnames or '_func_' in argnames), \ - 'You cannot use _call_ or _func_ as argument names!' - src = "lambda %(signature)s: _call_(_func_, %(signature)s)" % infodict - dec_func = eval(src, dict(_func_=func, _call_=caller)) - return update_wrapper(dec_func, func) - -def decorator(caller, func=None): - """ - General purpose decorator factory: takes a caller function as - input and returns a decorator with the same attributes. - A caller function is any function like this:: - - def caller(func, *args, **kw): - # do something - return func(*args, **kw) - - Here is an example of usage: - - >>> @decorator - ... def chatty(f, *args, **kw): - ... print "Calling %r" % f.__name__ - ... return f(*args, **kw) - - >>> chatty.__name__ - 'chatty' - - >>> @chatty - ... def f(): pass - ... - >>> f() - Calling 'f' - - For sake of convenience, the decorator factory can also be called with - two arguments. In this casem ``decorator(caller, func)`` is just a - shortcut for ``decorator(caller)(func)``. - """ - if func is None: # return a decorator function - return update_wrapper(lambda f : _decorator(caller, f), caller) - else: # return a decorated function - return _decorator(caller, func) - -if __name__ == "__main__": - import doctest; doctest.testmod() - -####################### LEGALESE ################################## - -## Redistributions of source code must retain the above copyright -## notice, this list of conditions and the following disclaimer. -## Redistributions in bytecode form must reproduce the above copyright -## notice, this list of conditions and the following disclaimer in -## the documentation and/or other materials provided with the -## distribution. - -## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -## HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -## INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -## BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS -## OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -## ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR -## TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE -## USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH -## DAMAGE. diff --git a/IPython/testing/decorators.py b/IPython/testing/decorators.py index 10eefd4..d2ecbd8 100644 --- a/IPython/testing/decorators.py +++ b/IPython/testing/decorators.py @@ -22,7 +22,7 @@ import sys # Third-party imports # This is Michele Simionato's decorator module, also kept verbatim. -from decorator_msim import decorator, update_wrapper +from IPython.external.decorator import decorator, update_wrapper # Grab the numpy-specific decorators which we keep in a file that we # occasionally update from upstream: decorators_numpy.py is an IDENTICAL copy diff --git a/IPython/testing/decorators_numpy.py b/IPython/testing/decorators_numpy.py index 792dd00..540f3c7 100644 --- a/IPython/testing/decorators_numpy.py +++ b/IPython/testing/decorators_numpy.py @@ -45,10 +45,10 @@ def skipif(skip_condition=True, msg=None): Parameters ---------- skip_condition : bool or callable. - Flag to determine whether to skip test. If the condition is a - callable, it is used at runtime to dynamically make the decision. This - is useful for tests that may require costly imports, to delay the cost - until the test suite is actually executed. + Flag to determine whether to skip test. If the condition is a + callable, it is used at runtime to dynamically make the decision. This + is useful for tests that may require costly imports, to delay the cost + until the test suite is actually executed. msg : string Message to give on raising a SkipTest exception diff --git a/IPython/testing/iptest.py b/IPython/testing/iptest.py index 5ea906f..e229787 100644 --- a/IPython/testing/iptest.py +++ b/IPython/testing/iptest.py @@ -24,14 +24,15 @@ import os import os.path as path import sys import subprocess +import tempfile import time import warnings import nose.plugins.builtin from nose.core import TestProgram -from IPython.platutils import find_cmd -from IPython.testing.plugin.ipdoctest import IPythonDoctest +from IPython.utils.platutils import find_cmd +# from IPython.testing.plugin.ipdoctest import IPythonDoctest pjoin = path.join @@ -56,59 +57,82 @@ have_twisted = test_for('twisted') have_foolscap = test_for('foolscap') have_objc = test_for('objc') have_pexpect = test_for('pexpect') +have_gtk = test_for('gtk') +have_gobject = test_for('gobject') + + +def make_exclude(): + + # For the IPythonDoctest plugin, we need to exclude certain patterns that cause + # testing problems. We should strive to minimize the number of skipped + # modules, since this means untested code. As the testing machinery + # solidifies, this list should eventually become empty. + EXCLUDE = [pjoin('IPython', 'external'), + pjoin('IPython', 'frontend', 'process', 'winprocess.py'), + pjoin('IPython_doctest_plugin'), + pjoin('IPython', 'quarantine'), + pjoin('IPython', 'deathrow'), + pjoin('IPython', 'testing', 'attic'), + pjoin('IPython', 'testing', 'tools'), + pjoin('IPython', 'testing', 'mkdoctests'), + pjoin('IPython', 'lib', 'inputhook') + ] + + if not have_wx: + EXCLUDE.append(pjoin('IPython', 'gui')) + EXCLUDE.append(pjoin('IPython', 'frontend', 'wx')) + EXCLUDE.append(pjoin('IPython', 'lib', 'inputhookwx')) + + if not have_gtk or not have_gobject: + EXCLUDE.append(pjoin('IPython', 'lib', 'inputhookgtk')) + + if not have_wx_aui: + EXCLUDE.append(pjoin('IPython', 'gui', 'wx', 'wxIPython')) + + if not have_objc: + EXCLUDE.append(pjoin('IPython', 'frontend', 'cocoa')) + + if not sys.platform == 'win32': + EXCLUDE.append(pjoin('IPython', 'utils', 'platutils_win32')) + + # These have to be skipped on win32 because the use echo, rm, cd, etc. + # See ticket https://bugs.launchpad.net/bugs/366982 + if sys.platform == 'win32': + EXCLUDE.append(pjoin('IPython', 'testing', 'plugin', 'test_exampleip')) + EXCLUDE.append(pjoin('IPython', 'testing', 'plugin', 'dtexample')) + + if not os.name == 'posix': + EXCLUDE.append(pjoin('IPython', 'utils', 'platutils_posix')) + + if not have_pexpect: + EXCLUDE.append(pjoin('IPython', 'scripts', 'irunner')) + + # This is scary. We still have things in frontend and testing that + # are being tested by nose that use twisted. We need to rethink + # how we are isolating dependencies in testing. + if not (have_twisted and have_zi and have_foolscap): + EXCLUDE.append(pjoin('IPython', 'frontend', 'asyncfrontendbase')) + EXCLUDE.append(pjoin('IPython', 'frontend', 'prefilterfrontend')) + EXCLUDE.append(pjoin('IPython', 'frontend', 'frontendbase')) + EXCLUDE.append(pjoin('IPython', 'frontend', 'linefrontendbase')) + EXCLUDE.append(pjoin('IPython', 'frontend', 'tests', + 'test_linefrontend')) + EXCLUDE.append(pjoin('IPython', 'frontend', 'tests', + 'test_frontendbase')) + EXCLUDE.append(pjoin('IPython', 'frontend', 'tests', + 'test_prefilterfrontend')) + EXCLUDE.append(pjoin('IPython', 'frontend', 'tests', + 'test_asyncfrontendbase')), + EXCLUDE.append(pjoin('IPython', 'testing', 'parametric')) + EXCLUDE.append(pjoin('IPython', 'testing', 'util')) + EXCLUDE.append(pjoin('IPython', 'testing', 'tests', + 'test_decorators_trial')) + + # This is needed for the reg-exp to match on win32 in the ipdoctest plugin. + if sys.platform == 'win32': + EXCLUDE = [s.replace('\\','\\\\') for s in EXCLUDE] -# For the IPythonDoctest plugin, we need to exclude certain patterns that cause -# testing problems. We should strive to minimize the number of skipped -# modules, since this means untested code. As the testing machinery -# solidifies, this list should eventually become empty. -EXCLUDE = [pjoin('IPython', 'external'), - pjoin('IPython', 'frontend', 'process', 'winprocess.py'), - pjoin('IPython_doctest_plugin'), - pjoin('IPython', 'Gnuplot'), - pjoin('IPython', 'Extensions', 'ipy_'), - pjoin('IPython', 'Extensions', 'PhysicalQInput'), - pjoin('IPython', 'Extensions', 'PhysicalQInteractive'), - pjoin('IPython', 'Extensions', 'InterpreterPasteInput'), - pjoin('IPython', 'Extensions', 'scitedirector'), - pjoin('IPython', 'Extensions', 'numeric_formats'), - pjoin('IPython', 'testing', 'attic'), - pjoin('IPython', 'testing', 'tutils'), - pjoin('IPython', 'testing', 'tools'), - pjoin('IPython', 'testing', 'mkdoctests'), - ] - -if not have_wx: - EXCLUDE.append(pjoin('IPython', 'Extensions', 'igrid')) - EXCLUDE.append(pjoin('IPython', 'gui')) - EXCLUDE.append(pjoin('IPython', 'frontend', 'wx')) - -if not have_wx_aui: - EXCLUDE.append(pjoin('IPython', 'gui', 'wx', 'wxIPython')) - -if not have_objc: - EXCLUDE.append(pjoin('IPython', 'frontend', 'cocoa')) - -if not have_curses: - EXCLUDE.append(pjoin('IPython', 'Extensions', 'ibrowse')) - -if not sys.platform == 'win32': - EXCLUDE.append(pjoin('IPython', 'platutils_win32')) - -# These have to be skipped on win32 because the use echo, rm, cd, etc. -# See ticket https://bugs.launchpad.net/bugs/366982 -if sys.platform == 'win32': - EXCLUDE.append(pjoin('IPython', 'testing', 'plugin', 'test_exampleip')) - EXCLUDE.append(pjoin('IPython', 'testing', 'plugin', 'dtexample')) - -if not os.name == 'posix': - EXCLUDE.append(pjoin('IPython', 'platutils_posix')) - -if not have_pexpect: - EXCLUDE.append(pjoin('IPython', 'irunner')) - -# This is needed for the reg-exp to match on win32 in the ipdoctest plugin. -if sys.platform == 'win32': - EXCLUDE = [s.replace('\\','\\\\') for s in EXCLUDE] + return EXCLUDE #----------------------------------------------------------------------------- @@ -132,9 +156,9 @@ def run_iptest(): # test suite back into working shape. Our nose # plugin needs to be gone through with a fine # toothed comb to find what is causing the problem. - '--with-ipdoctest', - '--ipdoctest-tests','--ipdoctest-extension=txt', - '--detailed-errors', + # '--with-ipdoctest', + # '--ipdoctest-tests','--ipdoctest-extension=txt', + # '--detailed-errors', # We add --exe because of setuptools' imbecility (it # blindly does chmod +x on ALL files). Nose does the @@ -160,13 +184,13 @@ def run_iptest(): # Construct list of plugins, omitting the existing doctest plugin, which # ours replaces (and extends). - plugins = [IPythonDoctest(EXCLUDE)] + EXCLUDE = make_exclude() + plugins = [] + # plugins = [IPythonDoctest(EXCLUDE)] for p in nose.plugins.builtin.plugins: plug = p() if plug.name == 'doctest': continue - - #print '*** adding plugin:',plug.name # dbg plugins.append(plug) TestProgram(argv=argv,plugins=plugins) @@ -190,61 +214,54 @@ class IPTester(object): # Assemble call self.call_args = self.runner+self.params - def run(self): - """Run the stored commands""" - return subprocess.call(self.call_args) + if sys.platform == 'win32': + def run(self): + """Run the stored commands""" + # On Windows, cd to temporary directory to run tests. Otherwise, + # Twisted's trial may not be able to execute 'trial IPython', since + # it will confuse the IPython module name with the ipython + # execution scripts, because the windows file system isn't case + # sensitive. + # We also use os.system instead of subprocess.call, because I was + # having problems with subprocess and I just don't know enough + # about win32 to debug this reliably. Os.system may be the 'old + # fashioned' way to do it, but it works just fine. If someone + # later can clean this up that's fine, as long as the tests run + # reliably in win32. + curdir = os.getcwd() + os.chdir(tempfile.gettempdir()) + stat = os.system(' '.join(self.call_args)) + os.chdir(curdir) + return stat + else: + def run(self): + """Run the stored commands""" + return subprocess.call(self.call_args) def make_runners(): - """Define the modules and packages that need to be tested. + """Define the top-level packages that need to be tested. """ - - # This omits additional top-level modules that should not be doctested. - # XXX: Shell.py is also ommited because of a bug in the skip_doctest - # decorator. See ticket https://bugs.launchpad.net/bugs/366209 - top_mod = \ - ['background_jobs.py', 'ColorANSI.py', 'completer.py', 'ConfigLoader.py', - 'CrashHandler.py', 'Debugger.py', 'deep_reload.py', 'demo.py', - 'DPyGetOpt.py', 'dtutils.py', 'excolors.py', 'FakeModule.py', - 'generics.py', 'genutils.py', 'history.py', 'hooks.py', 'ipapi.py', - 'iplib.py', 'ipmaker.py', 'ipstruct.py', 'Itpl.py', - 'Logger.py', 'macro.py', 'Magic.py', 'OInspect.py', - 'OutputTrap.py', 'platutils.py', 'prefilter.py', 'Prompts.py', - 'PyColorize.py', 'Release.py', 'rlineimpl.py', 'shadowns.py', - 'shellglobals.py', 'strdispatch.py', 'twshell.py', - 'ultraTB.py', 'upgrade_dir.py', 'usage.py', 'wildcard.py', - # See note above for why this is skipped - # 'Shell.py', - 'winconsole.py'] - - if have_pexpect: - top_mod.append('irunner.py') - if sys.platform == 'win32': - top_mod.append('platutils_win32.py') - elif os.name == 'posix': - top_mod.append('platutils_posix.py') - else: - top_mod.append('platutils_dummy.py') - - # These are tested by nose, so skip IPython.kernel - top_pack = ['config','Extensions','frontend', - 'testing','tests','tools','UserConfig'] + nose_packages = ['config', 'core', 'extensions', + 'frontend', 'lib', + 'scripts', 'testing', 'utils'] + trial_packages = ['kernel'] if have_wx: - top_pack.append('gui') + nose_packages.append('gui') - modules = ['IPython.%s' % m[:-3] for m in top_mod ] - packages = ['IPython.%s' % m for m in top_pack ] + nose_packages = ['IPython.%s' % m for m in nose_packages ] + trial_packages = ['IPython.%s' % m for m in trial_packages ] # Make runners - runners = dict(zip(top_pack, [IPTester(params=v) for v in packages])) + runners = dict() - # Test IPython.kernel using trial if twisted is installed + nose_runners = dict(zip(nose_packages, [IPTester(params=v) for v in nose_packages])) if have_zi and have_twisted and have_foolscap: - runners['trial'] = IPTester('trial',['IPython']) - - runners['modules'] = IPTester(params=modules) + trial_runners = dict(zip(trial_packages, [IPTester('trial',params=v) for v in trial_packages])) + runners.update(nose_runners) + runners.update(trial_runners) return runners @@ -257,7 +274,9 @@ def run_iptestall(): and packages of IPython to be tested each in their own subprocess using nose or twisted.trial appropriately. """ + runners = make_runners() + # Run all test runners, tracking execution time failed = {} t_start = time.time() diff --git a/IPython/testing/mkdoctests.py b/IPython/testing/mkdoctests.py index 142f1cc..4ea9335 100644 --- a/IPython/testing/mkdoctests.py +++ b/IPython/testing/mkdoctests.py @@ -37,8 +37,8 @@ import sys import tempfile # IPython-specific libraries -from IPython import irunner -from IPython.genutils import fatal +from IPython.lib import irunner +from IPython.utils.genutils import fatal class IndentOut(object): """A simple output stream that indents all output by a fixed amount. diff --git a/IPython/testing/plugin/Makefile b/IPython/testing/plugin/Makefile index dd65022..db82075 100644 --- a/IPython/testing/plugin/Makefile +++ b/IPython/testing/plugin/Makefile @@ -34,16 +34,16 @@ deco: $(NOSE0) IPython.testing.decorators magic: plugin - $(NOSE) IPython.Magic + $(NOSE) IPython.core.magic excolors: plugin - $(NOSE) IPython.excolors + $(NOSE) IPython.core.excolors iplib: plugin - $(NOSE) IPython.iplib + $(NOSE) IPython.core.iplib strd: plugin - $(NOSE) IPython.strdispatch + $(NOSE) IPython.core.strdispatch engine: plugin $(NOSE) IPython.kernel diff --git a/IPython/testing/plugin/ipdoctest.py b/IPython/testing/plugin/ipdoctest.py index 3743a08..fb6394d 100644 --- a/IPython/testing/plugin/ipdoctest.py +++ b/IPython/testing/plugin/ipdoctest.py @@ -64,8 +64,8 @@ def default_argv(): # Get the install directory for the user configuration and tell ipython to # use the default profile from there. - from IPython import UserConfig - ipcdir = os.path.dirname(UserConfig.__file__) + from IPython.config import userconfig + ipcdir = os.path.dirname(userconfig.__file__) #ipconf = os.path.join(ipcdir,'ipy_user_conf.py') ipconf = os.path.join(ipcdir,'ipythonrc') #print 'conf:',ipconf # dbg @@ -82,7 +82,7 @@ class py_file_finder(object): self.test_filename = test_filename def __call__(self,name): - from IPython.genutils import get_py_filename + from IPython.utils.genutils import get_py_filename try: return get_py_filename(name) except IOError: @@ -105,7 +105,7 @@ def _run_ns_sync(self,arg_s,runner=None): fname = arg_s finder = py_file_finder(fname) - out = _ip.IP.magic_run_ori(arg_s,runner,finder) + out = _ip.magic_run_ori(arg_s,runner,finder) # Simliarly, there is no test_globs when a test is NOT a doctest if hasattr(_run_ns_sync,'test_globs'): @@ -164,14 +164,15 @@ def start_ipython(): import new import IPython - + from IPython.core import ipapi + def xsys(cmd): """Execute a command and print its output. This is just a convenience function to replace the IPython system call with one that is more doctest-friendly. """ - cmd = _ip.IP.var_expand(cmd,depth=1) + cmd = _ip.var_expand(cmd,depth=1) sys.stdout.write(commands.getoutput(cmd)) sys.stdout.flush() @@ -183,8 +184,7 @@ def start_ipython(): argv = default_argv() # Start IPython instance. We customize it to start with minimal frills. - user_ns,global_ns = IPython.ipapi.make_user_namespaces(ipnsdict(),dict()) - IPython.Shell.IPShell(argv,user_ns,global_ns) + IPython.shell.IPShell(argv,ipnsdict(),global_ns) # Deactivate the various python system hooks added by ipython for # interactive convenience so we don't confuse the doctest system @@ -194,7 +194,7 @@ def start_ipython(): # So that ipython magics and aliases can be doctested (they work by making # a call into a global _ip object) - _ip = IPython.ipapi.get() + _ip = ipapi.get() __builtin__._ip = _ip # Modify the IPython system call with one that uses getoutput, so that we @@ -203,16 +203,16 @@ def start_ipython(): _ip.system = xsys # Also patch our %run function in. - im = new.instancemethod(_run_ns_sync,_ip.IP, _ip.IP.__class__) - _ip.IP.magic_run_ori = _ip.IP.magic_run - _ip.IP.magic_run = im + im = new.instancemethod(_run_ns_sync,_ip, _ip.__class__) + _ip.magic_run_ori = _ip.magic_run + _ip.magic_run = im # XXX - For some very bizarre reason, the loading of %history by default is # failing. This needs to be fixed later, but for now at least this ensures # that tests that use %hist run to completion. - from IPython import history + from IPython.core import history history.init_ipython(_ip) - if not hasattr(_ip.IP,'magic_history'): + if not hasattr(_ip,'magic_history'): raise RuntimeError("Can't load magics, aborting") @@ -436,10 +436,32 @@ class DocTestCase(doctests.DocTestCase): # for IPython examples *only*, we swap the globals with the ipython # namespace, after updating it with the globals (which doctest # fills with the necessary info from the module being tested). - _ip.IP.user_ns.update(self._dt_test.globs) - self._dt_test.globs = _ip.IP.user_ns - - doctests.DocTestCase.setUp(self) + _ip.user_ns.update(self._dt_test.globs) + self._dt_test.globs = _ip.user_ns + + super(DocTestCase, self).setUp() + + def tearDown(self): + # XXX - fperez: I am not sure if this is truly a bug in nose 0.11, but + # it does look like one to me: its tearDown method tries to run + # + # delattr(__builtin__, self._result_var) + # + # without checking that the attribute really is there; it implicitly + # assumes it should have been set via displayhook. But if the + # displayhook was never called, this doesn't necessarily happen. I + # haven't been able to find a little self-contained example outside of + # ipython that would show the problem so I can report it to the nose + # team, but it does happen a lot in our code. + # + # So here, we just protect as narrowly as possible by trapping an + # attribute error whose message would be the name of self._result_var, + # and letting any other error propagate. + try: + super(DocTestCase, self).tearDown() + except AttributeError, exc: + if exc.args[0] != self._result_var: + raise # A simple subclassing of the original with a different class name, so we can @@ -516,7 +538,7 @@ class IPDocTestParser(doctest.DocTestParser): # and turned into lines, so it looks to the parser like regular user # input for lnum,line in enumerate(source.strip().splitlines()): - newline(_ip.IP.prefilter(line,lnum>0)) + newline(_ip.prefilter(line,lnum>0)) newline('') # ensure a closing newline, needed by doctest #print "PYSRC:", '\n'.join(out) # dbg return '\n'.join(out) diff --git a/IPython/testing/plugin/show_refs.py b/IPython/testing/plugin/show_refs.py index cbba10f..d829644 100644 --- a/IPython/testing/plugin/show_refs.py +++ b/IPython/testing/plugin/show_refs.py @@ -10,9 +10,10 @@ class C(object): pass #print 'deleting object...' # dbg -c = C() +if __name__ == '__main__': + c = C() -c_refs = gc.get_referrers(c) -ref_ids = map(id,c_refs) + c_refs = gc.get_referrers(c) + ref_ids = map(id,c_refs) -print 'c referrers:',map(type,c_refs) + print 'c referrers:',map(type,c_refs) diff --git a/IPython/testing/tools.py b/IPython/testing/tools.py index c8c806f..89d1504 100644 --- a/IPython/testing/tools.py +++ b/IPython/testing/tools.py @@ -32,7 +32,7 @@ import sys import nose.tools as nt -from IPython.tools import utils +from IPython.utils import genutils from IPython.testing import decorators as dec #----------------------------------------------------------------------------- @@ -85,7 +85,7 @@ def full_path(startPath,files): ['/a.txt'] """ - files = utils.list_strings(files) + files = genutils.list_strings(files) base = os.path.split(startPath)[0] return [ os.path.join(base,f) for f in files ] diff --git a/IPython/testing/tutils.py b/IPython/testing/tutils.py deleted file mode 100644 index 43e7ba9..0000000 --- a/IPython/testing/tutils.py +++ /dev/null @@ -1,87 +0,0 @@ -"""DEPRECATED - use IPython.testing.util instead. - -Utilities for testing code. -""" - -############################################################################# - -# This was old testing code we never really used in IPython. The pieces of -# testing machinery from snakeoil that were good have already been merged into -# the nose plugin, so this can be taken away soon. Leave a warning for now, -# we'll remove it in a later release (around 0.10 or so). - -from warnings import warn -warn('This will be removed soon. Use IPython.testing.util instead', - DeprecationWarning) - -############################################################################# - -# Required modules and packages - -# Standard Python lib -import os -import sys - -# From this project -from IPython.tools import utils - -# path to our own installation, so we can find source files under this. -TEST_PATH = os.path.dirname(os.path.abspath(__file__)) - -# Global flag, used by vprint -VERBOSE = '-v' in sys.argv or '--verbose' in sys.argv - -########################################################################## -# Code begins - -# Some utility functions -def vprint(*args): - """Print-like function which relies on a global VERBOSE flag.""" - if not VERBOSE: - return - - write = sys.stdout.write - for item in args: - write(str(item)) - write('\n') - sys.stdout.flush() - -def test_path(path): - """Return a path as a subdir of the test package. - - This finds the correct path of the test package on disk, and prepends it - to the input path.""" - - return os.path.join(TEST_PATH,path) - -def fullPath(startPath,files): - """Make full paths for all the listed files, based on startPath. - - Only the base part of startPath is kept, since this routine is typically - used with a script's __file__ variable as startPath. The base of startPath - is then prepended to all the listed files, forming the output list. - - :Parameters: - startPath : string - Initial path to use as the base for the results. This path is split - using os.path.split() and only its first component is kept. - - files : string or list - One or more files. - - :Examples: - - >>> fullPath('/foo/bar.py',['a.txt','b.txt']) - ['/foo/a.txt', '/foo/b.txt'] - - >>> fullPath('/foo',['a.txt','b.txt']) - ['/a.txt', '/b.txt'] - - If a single file is given, the output is still a list: - >>> fullPath('/foo','a.txt') - ['/a.txt'] - """ - - files = utils.list_strings(files) - base = os.path.split(startPath)[0] - return [ os.path.join(base,f) for f in files ] diff --git a/IPython/testing/util.py b/IPython/testing/util.py index 7036650..5c4253f 100644 --- a/IPython/testing/util.py +++ b/IPython/testing/util.py @@ -21,7 +21,7 @@ from twisted.internet import defer class DeferredTestCase(unittest.TestCase): def assertDeferredEquals(self, deferred, expectedResult, - chainDeferred=None): + chainDeferred=None): """Calls assertEquals on the result of the deferred and expectedResult. chainDeferred can be used to pass in previous Deferred objects that @@ -32,7 +32,7 @@ class DeferredTestCase(unittest.TestCase): if chainDeferred is None: chainDeferred = defer.succeed(None) - + def gotResult(actualResult): self.assertEquals(actualResult, expectedResult) @@ -41,7 +41,7 @@ class DeferredTestCase(unittest.TestCase): return chainDeferred.addCallback(lambda _: deferred) def assertDeferredRaises(self, deferred, expectedException, - chainDeferred=None): + chainDeferred=None): """Calls assertRaises on the Failure of the deferred and expectedException. chainDeferred can be used to pass in previous Deferred objects that diff --git a/IPython/tools/tests/test_tools_utils.txt b/IPython/tools/tests/test_tools_utils.txt deleted file mode 100644 index 60235bc..0000000 --- a/IPython/tools/tests/test_tools_utils.txt +++ /dev/null @@ -1,17 +0,0 @@ -========================================= - Doctests for the ``tools.utils`` module -========================================= - -The way doctest loads these, the entire document is applied as a single test -rather than multiple individual ones, unfortunately:: - - >>> from IPython.tools import utils - - # Some other tests for utils - - >>> utils.marquee('Testing marquee') - '****************************** Testing marquee ******************************' - - >>> utils.marquee('Another test',30,'.') - '........ Another test ........' - diff --git a/IPython/tools/utils.py b/IPython/tools/utils.py deleted file mode 100644 index f34cf5a..0000000 --- a/IPython/tools/utils.py +++ /dev/null @@ -1,128 +0,0 @@ -# encoding: utf-8 -"""Generic utilities for use by IPython's various subsystems. -""" - -__docformat__ = "restructuredtext en" - -#------------------------------------------------------------------------------- -# Copyright (C) 2006 Fernando Perez -# Brian E Granger -# -# Distributed under the terms of the BSD License. The full license is in -# the file COPYING, distributed as part of this software. -#------------------------------------------------------------------------------- - -#--------------------------------------------------------------------------- -# Stdlib imports -#--------------------------------------------------------------------------- - -import os -import sys - -#--------------------------------------------------------------------------- -# Other IPython utilities -#--------------------------------------------------------------------------- - - -#--------------------------------------------------------------------------- -# Normal code begins -#--------------------------------------------------------------------------- - -def extractVars(*names,**kw): - """Extract a set of variables by name from another frame. - - :Parameters: - - `*names`: strings - One or more variable names which will be extracted from the caller's - frame. - - :Keywords: - - `depth`: integer (0) - How many frames in the stack to walk when looking for your variables. - - - Examples: - - In [2]: def func(x): - ...: y = 1 - ...: print extractVars('x','y') - ...: - - In [3]: func('hello') - {'y': 1, 'x': 'hello'} - """ - - depth = kw.get('depth',0) - - callerNS = sys._getframe(depth+1).f_locals - return dict((k,callerNS[k]) for k in names) - - -def extractVarsAbove(*names): - """Extract a set of variables by name from another frame. - - Similar to extractVars(), but with a specified depth of 1, so that names - are exctracted exactly from above the caller. - - This is simply a convenience function so that the very common case (for us) - of skipping exactly 1 frame doesn't have to construct a special dict for - keyword passing.""" - - callerNS = sys._getframe(2).f_locals - return dict((k,callerNS[k]) for k in names) - -def shexp(s): - """Expand $VARS and ~names in a string, like a shell - - :Examples: - - In [2]: os.environ['FOO']='test' - - In [3]: shexp('variable FOO is $FOO') - Out[3]: 'variable FOO is test' - """ - return os.path.expandvars(os.path.expanduser(s)) - - -def list_strings(arg): - """Always return a list of strings, given a string or list of strings - as input. - - :Examples: - - In [7]: list_strings('A single string') - Out[7]: ['A single string'] - - In [8]: list_strings(['A single string in a list']) - Out[8]: ['A single string in a list'] - - In [9]: list_strings(['A','list','of','strings']) - Out[9]: ['A', 'list', 'of', 'strings'] - """ - - if isinstance(arg,basestring): return [arg] - else: return arg - -def marquee(txt='',width=78,mark='*'): - """Return the input string centered in a 'marquee'. - - :Examples: - - In [16]: marquee('A test',40) - Out[16]: '**************** A test ****************' - - In [17]: marquee('A test',40,'-') - Out[17]: '---------------- A test ----------------' - - In [18]: marquee('A test',40,' ') - Out[18]: ' A test ' - - """ - if not txt: - return (mark*width)[:width] - nmark = (width-len(txt)-2)/len(mark)/2 - if nmark < 0: nmark =0 - marks = mark*nmark - return '%s %s %s' % (marks,txt,marks) - - diff --git a/IPython/ultraTB.py b/IPython/ultraTB.py deleted file mode 100644 index 1dcbb2f..0000000 --- a/IPython/ultraTB.py +++ /dev/null @@ -1,1056 +0,0 @@ -# -*- coding: utf-8 -*- -""" -ultraTB.py -- Spice up your tracebacks! - -* ColorTB -I've always found it a bit hard to visually parse tracebacks in Python. The -ColorTB class is a solution to that problem. It colors the different parts of a -traceback in a manner similar to what you would expect from a syntax-highlighting -text editor. - -Installation instructions for ColorTB: - import sys,ultraTB - sys.excepthook = ultraTB.ColorTB() - -* VerboseTB -I've also included a port of Ka-Ping Yee's "cgitb.py" that produces all kinds -of useful info when a traceback occurs. Ping originally had it spit out HTML -and intended it for CGI programmers, but why should they have all the fun? I -altered it to spit out colored text to the terminal. It's a bit overwhelming, -but kind of neat, and maybe useful for long-running programs that you believe -are bug-free. If a crash *does* occur in that type of program you want details. -Give it a shot--you'll love it or you'll hate it. - -Note: - - The Verbose mode prints the variables currently visible where the exception - happened (shortening their strings if too long). This can potentially be - very slow, if you happen to have a huge data structure whose string - representation is complex to compute. Your computer may appear to freeze for - a while with cpu usage at 100%. If this occurs, you can cancel the traceback - with Ctrl-C (maybe hitting it more than once). - - If you encounter this kind of situation often, you may want to use the - Verbose_novars mode instead of the regular Verbose, which avoids formatting - variables (but otherwise includes the information and context given by - Verbose). - - -Installation instructions for ColorTB: - import sys,ultraTB - sys.excepthook = ultraTB.VerboseTB() - -Note: Much of the code in this module was lifted verbatim from the standard -library module 'traceback.py' and Ka-Ping Yee's 'cgitb.py'. - -* Color schemes -The colors are defined in the class TBTools through the use of the -ColorSchemeTable class. Currently the following exist: - - - NoColor: allows all of this module to be used in any terminal (the color - escapes are just dummy blank strings). - - - Linux: is meant to look good in a terminal like the Linux console (black - or very dark background). - - - LightBG: similar to Linux but swaps dark/light colors to be more readable - in light background terminals. - -You can implement other color schemes easily, the syntax is fairly -self-explanatory. Please send back new schemes you develop to the author for -possible inclusion in future releases. -""" - -#***************************************************************************** -# Copyright (C) 2001 Nathaniel Gray -# Copyright (C) 2001-2004 Fernando Perez -# -# Distributed under the terms of the BSD License. The full license is in -# the file COPYING, distributed as part of this software. -#***************************************************************************** - -# Required modules -import inspect -import keyword -import linecache -import os -import pydoc -import re -import string -import sys -import time -import tokenize -import traceback -import types - -# For purposes of monkeypatching inspect to fix a bug in it. -from inspect import getsourcefile, getfile, getmodule,\ - ismodule, isclass, ismethod, isfunction, istraceback, isframe, iscode - - -# IPython's own modules -# Modified pdb which doesn't damage IPython's readline handling -from IPython import Debugger, PyColorize, ipapi -from IPython.ipstruct import Struct -from IPython.excolors import exception_colors -from IPython.genutils import Term,uniq_stable,error,info - -# Globals -# amount of space to put line numbers before verbose tracebacks -INDENT_SIZE = 8 - -# Default color scheme. This is used, for example, by the traceback -# formatter. When running in an actual IPython instance, the user's rc.colors -# value is used, but havinga module global makes this functionality available -# to users of ultraTB who are NOT running inside ipython. -DEFAULT_SCHEME = 'NoColor' - -#--------------------------------------------------------------------------- -# Code begins - -# Utility functions -def inspect_error(): - """Print a message about internal inspect errors. - - These are unfortunately quite common.""" - - error('Internal Python error in the inspect module.\n' - 'Below is the traceback from this internal error.\n') - - -def findsource(object): - """Return the entire source file and starting line number for an object. - - The argument may be a module, class, method, function, traceback, frame, - or code object. The source code is returned as a list of all the lines - in the file and the line number indexes a line in that list. An IOError - is raised if the source code cannot be retrieved. - - FIXED version with which we monkeypatch the stdlib to work around a bug.""" - - file = getsourcefile(object) or getfile(object) - # If the object is a frame, then trying to get the globals dict from its - # module won't work. Instead, the frame object itself has the globals - # dictionary. - globals_dict = None - if inspect.isframe(object): - # XXX: can this ever be false? - globals_dict = object.f_globals - else: - module = getmodule(object, file) - if module: - globals_dict = module.__dict__ - lines = linecache.getlines(file, globals_dict) - if not lines: - raise IOError('could not get source code') - - if ismodule(object): - return lines, 0 - - if isclass(object): - name = object.__name__ - pat = re.compile(r'^(\s*)class\s*' + name + r'\b') - # make some effort to find the best matching class definition: - # use the one with the least indentation, which is the one - # that's most probably not inside a function definition. - candidates = [] - for i in range(len(lines)): - match = pat.match(lines[i]) - if match: - # if it's at toplevel, it's already the best one - if lines[i][0] == 'c': - return lines, i - # else add whitespace to candidate list - candidates.append((match.group(1), i)) - if candidates: - # this will sort by whitespace, and by line number, - # less whitespace first - candidates.sort() - return lines, candidates[0][1] - else: - raise IOError('could not find class definition') - - if ismethod(object): - object = object.im_func - if isfunction(object): - object = object.func_code - if istraceback(object): - object = object.tb_frame - if isframe(object): - object = object.f_code - if iscode(object): - if not hasattr(object, 'co_firstlineno'): - raise IOError('could not find function definition') - pat = re.compile(r'^(\s*def\s)|(.*(? 0: - if pmatch(lines[lnum]): break - lnum -= 1 - - return lines, lnum - raise IOError('could not find code object') - -# Monkeypatch inspect to apply our bugfix. This code only works with py25 -if sys.version_info[:2] >= (2,5): - inspect.findsource = findsource - -def fix_frame_records_filenames(records): - """Try to fix the filenames in each record from inspect.getinnerframes(). - - Particularly, modules loaded from within zip files have useless filenames - attached to their code object, and inspect.getinnerframes() just uses it. - """ - fixed_records = [] - for frame, filename, line_no, func_name, lines, index in records: - # Look inside the frame's globals dictionary for __file__, which should - # be better. - better_fn = frame.f_globals.get('__file__', None) - if isinstance(better_fn, str): - # Check the type just in case someone did something weird with - # __file__. It might also be None if the error occurred during - # import. - filename = better_fn - fixed_records.append((frame, filename, line_no, func_name, lines, index)) - return fixed_records - - -def _fixed_getinnerframes(etb, context=1,tb_offset=0): - import linecache - LNUM_POS, LINES_POS, INDEX_POS = 2, 4, 5 - - records = fix_frame_records_filenames(inspect.getinnerframes(etb, context)) - - # If the error is at the console, don't build any context, since it would - # otherwise produce 5 blank lines printed out (there is no file at the - # console) - rec_check = records[tb_offset:] - try: - rname = rec_check[0][1] - if rname == '' or rname.endswith(''): - return rec_check - except IndexError: - pass - - aux = traceback.extract_tb(etb) - assert len(records) == len(aux) - for i, (file, lnum, _, _) in zip(range(len(records)), aux): - maybeStart = lnum-1 - context//2 - start = max(maybeStart, 0) - end = start + context - lines = linecache.getlines(file)[start:end] - # pad with empty lines if necessary - if maybeStart < 0: - lines = (['\n'] * -maybeStart) + lines - if len(lines) < context: - lines += ['\n'] * (context - len(lines)) - buf = list(records[i]) - buf[LNUM_POS] = lnum - buf[INDEX_POS] = lnum - 1 - start - buf[LINES_POS] = lines - records[i] = tuple(buf) - return records[tb_offset:] - -# Helper function -- largely belongs to VerboseTB, but we need the same -# functionality to produce a pseudo verbose TB for SyntaxErrors, so that they -# can be recognized properly by ipython.el's py-traceback-line-re -# (SyntaxErrors have to be treated specially because they have no traceback) - -_parser = PyColorize.Parser() - -def _formatTracebackLines(lnum, index, lines, Colors, lvals=None,scheme=None): - numbers_width = INDENT_SIZE - 1 - res = [] - i = lnum - index - - # This lets us get fully syntax-highlighted tracebacks. - if scheme is None: - try: - scheme = ipapi.get().IP.rc.colors - except: - scheme = DEFAULT_SCHEME - _line_format = _parser.format2 - - for line in lines: - new_line, err = _line_format(line,'str',scheme) - if not err: line = new_line - - if i == lnum: - # This is the line with the error - pad = numbers_width - len(str(i)) - if pad >= 3: - marker = '-'*(pad-3) + '-> ' - elif pad == 2: - marker = '> ' - elif pad == 1: - marker = '>' - else: - marker = '' - num = marker + str(i) - line = '%s%s%s %s%s' %(Colors.linenoEm, num, - Colors.line, line, Colors.Normal) - else: - num = '%*s' % (numbers_width,i) - line = '%s%s%s %s' %(Colors.lineno, num, - Colors.Normal, line) - - res.append(line) - if lvals and i == lnum: - res.append(lvals + '\n') - i = i + 1 - return res - - -#--------------------------------------------------------------------------- -# Module classes -class TBTools: - """Basic tools used by all traceback printer classes.""" - - def __init__(self,color_scheme = 'NoColor',call_pdb=False): - # Whether to call the interactive pdb debugger after printing - # tracebacks or not - self.call_pdb = call_pdb - - # Create color table - self.color_scheme_table = exception_colors() - - self.set_colors(color_scheme) - self.old_scheme = color_scheme # save initial value for toggles - - if call_pdb: - self.pdb = Debugger.Pdb(self.color_scheme_table.active_scheme_name) - else: - self.pdb = None - - def set_colors(self,*args,**kw): - """Shorthand access to the color table scheme selector method.""" - - # Set own color table - self.color_scheme_table.set_active_scheme(*args,**kw) - # for convenience, set Colors to the active scheme - self.Colors = self.color_scheme_table.active_colors - # Also set colors of debugger - if hasattr(self,'pdb') and self.pdb is not None: - self.pdb.set_colors(*args,**kw) - - def color_toggle(self): - """Toggle between the currently active color scheme and NoColor.""" - - if self.color_scheme_table.active_scheme_name == 'NoColor': - self.color_scheme_table.set_active_scheme(self.old_scheme) - self.Colors = self.color_scheme_table.active_colors - else: - self.old_scheme = self.color_scheme_table.active_scheme_name - self.color_scheme_table.set_active_scheme('NoColor') - self.Colors = self.color_scheme_table.active_colors - -#--------------------------------------------------------------------------- -class ListTB(TBTools): - """Print traceback information from a traceback list, with optional color. - - Calling: requires 3 arguments: - (etype, evalue, elist) - as would be obtained by: - etype, evalue, tb = sys.exc_info() - if tb: - elist = traceback.extract_tb(tb) - else: - elist = None - - It can thus be used by programs which need to process the traceback before - printing (such as console replacements based on the code module from the - standard library). - - Because they are meant to be called without a full traceback (only a - list), instances of this class can't call the interactive pdb debugger.""" - - def __init__(self,color_scheme = 'NoColor'): - TBTools.__init__(self,color_scheme = color_scheme,call_pdb=0) - - def __call__(self, etype, value, elist): - Term.cout.flush() - print >> Term.cerr, self.text(etype,value,elist) - Term.cerr.flush() - - def text(self,etype, value, elist,context=5): - """Return a color formatted string with the traceback info.""" - - Colors = self.Colors - out_string = ['%s%s%s\n' % (Colors.topline,'-'*60,Colors.Normal)] - if elist: - out_string.append('Traceback %s(most recent call last)%s:' % \ - (Colors.normalEm, Colors.Normal) + '\n') - out_string.extend(self._format_list(elist)) - lines = self._format_exception_only(etype, value) - for line in lines[:-1]: - out_string.append(" "+line) - out_string.append(lines[-1]) - return ''.join(out_string) - - def _format_list(self, extracted_list): - """Format a list of traceback entry tuples for printing. - - Given a list of tuples as returned by extract_tb() or - extract_stack(), return a list of strings ready for printing. - Each string in the resulting list corresponds to the item with the - same index in the argument list. Each string ends in a newline; - the strings may contain internal newlines as well, for those items - whose source text line is not None. - - Lifted almost verbatim from traceback.py - """ - - Colors = self.Colors - list = [] - for filename, lineno, name, line in extracted_list[:-1]: - item = ' File %s"%s"%s, line %s%d%s, in %s%s%s\n' % \ - (Colors.filename, filename, Colors.Normal, - Colors.lineno, lineno, Colors.Normal, - Colors.name, name, Colors.Normal) - if line: - item = item + ' %s\n' % line.strip() - list.append(item) - # Emphasize the last entry - filename, lineno, name, line = extracted_list[-1] - item = '%s File %s"%s"%s, line %s%d%s, in %s%s%s%s\n' % \ - (Colors.normalEm, - Colors.filenameEm, filename, Colors.normalEm, - Colors.linenoEm, lineno, Colors.normalEm, - Colors.nameEm, name, Colors.normalEm, - Colors.Normal) - if line: - item = item + '%s %s%s\n' % (Colors.line, line.strip(), - Colors.Normal) - list.append(item) - return list - - def _format_exception_only(self, etype, value): - """Format the exception part of a traceback. - - The arguments are the exception type and value such as given by - sys.exc_info()[:2]. The return value is a list of strings, each ending - in a newline. Normally, the list contains a single string; however, - for SyntaxError exceptions, it contains several lines that (when - printed) display detailed information about where the syntax error - occurred. The message indicating which exception occurred is the - always last string in the list. - - Also lifted nearly verbatim from traceback.py - """ - - have_filedata = False - Colors = self.Colors - list = [] - try: - stype = Colors.excName + etype.__name__ + Colors.Normal - except AttributeError: - stype = etype # String exceptions don't get special coloring - if value is None: - list.append( str(stype) + '\n') - else: - if etype is SyntaxError: - try: - msg, (filename, lineno, offset, line) = value - except: - have_filedata = False - else: - have_filedata = True - #print 'filename is',filename # dbg - if not filename: filename = "" - list.append('%s File %s"%s"%s, line %s%d%s\n' % \ - (Colors.normalEm, - Colors.filenameEm, filename, Colors.normalEm, - Colors.linenoEm, lineno, Colors.Normal )) - if line is not None: - i = 0 - while i < len(line) and line[i].isspace(): - i = i+1 - list.append('%s %s%s\n' % (Colors.line, - line.strip(), - Colors.Normal)) - if offset is not None: - s = ' ' - for c in line[i:offset-1]: - if c.isspace(): - s = s + c - else: - s = s + ' ' - list.append('%s%s^%s\n' % (Colors.caret, s, - Colors.Normal) ) - value = msg - s = self._some_str(value) - if s: - list.append('%s%s:%s %s\n' % (str(stype), Colors.excName, - Colors.Normal, s)) - else: - list.append('%s\n' % str(stype)) - - # vds:>> - if have_filedata: - ipapi.get().IP.hooks.synchronize_with_editor(filename, lineno, 0) - # vds:<< - - return list - - def _some_str(self, value): - # Lifted from traceback.py - try: - return str(value) - except: - return '' % type(value).__name__ - -#---------------------------------------------------------------------------- -class VerboseTB(TBTools): - """A port of Ka-Ping Yee's cgitb.py module that outputs color text instead - of HTML. Requires inspect and pydoc. Crazy, man. - - Modified version which optionally strips the topmost entries from the - traceback, to be used with alternate interpreters (because their own code - would appear in the traceback).""" - - def __init__(self,color_scheme = 'Linux',tb_offset=0,long_header=0, - call_pdb = 0, include_vars=1): - """Specify traceback offset, headers and color scheme. - - Define how many frames to drop from the tracebacks. Calling it with - tb_offset=1 allows use of this handler in interpreters which will have - their own code at the top of the traceback (VerboseTB will first - remove that frame before printing the traceback info).""" - TBTools.__init__(self,color_scheme=color_scheme,call_pdb=call_pdb) - self.tb_offset = tb_offset - self.long_header = long_header - self.include_vars = include_vars - - def text(self, etype, evalue, etb, context=5): - """Return a nice text document describing the traceback.""" - - # some locals - try: - etype = etype.__name__ - except AttributeError: - pass - Colors = self.Colors # just a shorthand + quicker name lookup - ColorsNormal = Colors.Normal # used a lot - col_scheme = self.color_scheme_table.active_scheme_name - indent = ' '*INDENT_SIZE - em_normal = '%s\n%s%s' % (Colors.valEm, indent,ColorsNormal) - undefined = '%sundefined%s' % (Colors.em, ColorsNormal) - exc = '%s%s%s' % (Colors.excName,etype,ColorsNormal) - - # some internal-use functions - def text_repr(value): - """Hopefully pretty robust repr equivalent.""" - # this is pretty horrible but should always return *something* - try: - return pydoc.text.repr(value) - except KeyboardInterrupt: - raise - except: - try: - return repr(value) - except KeyboardInterrupt: - raise - except: - try: - # all still in an except block so we catch - # getattr raising - name = getattr(value, '__name__', None) - if name: - # ick, recursion - return text_repr(name) - klass = getattr(value, '__class__', None) - if klass: - return '%s instance' % text_repr(klass) - except KeyboardInterrupt: - raise - except: - return 'UNRECOVERABLE REPR FAILURE' - def eqrepr(value, repr=text_repr): return '=%s' % repr(value) - def nullrepr(value, repr=text_repr): return '' - - # meat of the code begins - try: - etype = etype.__name__ - except AttributeError: - pass - - if self.long_header: - # Header with the exception type, python version, and date - pyver = 'Python ' + string.split(sys.version)[0] + ': ' + sys.executable - date = time.ctime(time.time()) - - head = '%s%s%s\n%s%s%s\n%s' % (Colors.topline, '-'*75, ColorsNormal, - exc, ' '*(75-len(str(etype))-len(pyver)), - pyver, string.rjust(date, 75) ) - head += "\nA problem occured executing Python code. Here is the sequence of function"\ - "\ncalls leading up to the error, with the most recent (innermost) call last." - else: - # Simplified header - head = '%s%s%s\n%s%s' % (Colors.topline, '-'*75, ColorsNormal,exc, - string.rjust('Traceback (most recent call last)', - 75 - len(str(etype)) ) ) - frames = [] - # Flush cache before calling inspect. This helps alleviate some of the - # problems with python 2.3's inspect.py. - linecache.checkcache() - # Drop topmost frames if requested - try: - # Try the default getinnerframes and Alex's: Alex's fixes some - # problems, but it generates empty tracebacks for console errors - # (5 blanks lines) where none should be returned. - #records = inspect.getinnerframes(etb, context)[self.tb_offset:] - #print 'python records:', records # dbg - records = _fixed_getinnerframes(etb, context,self.tb_offset) - #print 'alex records:', records # dbg - except: - - # FIXME: I've been getting many crash reports from python 2.3 - # users, traceable to inspect.py. If I can find a small test-case - # to reproduce this, I should either write a better workaround or - # file a bug report against inspect (if that's the real problem). - # So far, I haven't been able to find an isolated example to - # reproduce the problem. - inspect_error() - traceback.print_exc(file=Term.cerr) - info('\nUnfortunately, your original traceback can not be constructed.\n') - return '' - - # build some color string templates outside these nested loops - tpl_link = '%s%%s%s' % (Colors.filenameEm,ColorsNormal) - tpl_call = 'in %s%%s%s%%s%s' % (Colors.vName, Colors.valEm, - ColorsNormal) - tpl_call_fail = 'in %s%%s%s(***failed resolving arguments***)%s' % \ - (Colors.vName, Colors.valEm, ColorsNormal) - tpl_local_var = '%s%%s%s' % (Colors.vName, ColorsNormal) - tpl_global_var = '%sglobal%s %s%%s%s' % (Colors.em, ColorsNormal, - Colors.vName, ColorsNormal) - tpl_name_val = '%%s %s= %%s%s' % (Colors.valEm, ColorsNormal) - tpl_line = '%s%%s%s %%s' % (Colors.lineno, ColorsNormal) - tpl_line_em = '%s%%s%s %%s%s' % (Colors.linenoEm,Colors.line, - ColorsNormal) - - # now, loop over all records printing context and info - abspath = os.path.abspath - for frame, file, lnum, func, lines, index in records: - #print '*** record:',file,lnum,func,lines,index # dbg - try: - file = file and abspath(file) or '?' - except OSError: - # if file is '' or something not in the filesystem, - # the abspath call will throw an OSError. Just ignore it and - # keep the original file string. - pass - link = tpl_link % file - try: - args, varargs, varkw, locals = inspect.getargvalues(frame) - except: - # This can happen due to a bug in python2.3. We should be - # able to remove this try/except when 2.4 becomes a - # requirement. Bug details at http://python.org/sf/1005466 - inspect_error() - traceback.print_exc(file=Term.cerr) - info("\nIPython's exception reporting continues...\n") - - if func == '?': - call = '' - else: - # Decide whether to include variable details or not - var_repr = self.include_vars and eqrepr or nullrepr - try: - call = tpl_call % (func,inspect.formatargvalues(args, - varargs, varkw, - locals,formatvalue=var_repr)) - except KeyError: - # Very odd crash from inspect.formatargvalues(). The - # scenario under which it appeared was a call to - # view(array,scale) in NumTut.view.view(), where scale had - # been defined as a scalar (it should be a tuple). Somehow - # inspect messes up resolving the argument list of view() - # and barfs out. At some point I should dig into this one - # and file a bug report about it. - inspect_error() - traceback.print_exc(file=Term.cerr) - info("\nIPython's exception reporting continues...\n") - call = tpl_call_fail % func - - # Initialize a list of names on the current line, which the - # tokenizer below will populate. - names = [] - - def tokeneater(token_type, token, start, end, line): - """Stateful tokeneater which builds dotted names. - - The list of names it appends to (from the enclosing scope) can - contain repeated composite names. This is unavoidable, since - there is no way to disambguate partial dotted structures until - the full list is known. The caller is responsible for pruning - the final list of duplicates before using it.""" - - # build composite names - if token == '.': - try: - names[-1] += '.' - # store state so the next token is added for x.y.z names - tokeneater.name_cont = True - return - except IndexError: - pass - if token_type == tokenize.NAME and token not in keyword.kwlist: - if tokeneater.name_cont: - # Dotted names - names[-1] += token - tokeneater.name_cont = False - else: - # Regular new names. We append everything, the caller - # will be responsible for pruning the list later. It's - # very tricky to try to prune as we go, b/c composite - # names can fool us. The pruning at the end is easy - # to do (or the caller can print a list with repeated - # names if so desired. - names.append(token) - elif token_type == tokenize.NEWLINE: - raise IndexError - # we need to store a bit of state in the tokenizer to build - # dotted names - tokeneater.name_cont = False - - def linereader(file=file, lnum=[lnum], getline=linecache.getline): - line = getline(file, lnum[0]) - lnum[0] += 1 - return line - - # Build the list of names on this line of code where the exception - # occurred. - try: - # This builds the names list in-place by capturing it from the - # enclosing scope. - tokenize.tokenize(linereader, tokeneater) - except IndexError: - # signals exit of tokenizer - pass - except tokenize.TokenError,msg: - _m = ("An unexpected error occurred while tokenizing input\n" - "The following traceback may be corrupted or invalid\n" - "The error message is: %s\n" % msg) - error(_m) - - # prune names list of duplicates, but keep the right order - unique_names = uniq_stable(names) - - # Start loop over vars - lvals = [] - if self.include_vars: - for name_full in unique_names: - name_base = name_full.split('.',1)[0] - if name_base in frame.f_code.co_varnames: - if locals.has_key(name_base): - try: - value = repr(eval(name_full,locals)) - except: - value = undefined - else: - value = undefined - name = tpl_local_var % name_full - else: - if frame.f_globals.has_key(name_base): - try: - value = repr(eval(name_full,frame.f_globals)) - except: - value = undefined - else: - value = undefined - name = tpl_global_var % name_full - lvals.append(tpl_name_val % (name,value)) - if lvals: - lvals = '%s%s' % (indent,em_normal.join(lvals)) - else: - lvals = '' - - level = '%s %s\n' % (link,call) - - if index is None: - frames.append(level) - else: - frames.append('%s%s' % (level,''.join( - _formatTracebackLines(lnum,index,lines,Colors,lvals, - col_scheme)))) - - # Get (safely) a string form of the exception info - try: - etype_str,evalue_str = map(str,(etype,evalue)) - except: - # User exception is improperly defined. - etype,evalue = str,sys.exc_info()[:2] - etype_str,evalue_str = map(str,(etype,evalue)) - # ... and format it - exception = ['%s%s%s: %s' % (Colors.excName, etype_str, - ColorsNormal, evalue_str)] - if type(evalue) is types.InstanceType: - try: - names = [w for w in dir(evalue) if isinstance(w, basestring)] - except: - # Every now and then, an object with funny inernals blows up - # when dir() is called on it. We do the best we can to report - # the problem and continue - _m = '%sException reporting error (object with broken dir())%s:' - exception.append(_m % (Colors.excName,ColorsNormal)) - etype_str,evalue_str = map(str,sys.exc_info()[:2]) - exception.append('%s%s%s: %s' % (Colors.excName,etype_str, - ColorsNormal, evalue_str)) - names = [] - for name in names: - value = text_repr(getattr(evalue, name)) - exception.append('\n%s%s = %s' % (indent, name, value)) - - # vds: >> - if records: - filepath, lnum = records[-1][1:3] - #print "file:", str(file), "linenb", str(lnum) # dbg - filepath = os.path.abspath(filepath) - ipapi.get().IP.hooks.synchronize_with_editor(filepath, lnum, 0) - # vds: << - - # return all our info assembled as a single string - return '%s\n\n%s\n%s' % (head,'\n'.join(frames),''.join(exception[0]) ) - - def debugger(self,force=False): - """Call up the pdb debugger if desired, always clean up the tb - reference. - - Keywords: - - - force(False): by default, this routine checks the instance call_pdb - flag and does not actually invoke the debugger if the flag is false. - The 'force' option forces the debugger to activate even if the flag - is false. - - If the call_pdb flag is set, the pdb interactive debugger is - invoked. In all cases, the self.tb reference to the current traceback - is deleted to prevent lingering references which hamper memory - management. - - Note that each call to pdb() does an 'import readline', so if your app - requires a special setup for the readline completers, you'll have to - fix that by hand after invoking the exception handler.""" - - if force or self.call_pdb: - if self.pdb is None: - self.pdb = Debugger.Pdb( - self.color_scheme_table.active_scheme_name) - # the system displayhook may have changed, restore the original - # for pdb - dhook = sys.displayhook - sys.displayhook = sys.__displayhook__ - self.pdb.reset() - # Find the right frame so we don't pop up inside ipython itself - if hasattr(self,'tb'): - etb = self.tb - else: - etb = self.tb = sys.last_traceback - while self.tb.tb_next is not None: - self.tb = self.tb.tb_next - try: - if etb and etb.tb_next: - etb = etb.tb_next - self.pdb.botframe = etb.tb_frame - self.pdb.interaction(self.tb.tb_frame, self.tb) - finally: - sys.displayhook = dhook - - if hasattr(self,'tb'): - del self.tb - - def handler(self, info=None): - (etype, evalue, etb) = info or sys.exc_info() - self.tb = etb - Term.cout.flush() - print >> Term.cerr, self.text(etype, evalue, etb) - Term.cerr.flush() - - # Changed so an instance can just be called as VerboseTB_inst() and print - # out the right info on its own. - def __call__(self, etype=None, evalue=None, etb=None): - """This hook can replace sys.excepthook (for Python 2.1 or higher).""" - if etb is None: - self.handler() - else: - self.handler((etype, evalue, etb)) - try: - self.debugger() - except KeyboardInterrupt: - print "\nKeyboardInterrupt" - -#---------------------------------------------------------------------------- -class FormattedTB(VerboseTB,ListTB): - """Subclass ListTB but allow calling with a traceback. - - It can thus be used as a sys.excepthook for Python > 2.1. - - Also adds 'Context' and 'Verbose' modes, not available in ListTB. - - Allows a tb_offset to be specified. This is useful for situations where - one needs to remove a number of topmost frames from the traceback (such as - occurs with python programs that themselves execute other python code, - like Python shells). """ - - def __init__(self, mode = 'Plain', color_scheme='Linux', - tb_offset = 0,long_header=0,call_pdb=0,include_vars=0): - - # NEVER change the order of this list. Put new modes at the end: - self.valid_modes = ['Plain','Context','Verbose'] - self.verbose_modes = self.valid_modes[1:3] - - VerboseTB.__init__(self,color_scheme,tb_offset,long_header, - call_pdb=call_pdb,include_vars=include_vars) - self.set_mode(mode) - - def _extract_tb(self,tb): - if tb: - return traceback.extract_tb(tb) - else: - return None - - def text(self, etype, value, tb,context=5,mode=None): - """Return formatted traceback. - - If the optional mode parameter is given, it overrides the current - mode.""" - - if mode is None: - mode = self.mode - if mode in self.verbose_modes: - # verbose modes need a full traceback - return VerboseTB.text(self,etype, value, tb,context=5) - else: - # We must check the source cache because otherwise we can print - # out-of-date source code. - linecache.checkcache() - # Now we can extract and format the exception - elist = self._extract_tb(tb) - if len(elist) > self.tb_offset: - del elist[:self.tb_offset] - return ListTB.text(self,etype,value,elist) - - def set_mode(self,mode=None): - """Switch to the desired mode. - - If mode is not specified, cycles through the available modes.""" - - if not mode: - new_idx = ( self.valid_modes.index(self.mode) + 1 ) % \ - len(self.valid_modes) - self.mode = self.valid_modes[new_idx] - elif mode not in self.valid_modes: - raise ValueError, 'Unrecognized mode in FormattedTB: <'+mode+'>\n'\ - 'Valid modes: '+str(self.valid_modes) - else: - self.mode = mode - # include variable details only in 'Verbose' mode - self.include_vars = (self.mode == self.valid_modes[2]) - - # some convenient shorcuts - def plain(self): - self.set_mode(self.valid_modes[0]) - - def context(self): - self.set_mode(self.valid_modes[1]) - - def verbose(self): - self.set_mode(self.valid_modes[2]) - -#---------------------------------------------------------------------------- -class AutoFormattedTB(FormattedTB): - """A traceback printer which can be called on the fly. - - It will find out about exceptions by itself. - - A brief example: - - AutoTB = AutoFormattedTB(mode = 'Verbose',color_scheme='Linux') - try: - ... - except: - AutoTB() # or AutoTB(out=logfile) where logfile is an open file object - """ - def __call__(self,etype=None,evalue=None,etb=None, - out=None,tb_offset=None): - """Print out a formatted exception traceback. - - Optional arguments: - - out: an open file-like object to direct output to. - - - tb_offset: the number of frames to skip over in the stack, on a - per-call basis (this overrides temporarily the instance's tb_offset - given at initialization time. """ - - if out is None: - out = Term.cerr - Term.cout.flush() - if tb_offset is not None: - tb_offset, self.tb_offset = self.tb_offset, tb_offset - print >> out, self.text(etype, evalue, etb) - self.tb_offset = tb_offset - else: - print >> out, self.text(etype, evalue, etb) - out.flush() - try: - self.debugger() - except KeyboardInterrupt: - print "\nKeyboardInterrupt" - - def text(self,etype=None,value=None,tb=None,context=5,mode=None): - if etype is None: - etype,value,tb = sys.exc_info() - self.tb = tb - return FormattedTB.text(self,etype,value,tb,context=5,mode=mode) - -#--------------------------------------------------------------------------- -# A simple class to preserve Nathan's original functionality. -class ColorTB(FormattedTB): - """Shorthand to initialize a FormattedTB in Linux colors mode.""" - def __init__(self,color_scheme='Linux',call_pdb=0): - FormattedTB.__init__(self,color_scheme=color_scheme, - call_pdb=call_pdb) - -#---------------------------------------------------------------------------- -# module testing (minimal) -if __name__ == "__main__": - def spam(c, (d, e)): - x = c + d - y = c * d - foo(x, y) - - def foo(a, b, bar=1): - eggs(a, b + bar) - - def eggs(f, g, z=globals()): - h = f + g - i = f - g - return h / i - - print '' - print '*** Before ***' - try: - print spam(1, (2, 3)) - except: - traceback.print_exc() - print '' - - handler = ColorTB() - print '*** ColorTB ***' - try: - print spam(1, (2, 3)) - except: - apply(handler, sys.exc_info() ) - print '' - - handler = VerboseTB() - print '*** VerboseTB ***' - try: - print spam(1, (2, 3)) - except: - apply(handler, sys.exc_info() ) - print '' - diff --git a/IPython/DPyGetOpt.py b/IPython/utils/DPyGetOpt.py similarity index 100% rename from IPython/DPyGetOpt.py rename to IPython/utils/DPyGetOpt.py diff --git a/IPython/PyColorize.py b/IPython/utils/PyColorize.py similarity index 94% rename from IPython/PyColorize.py rename to IPython/utils/PyColorize.py index 7d463ea..613ae19 100644 --- a/IPython/PyColorize.py +++ b/IPython/utils/PyColorize.py @@ -38,12 +38,11 @@ import cStringIO import keyword import os import optparse -import string import sys import token import tokenize -from IPython.ColorANSI import * +from IPython.utils.coloransi import * ############################################################################# ### Python Source Parser (does Hilighting) diff --git a/IPython/utils/__init__.py b/IPython/utils/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/IPython/utils/__init__.py diff --git a/IPython/utils/autoattr.py b/IPython/utils/autoattr.py new file mode 100644 index 0000000..3252eaf --- /dev/null +++ b/IPython/utils/autoattr.py @@ -0,0 +1,162 @@ +#!/usr/bin/env python +# encoding: utf-8 +"""Descriptor support for NIPY. + +Utilities to support special Python descriptors [1,2], in particular the use of +a useful pattern for properties we call 'one time properties'. These are +object attributes which are declared as properties, but become regular +attributes once they've been read the first time. They can thus be evaluated +later in the object's life cycle, but once evaluated they become normal, static +attributes with no function call overhead on access or any other constraints. + +A special ResetMixin class is provided to add a .reset() method to users who +may want to have their objects capable of resetting these computed properties +to their 'untriggered' state. + +References +---------- +[1] How-To Guide for Descriptors, Raymond +Hettinger. http://users.rcn.com/python/download/Descriptor.htm + +[2] Python data model, http://docs.python.org/reference/datamodel.html + +Notes +----- +This module is taken from the NiPy project +(http://neuroimaging.scipy.org/site/index.html), and is BSD licensed. +""" + +#----------------------------------------------------------------------------- +# Classes and Functions +#----------------------------------------------------------------------------- + +class ResetMixin(object): + """A Mixin class to add a .reset() method to users of OneTimeProperty. + + By default, auto attributes once computed, become static. If they happen to + depend on other parts of an object and those parts change, their values may + now be invalid. + + This class offers a .reset() method that users can call *explicitly* when + they know the state of their objects may have changed and they want to + ensure that *all* their special attributes should be invalidated. Once + reset() is called, all their auto attributes are reset to their + OneTimeProperty descriptors, and their accessor functions will be triggered + again. + + Example + ------- + + >>> class A(ResetMixin): + ... def __init__(self,x=1.0): + ... self.x = x + ... + ... @auto_attr + ... def y(self): + ... print '*** y computation executed ***' + ... return self.x / 2.0 + ... + + >>> a = A(10) + + About to access y twice, the second time no computation is done: + >>> a.y + *** y computation executed *** + 5.0 + >>> a.y + 5.0 + + Changing x + >>> a.x = 20 + + a.y doesn't change to 10, since it is a static attribute: + >>> a.y + 5.0 + + We now reset a, and this will then force all auto attributes to recompute + the next time we access them: + >>> a.reset() + + About to access y twice again after reset(): + >>> a.y + *** y computation executed *** + 10.0 + >>> a.y + 10.0 + """ + + def reset(self): + """Reset all OneTimeProperty attributes that may have fired already.""" + instdict = self.__dict__ + classdict = self.__class__.__dict__ + # To reset them, we simply remove them from the instance dict. At that + # point, it's as if they had never been computed. On the next access, + # the accessor function from the parent class will be called, simply + # because that's how the python descriptor protocol works. + for mname, mval in classdict.items(): + if mname in instdict and isinstance(mval, OneTimeProperty): + delattr(self, mname) + + +class OneTimeProperty(object): + """A descriptor to make special properties that become normal attributes. + + This is meant to be used mostly by the auto_attr decorator in this module. + """ + def __init__(self,func): + """Create a OneTimeProperty instance. + + Parameters + ---------- + func : method + + The method that will be called the first time to compute a value. + Afterwards, the method's name will be a standard attribute holding + the value of this computation. + """ + self.getter = func + self.name = func.func_name + + def __get__(self,obj,type=None): + """This will be called on attribute access on the class or instance. """ + + if obj is None: + # Being called on the class, return the original function. This way, + # introspection works on the class. + #return func + return self.getter + + val = self.getter(obj) + #print "** auto_attr - loading '%s'" % self.name # dbg + setattr(obj, self.name, val) + return val + + +def auto_attr(func): + """Decorator to create OneTimeProperty attributes. + + Parameters + ---------- + func : method + The method that will be called the first time to compute a value. + Afterwards, the method's name will be a standard attribute holding the + value of this computation. + + Examples + -------- + >>> class MagicProp(object): + ... @auto_attr + ... def a(self): + ... return 99 + ... + >>> x = MagicProp() + >>> 'a' in x.__dict__ + False + >>> x.a + 99 + >>> 'a' in x.__dict__ + True + """ + return OneTimeProperty(func) + + diff --git a/IPython/ColorANSI.py b/IPython/utils/coloransi.py similarity index 98% rename from IPython/ColorANSI.py rename to IPython/utils/coloransi.py index 783aa1d..cdc5a68 100644 --- a/IPython/ColorANSI.py +++ b/IPython/utils/coloransi.py @@ -13,7 +13,7 @@ __all__ = ['TermColors','InputTermColors','ColorScheme','ColorSchemeTable'] import os -from IPython.ipstruct import Struct +from IPython.utils.ipstruct import Struct def make_color_table(in_class): """Build a set of color attributes in a class. @@ -111,7 +111,7 @@ class ColorScheme: """Return a full copy of the object, optionally renaming it.""" if name is None: name = self.name - return ColorScheme(name,self.colors.__dict__) + return ColorScheme(name, self.colors.dict()) class ColorSchemeTable(dict): """General class to handle tables of color schemes. diff --git a/IPython/utils/generics.py b/IPython/utils/generics.py new file mode 100644 index 0000000..8ca605f --- /dev/null +++ b/IPython/utils/generics.py @@ -0,0 +1,43 @@ +"""Generic functions for extending IPython. + +See http://cheeseshop.python.org/pypi/simplegeneric. + +Here is an example from genutils.py:: + + def print_lsstring(arg): + "Prettier (non-repr-like) and more informative printer for LSString" + print "LSString (.p, .n, .l, .s available). Value:" + print arg + + print_lsstring = result_display.when_type(LSString)(print_lsstring) +""" + +from IPython.core.error import TryNext +from IPython.external.simplegeneric import generic + +@generic +def result_display(result): + """Print the result of computation.""" + raise TryNext + +@generic +def inspect_object(obj): + """Called when you do obj?""" + raise TryNext + +@generic +def complete_object(obj, prev_completions): + """Custom completer dispatching for python objects. + + Parameters + ---------- + obj : object + The object to complete. + prev_completions : list + List of attributes discovered so far. + + This should return the list of attributes in obj. If you only wish to + add to the attributes already discovered normally, return + own_attrs + prev_completions. + """ + raise TryNext diff --git a/IPython/genutils.py b/IPython/utils/genutils.py similarity index 72% rename from IPython/genutils.py rename to IPython/utils/genutils.py index cfc5d80..0a81348 100644 --- a/IPython/genutils.py +++ b/IPython/utils/genutils.py @@ -15,11 +15,7 @@ these things are also convenient when working at the command line. #**************************************************************************** # required modules from the Python standard library import __main__ -import commands -try: - import doctest -except ImportError: - pass + import os import platform import re @@ -27,7 +23,6 @@ import shlex import shutil import subprocess import sys -import tempfile import time import types import warnings @@ -46,13 +41,10 @@ else: # Other IPython utilities import IPython -from IPython.Itpl import Itpl,itpl,printpl -from IPython import DPyGetOpt, platutils -from IPython.generics import result_display -import IPython.ipapi +from IPython.external.Itpl import itpl,printpl +from IPython.utils import platutils +from IPython.utils.generics import result_display from IPython.external.path import path -if os.name == "nt": - from IPython.winconsole import get_console_size try: set @@ -111,7 +103,7 @@ class IOTerm: # Global variable to be used for all I/O Term = IOTerm() -import IPython.rlineimpl as readline +import IPython.utils.rlineimpl as readline # Remake Term to use the readline i/o facilities if sys.platform == 'win32' and readline.have_readline: @@ -527,32 +519,53 @@ def get_py_filename(name): raise IOError,'File `%s` not found.' % name #----------------------------------------------------------------------------- -def filefind(fname,alt_dirs = None): - """Return the given filename either in the current directory, if it - exists, or in a specified list of directories. - ~ expansion is done on all file and directory names. - Upon an unsuccessful search, raise an IOError exception.""" +def filefind(filename, path_dirs=None): + """Find a file by looking through a sequence of paths. - if alt_dirs is None: - try: - alt_dirs = get_home_dir() - except HomeDirError: - alt_dirs = os.getcwd() - search = [fname] + list_strings(alt_dirs) - search = map(os.path.expanduser,search) - #print 'search list for',fname,'list:',search # dbg - fname = search[0] - if os.path.isfile(fname): - return fname - for direc in search[1:]: - testname = os.path.join(direc,fname) - #print 'testname',testname # dbg + This iterates through a sequence of paths looking for a file and returns + the full, absolute path of the first occurence of the file. If no set of + path dirs is given, the filename is tested as is, after running through + :func:`expandvars` and :func:`expanduser`. Thus a simple call:: + + filefind('myfile.txt') + + will find the file in the current working dir, but:: + + filefind('~/myfile.txt') + + Will find the file in the users home directory. This function does not + automatically try any paths, such as the cwd or the user's home directory. + + Parameters + ---------- + filename : str + The filename to look for. + path_dirs : str, None or sequence of str + The sequence of paths to look for the file in. If None, the filename + need to be absolute or be in the cwd. If a string, the string is + put into a sequence and the searched. If a sequence, walk through + each element and join with ``filename``, calling :func:`expandvars` + and :func:`expanduser` before testing for existence. + + Returns + ------- + Raises :exc:`IOError` or returns absolute path to file. + """ + if path_dirs is None: + path_dirs = ("",) + elif isinstance(path_dirs, basestring): + path_dirs = (path_dirs,) + for path in path_dirs: + if path == '.': path = os.getcwd() + testname = expand_path(os.path.join(path, filename)) if os.path.isfile(testname): - return testname - raise IOError,'File' + `fname` + \ - ' not found in current or supplied directories:' + `alt_dirs` + return os.path.abspath(testname) + raise IOError("File does not exist in any " + "of the search paths: %r, %r" % \ + (filename, path_dirs)) + #---------------------------------------------------------------------------- def file_read(filename): @@ -618,215 +631,6 @@ def unquote_ends(istr): return istr #---------------------------------------------------------------------------- -def process_cmdline(argv,names=[],defaults={},usage=''): - """ Process command-line options and arguments. - - Arguments: - - - argv: list of arguments, typically sys.argv. - - - names: list of option names. See DPyGetOpt docs for details on options - syntax. - - - defaults: dict of default values. - - - usage: optional usage notice to print if a wrong argument is passed. - - Return a dict of options and a list of free arguments.""" - - getopt = DPyGetOpt.DPyGetOpt() - getopt.setIgnoreCase(0) - getopt.parseConfiguration(names) - - try: - getopt.processArguments(argv) - except DPyGetOpt.ArgumentError, exc: - print usage - warn('"%s"' % exc,level=4) - - defaults.update(getopt.optionValues) - args = getopt.freeValues - - return defaults,args - -#---------------------------------------------------------------------------- -def optstr2types(ostr): - """Convert a string of option names to a dict of type mappings. - - optstr2types(str) -> {None:'string_opts',int:'int_opts',float:'float_opts'} - - This is used to get the types of all the options in a string formatted - with the conventions of DPyGetOpt. The 'type' None is used for options - which are strings (they need no further conversion). This function's main - use is to get a typemap for use with read_dict(). - """ - - typeconv = {None:'',int:'',float:''} - typemap = {'s':None,'i':int,'f':float} - opt_re = re.compile(r'([\w]*)([^:=]*:?=?)([sif]?)') - - for w in ostr.split(): - oname,alias,otype = opt_re.match(w).groups() - if otype == '' or alias == '!': # simple switches are integers too - otype = 'i' - typeconv[typemap[otype]] += oname + ' ' - return typeconv - -#---------------------------------------------------------------------------- -def read_dict(filename,type_conv=None,**opt): - r"""Read a dictionary of key=value pairs from an input file, optionally - performing conversions on the resulting values. - - read_dict(filename,type_conv,**opt) -> dict - - Only one value per line is accepted, the format should be - # optional comments are ignored - key value\n - - Args: - - - type_conv: A dictionary specifying which keys need to be converted to - which types. By default all keys are read as strings. This dictionary - should have as its keys valid conversion functions for strings - (int,long,float,complex, or your own). The value for each key - (converter) should be a whitespace separated string containing the names - of all the entries in the file to be converted using that function. For - keys to be left alone, use None as the conversion function (only needed - with purge=1, see below). - - - opt: dictionary with extra options as below (default in parens) - - purge(0): if set to 1, all keys *not* listed in type_conv are purged out - of the dictionary to be returned. If purge is going to be used, the - set of keys to be left as strings also has to be explicitly specified - using the (non-existent) conversion function None. - - fs(None): field separator. This is the key/value separator to be used - when parsing the file. The None default means any whitespace [behavior - of string.split()]. - - strip(0): if 1, strip string values of leading/trailinig whitespace. - - warn(1): warning level if requested keys are not found in file. - - 0: silently ignore. - - 1: inform but proceed. - - 2: raise KeyError exception. - - no_empty(0): if 1, remove keys with whitespace strings as a value. - - unique([]): list of keys (or space separated string) which can't be - repeated. If one such key is found in the file, each new instance - overwrites the previous one. For keys not listed here, the behavior is - to make a list of all appearances. - - Example: - - If the input file test.ini contains (we put it in a string to keep the test - self-contained): - - >>> test_ini = '''\ - ... i 3 - ... x 4.5 - ... y 5.5 - ... s hi ho''' - - Then we can use it as follows: - >>> type_conv={int:'i',float:'x',None:'s'} - - >>> d = read_dict(test_ini) - - >>> sorted(d.items()) - [('i', '3'), ('s', 'hi ho'), ('x', '4.5'), ('y', '5.5')] - - >>> d = read_dict(test_ini,type_conv) - - >>> sorted(d.items()) - [('i', 3), ('s', 'hi ho'), ('x', 4.5), ('y', '5.5')] - - >>> d = read_dict(test_ini,type_conv,purge=True) - - >>> sorted(d.items()) - [('i', 3), ('s', 'hi ho'), ('x', 4.5)] - """ - - # starting config - opt.setdefault('purge',0) - opt.setdefault('fs',None) # field sep defaults to any whitespace - opt.setdefault('strip',0) - opt.setdefault('warn',1) - opt.setdefault('no_empty',0) - opt.setdefault('unique','') - if type(opt['unique']) in StringTypes: - unique_keys = qw(opt['unique']) - elif type(opt['unique']) in (types.TupleType,types.ListType): - unique_keys = opt['unique'] - else: - raise ValueError, 'Unique keys must be given as a string, List or Tuple' - - dict = {} - - # first read in table of values as strings - if '\n' in filename: - lines = filename.splitlines() - file = None - else: - file = open(filename,'r') - lines = file.readlines() - for line in lines: - line = line.strip() - if len(line) and line[0]=='#': continue - if len(line)>0: - lsplit = line.split(opt['fs'],1) - try: - key,val = lsplit - except ValueError: - key,val = lsplit[0],'' - key = key.strip() - if opt['strip']: val = val.strip() - if val == "''" or val == '""': val = '' - if opt['no_empty'] and (val=='' or val.isspace()): - continue - # if a key is found more than once in the file, build a list - # unless it's in the 'unique' list. In that case, last found in file - # takes precedence. User beware. - try: - if dict[key] and key in unique_keys: - dict[key] = val - elif type(dict[key]) is types.ListType: - dict[key].append(val) - else: - dict[key] = [dict[key],val] - except KeyError: - dict[key] = val - # purge if requested - if opt['purge']: - accepted_keys = qwflat(type_conv.values()) - for key in dict.keys(): - if key in accepted_keys: continue - del(dict[key]) - # now convert if requested - if type_conv==None: return dict - conversions = type_conv.keys() - try: conversions.remove(None) - except: pass - for convert in conversions: - for val in qw(type_conv[convert]): - try: - dict[val] = convert(dict[val]) - except KeyError,e: - if opt['warn'] == 0: - pass - elif opt['warn'] == 1: - print >>sys.stderr, 'Warning: key',val,\ - 'not found in file',filename - elif opt['warn'] == 2: - raise KeyError,e - else: - raise ValueError,'Warning level must be 0,1 or 2' - - return dict - -#---------------------------------------------------------------------------- def flag_calls(func): """Wrap a function to detect and flag when it gets called. @@ -891,8 +695,8 @@ def doctest_reload(): hard monkeypatch seems like a reasonable solution rather than asking users to manually use a different doctest runner when under IPython. - Note - ---- + Notes + ----- This function *used to* reload doctest, but this has been disabled because reloading doctest unconditionally can cause massive breakage of other @@ -911,10 +715,17 @@ class HomeDirError(Error): def get_home_dir(): """Return the closest possible equivalent to a 'home' directory. - We first try $HOME. Absent that, on NT it's $HOMEDRIVE\$HOMEPATH. - + * On POSIX, we try $HOME. + * On Windows we try: + - %HOMESHARE% + - %HOMEDRIVE\%HOMEPATH% + - %USERPROFILE% + - Registry hack + * On Dos C:\ + Currently only Posix and NT are implemented, a HomeDirError exception is - raised for all other OSes. """ + raised for all other OSes. + """ isdir = os.path.isdir env = os.environ @@ -930,93 +741,88 @@ def get_home_dir(): root=os.path.abspath(root).rstrip('\\') if isdir(os.path.join(root, '_ipython')): os.environ["IPYKITROOT"] = root - return root - try: - homedir = env['HOME'] - if not isdir(homedir): - # in case a user stuck some string which does NOT resolve to a - # valid path, it's as good as if we hadn't foud it - raise KeyError - return homedir - except KeyError: - if os.name == 'posix': - raise HomeDirError,'undefined $HOME, IPython can not proceed.' - elif os.name == 'nt': - # For some strange reason, win9x returns 'nt' for os.name. - try: - homedir = os.path.join(env['HOMEDRIVE'],env['HOMEPATH']) - if not isdir(homedir): - homedir = os.path.join(env['USERPROFILE']) - if not isdir(homedir): - raise HomeDirError - return homedir - except KeyError: - try: - # Use the registry to get the 'My Documents' folder. - import _winreg as wreg - key = wreg.OpenKey(wreg.HKEY_CURRENT_USER, - "Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders") - homedir = wreg.QueryValueEx(key,'Personal')[0] - key.Close() - if not isdir(homedir): - e = ('Invalid "Personal" folder registry key ' - 'typically "My Documents".\n' - 'Value: %s\n' - 'This is not a valid directory on your system.' % - homedir) - raise HomeDirError(e) - return homedir - except HomeDirError: - raise - except: - return 'C:\\' - elif os.name == 'dos': - # Desperate, may do absurd things in classic MacOS. May work under DOS. - return 'C:\\' + return root.decode(sys.getfilesystemencoding()) + + if os.name == 'posix': + # Linux, Unix, AIX, OS X + try: + homedir = env['HOME'] + except KeyError: + raise HomeDirError('Undefined $HOME, IPython cannot proceed.') + else: + return homedir.decode(sys.getfilesystemencoding()) + elif os.name == 'nt': + # Now for win9x, XP, Vista, 7? + # For some strange reason all of these return 'nt' for os.name. + # First look for a network home directory. This will return the UNC + # path (\\server\\Users\%username%) not the mapped path (Z:\). This + # is needed when running IPython on cluster where all paths have to + # be UNC. + try: + homedir = env['HOMESHARE'] + except KeyError: + pass else: - raise HomeDirError,'support for your operating system not implemented.' + if isdir(homedir): + return homedir.decode(sys.getfilesystemencoding()) + + # Now look for a local home directory + try: + homedir = os.path.join(env['HOMEDRIVE'],env['HOMEPATH']) + except KeyError: + pass + else: + if isdir(homedir): + return homedir.decode(sys.getfilesystemencoding()) + + # Now the users profile directory + try: + homedir = os.path.join(env['USERPROFILE']) + except KeyError: + pass + else: + if isdir(homedir): + return homedir.decode(sys.getfilesystemencoding()) + + # Use the registry to get the 'My Documents' folder. + try: + import _winreg as wreg + key = wreg.OpenKey( + wreg.HKEY_CURRENT_USER, + "Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders" + ) + homedir = wreg.QueryValueEx(key,'Personal')[0] + key.Close() + except: + pass + else: + if isdir(homedir): + return homedir.decode(sys.getfilesystemencoding()) + + # If all else fails, raise HomeDirError + raise HomeDirError('No valid home directory could be found') + elif os.name == 'dos': + # Desperate, may do absurd things in classic MacOS. May work under DOS. + return 'C:\\'.decode(sys.getfilesystemencoding()) + else: + raise HomeDirError('No valid home directory could be found for your OS') def get_ipython_dir(): """Get the IPython directory for this platform and user. This uses the logic in `get_home_dir` to find the home directory - and the adds either .ipython or _ipython to the end of the path. + and the adds .ipython to the end of the path. """ - if os.name == 'posix': - ipdir_def = '.ipython' - else: - ipdir_def = '_ipython' + ipdir_def = '.ipython' home_dir = get_home_dir() - ipdir = os.path.abspath(os.environ.get('IPYTHONDIR', - os.path.join(home_dir, ipdir_def))) + ipdir = os.environ.get( + 'IPYTHON_DIR', os.environ.get( + 'IPYTHONDIR', os.path.join(home_dir, ipdir_def) + ) + ) return ipdir.decode(sys.getfilesystemencoding()) -def get_security_dir(): - """Get the IPython security directory. - - This directory is the default location for all security related files, - including SSL/TLS certificates and FURL files. - - If the directory does not exist, it is created with 0700 permissions. - If it exists, permissions are set to 0700. - """ - security_dir = os.path.join(get_ipython_dir(), 'security') - if not os.path.isdir(security_dir): - os.mkdir(security_dir, 0700) - else: - os.chmod(security_dir, 0700) - return security_dir - -def get_log_dir(): - """Get the IPython log directory. - - If the log directory does not exist, it is created. - """ - log_dir = os.path.join(get_ipython_dir(), 'log') - if not os.path.isdir(log_dir): - os.mkdir(log_dir, 0777) - return log_dir #**************************************************************************** # strings and text @@ -1344,16 +1150,6 @@ def ask_yes_no(prompt,default=None): return answers[ans] #---------------------------------------------------------------------------- -def marquee(txt='',width=78,mark='*'): - """Return the input string centered in a 'marquee'.""" - if not txt: - return (mark*width)[:width] - nmark = (width-len(txt)-2)/len(mark)/2 - if nmark < 0: nmark =0 - marks = mark*nmark - return '%s %s %s' % (marks,txt,marks) - -#---------------------------------------------------------------------------- class EvalDict: """ Emulate a dict which evaluates its contents in the caller's frame. @@ -1423,14 +1219,6 @@ def qw_lol(indata): else: return qw(indata) -#----------------------------------------------------------------------------- -def list_strings(arg): - """Always return a list of strings, given a string or list of strings - as input.""" - - if type(arg) in StringTypes: return [arg] - else: return arg - #---------------------------------------------------------------------------- def grep(pat,list,case=1): """Simple minded grep-like function. @@ -1514,267 +1302,6 @@ def native_line_ends(filename,backup=1): except: pass -#---------------------------------------------------------------------------- -def get_pager_cmd(pager_cmd = None): - """Return a pager command. - - Makes some attempts at finding an OS-correct one.""" - - if os.name == 'posix': - default_pager_cmd = 'less -r' # -r for color control sequences - elif os.name in ['nt','dos']: - default_pager_cmd = 'type' - - if pager_cmd is None: - try: - pager_cmd = os.environ['PAGER'] - except: - pager_cmd = default_pager_cmd - return pager_cmd - -#----------------------------------------------------------------------------- -def get_pager_start(pager,start): - """Return the string for paging files with an offset. - - This is the '+N' argument which less and more (under Unix) accept. - """ - - if pager in ['less','more']: - if start: - start_string = '+' + str(start) - else: - start_string = '' - else: - start_string = '' - return start_string - -#---------------------------------------------------------------------------- -# (X)emacs on W32 doesn't like to be bypassed with msvcrt.getch() -if os.name == 'nt' and os.environ.get('TERM','dumb') != 'emacs': - import msvcrt - def page_more(): - """ Smart pausing between pages - - @return: True if need print more lines, False if quit - """ - Term.cout.write('---Return to continue, q to quit--- ') - ans = msvcrt.getch() - if ans in ("q", "Q"): - result = False - else: - result = True - Term.cout.write("\b"*37 + " "*37 + "\b"*37) - return result -else: - def page_more(): - ans = raw_input('---Return to continue, q to quit--- ') - if ans.lower().startswith('q'): - return False - else: - return True - -esc_re = re.compile(r"(\x1b[^m]+m)") - -def page_dumb(strng,start=0,screen_lines=25): - """Very dumb 'pager' in Python, for when nothing else works. - - Only moves forward, same interface as page(), except for pager_cmd and - mode.""" - - out_ln = strng.splitlines()[start:] - screens = chop(out_ln,screen_lines-1) - if len(screens) == 1: - print >>Term.cout, os.linesep.join(screens[0]) - else: - last_escape = "" - for scr in screens[0:-1]: - hunk = os.linesep.join(scr) - print >>Term.cout, last_escape + hunk - if not page_more(): - return - esc_list = esc_re.findall(hunk) - if len(esc_list) > 0: - last_escape = esc_list[-1] - print >>Term.cout, last_escape + os.linesep.join(screens[-1]) - -#---------------------------------------------------------------------------- -def page(strng,start=0,screen_lines=0,pager_cmd = None): - """Print a string, piping through a pager after a certain length. - - The screen_lines parameter specifies the number of *usable* lines of your - terminal screen (total lines minus lines you need to reserve to show other - information). - - If you set screen_lines to a number <=0, page() will try to auto-determine - your screen size and will only use up to (screen_size+screen_lines) for - printing, paging after that. That is, if you want auto-detection but need - to reserve the bottom 3 lines of the screen, use screen_lines = -3, and for - auto-detection without any lines reserved simply use screen_lines = 0. - - If a string won't fit in the allowed lines, it is sent through the - specified pager command. If none given, look for PAGER in the environment, - and ultimately default to less. - - If no system pager works, the string is sent through a 'dumb pager' - written in python, very simplistic. - """ - - # Some routines may auto-compute start offsets incorrectly and pass a - # negative value. Offset to 0 for robustness. - start = max(0,start) - - # first, try the hook - ip = IPython.ipapi.get() - if ip: - try: - ip.IP.hooks.show_in_pager(strng) - return - except IPython.ipapi.TryNext: - pass - - # Ugly kludge, but calling curses.initscr() flat out crashes in emacs - TERM = os.environ.get('TERM','dumb') - if TERM in ['dumb','emacs'] and os.name != 'nt': - print strng - return - # chop off the topmost part of the string we don't want to see - str_lines = strng.split(os.linesep)[start:] - str_toprint = os.linesep.join(str_lines) - num_newlines = len(str_lines) - len_str = len(str_toprint) - - # Dumb heuristics to guesstimate number of on-screen lines the string - # takes. Very basic, but good enough for docstrings in reasonable - # terminals. If someone later feels like refining it, it's not hard. - numlines = max(num_newlines,int(len_str/80)+1) - - if os.name == "nt": - screen_lines_def = get_console_size(defaulty=25)[1] - else: - screen_lines_def = 25 # default value if we can't auto-determine - - # auto-determine screen size - if screen_lines <= 0: - if TERM=='xterm': - use_curses = USE_CURSES - else: - # curses causes problems on many terminals other than xterm. - use_curses = False - if use_curses: - # There is a bug in curses, where *sometimes* it fails to properly - # initialize, and then after the endwin() call is made, the - # terminal is left in an unusable state. Rather than trying to - # check everytime for this (by requesting and comparing termios - # flags each time), we just save the initial terminal state and - # unconditionally reset it every time. It's cheaper than making - # the checks. - term_flags = termios.tcgetattr(sys.stdout) - scr = curses.initscr() - screen_lines_real,screen_cols = scr.getmaxyx() - curses.endwin() - # Restore terminal state in case endwin() didn't. - termios.tcsetattr(sys.stdout,termios.TCSANOW,term_flags) - # Now we have what we needed: the screen size in rows/columns - screen_lines += screen_lines_real - #print '***Screen size:',screen_lines_real,'lines x',\ - #screen_cols,'columns.' # dbg - else: - screen_lines += screen_lines_def - - #print 'numlines',numlines,'screenlines',screen_lines # dbg - if numlines <= screen_lines : - #print '*** normal print' # dbg - print >>Term.cout, str_toprint - else: - # Try to open pager and default to internal one if that fails. - # All failure modes are tagged as 'retval=1', to match the return - # value of a failed system command. If any intermediate attempt - # sets retval to 1, at the end we resort to our own page_dumb() pager. - pager_cmd = get_pager_cmd(pager_cmd) - pager_cmd += ' ' + get_pager_start(pager_cmd,start) - if os.name == 'nt': - if pager_cmd.startswith('type'): - # The default WinXP 'type' command is failing on complex strings. - retval = 1 - else: - tmpname = tempfile.mktemp('.txt') - tmpfile = file(tmpname,'wt') - tmpfile.write(strng) - tmpfile.close() - cmd = "%s < %s" % (pager_cmd,tmpname) - if os.system(cmd): - retval = 1 - else: - retval = None - os.remove(tmpname) - else: - try: - retval = None - # if I use popen4, things hang. No idea why. - #pager,shell_out = os.popen4(pager_cmd) - pager = os.popen(pager_cmd,'w') - pager.write(strng) - pager.close() - retval = pager.close() # success returns None - except IOError,msg: # broken pipe when user quits - if msg.args == (32,'Broken pipe'): - retval = None - else: - retval = 1 - except OSError: - # Other strange problems, sometimes seen in Win2k/cygwin - retval = 1 - if retval is not None: - page_dumb(strng,screen_lines=screen_lines) - -#---------------------------------------------------------------------------- -def page_file(fname,start = 0, pager_cmd = None): - """Page a file, using an optional pager command and starting line. - """ - - pager_cmd = get_pager_cmd(pager_cmd) - pager_cmd += ' ' + get_pager_start(pager_cmd,start) - - try: - if os.environ['TERM'] in ['emacs','dumb']: - raise EnvironmentError - xsys(pager_cmd + ' ' + fname) - except: - try: - if start > 0: - start -= 1 - page(open(fname).read(),start) - except: - print 'Unable to show file',`fname` - - -#---------------------------------------------------------------------------- -def snip_print(str,width = 75,print_full = 0,header = ''): - """Print a string snipping the midsection to fit in width. - - print_full: mode control: - - 0: only snip long strings - - 1: send to page() directly. - - 2: snip long strings and ask for full length viewing with page() - Return 1 if snipping was necessary, 0 otherwise.""" - - if print_full == 1: - page(header+str) - return 0 - - print header, - if len(str) < width: - print str - snip = 0 - else: - whalf = int((width -5)/2) - print str[:whalf] + ' <...> ' + str[-whalf:] - snip = 1 - if snip and print_full == 2: - if raw_input(header+' Snipped. View (y/n)? [N]').lower() == 'y': - page(str) - return snip - #**************************************************************************** # lists, dicts and structures @@ -2168,4 +1695,112 @@ def num_cpus(): ncpus = 1 return ncpus +def extract_vars(*names,**kw): + """Extract a set of variables by name from another frame. + + :Parameters: + - `*names`: strings + One or more variable names which will be extracted from the caller's + frame. + + :Keywords: + - `depth`: integer (0) + How many frames in the stack to walk when looking for your variables. + + + Examples: + + In [2]: def func(x): + ...: y = 1 + ...: print extract_vars('x','y') + ...: + + In [3]: func('hello') + {'y': 1, 'x': 'hello'} + """ + + depth = kw.get('depth',0) + + callerNS = sys._getframe(depth+1).f_locals + return dict((k,callerNS[k]) for k in names) + + +def extract_vars_above(*names): + """Extract a set of variables by name from another frame. + + Similar to extractVars(), but with a specified depth of 1, so that names + are exctracted exactly from above the caller. + + This is simply a convenience function so that the very common case (for us) + of skipping exactly 1 frame doesn't have to construct a special dict for + keyword passing.""" + + callerNS = sys._getframe(2).f_locals + return dict((k,callerNS[k]) for k in names) + +def expand_path(s): + """Expand $VARS and ~names in a string, like a shell + + :Examples: + + In [2]: os.environ['FOO']='test' + + In [3]: shexp('variable FOO is $FOO') + Out[3]: 'variable FOO is test' + """ + # This is a pretty subtle hack. When expand user is given a UNC path + # on Windows (\\server\share$\%username%), os.path.expandvars, removes + # the $ to get (\\server\share\%username%). I think it considered $ + # alone an empty var. But, we need the $ to remains there (it indicates + # a hidden share). + if os.name=='nt': + s = s.replace('$\\', 'IPYTHON_TEMP') + s = os.path.expandvars(os.path.expanduser(s)) + if os.name=='nt': + s = s.replace('IPYTHON_TEMP', '$\\') + return s + +def list_strings(arg): + """Always return a list of strings, given a string or list of strings + as input. + + :Examples: + + In [7]: list_strings('A single string') + Out[7]: ['A single string'] + + In [8]: list_strings(['A single string in a list']) + Out[8]: ['A single string in a list'] + + In [9]: list_strings(['A','list','of','strings']) + Out[9]: ['A', 'list', 'of', 'strings'] + """ + + if isinstance(arg,basestring): return [arg] + else: return arg + + +#---------------------------------------------------------------------------- +def marquee(txt='',width=78,mark='*'): + """Return the input string centered in a 'marquee'. + + :Examples: + + In [16]: marquee('A test',40) + Out[16]: '**************** A test ****************' + + In [17]: marquee('A test',40,'-') + Out[17]: '---------------- A test ----------------' + + In [18]: marquee('A test',40,' ') + Out[18]: ' A test ' + + """ + if not txt: + return (mark*width)[:width] + nmark = (width-len(txt)-2)/len(mark)/2 + if nmark < 0: nmark =0 + marks = mark*nmark + return '%s %s %s' % (marks,txt,marks) + #*************************** end of file ********************** diff --git a/IPython/tools/growl.py b/IPython/utils/growl.py similarity index 97% rename from IPython/tools/growl.py rename to IPython/utils/growl.py index cc4613c..fa11d96 100644 --- a/IPython/tools/growl.py +++ b/IPython/utils/growl.py @@ -18,7 +18,7 @@ class Notifier(object): def _notify(self, title, msg): if self.g_notifier is not None: - self.g_notifier.notify('kernel', title, msg) + self.g_notifier.notify('core', title, msg) def notify(self, title, msg): self._notify(title, msg) diff --git a/IPython/utils/importstring.py b/IPython/utils/importstring.py new file mode 100644 index 0000000..12752f4 --- /dev/null +++ b/IPython/utils/importstring.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +A simple utility to import something by its string name. + +Authors: + +* Brian Granger +""" + +#----------------------------------------------------------------------------- +# Copyright (C) 2008-2009 The IPython Development Team +# +# Distributed under the terms of the BSD License. The full license is in +# the file COPYING, distributed as part of this software. +#----------------------------------------------------------------------------- + +#----------------------------------------------------------------------------- +# Functions and classes +#----------------------------------------------------------------------------- + +def import_item(name): + """Import and return bar given the string foo.bar.""" + package = '.'.join(name.split('.')[0:-1]) + obj = name.split('.')[-1] + execString = 'from %s import %s' % (package, obj) + try: + exec execString + except SyntaxError: + raise ImportError("Invalid class specification: %s" % name) + exec 'temp = %s' % obj + return temp diff --git a/IPython/utils/ipstruct.py b/IPython/utils/ipstruct.py new file mode 100644 index 0000000..6816295 --- /dev/null +++ b/IPython/utils/ipstruct.py @@ -0,0 +1,400 @@ +#!/usr/bin/env python +# encoding: utf-8 +"""A dict subclass that supports attribute style access. + +Authors: + +* Fernando Perez (original) +* Brian Granger (refactoring to a dict subclass) +""" + +#----------------------------------------------------------------------------- +# Copyright (C) 2008-2009 The IPython Development Team +# +# Distributed under the terms of the BSD License. The full license is in +# the file COPYING, distributed as part of this software. +#----------------------------------------------------------------------------- + +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- + +import pprint + +from IPython.utils.genutils import list2dict2 + +__all__ = ['Struct'] + +#----------------------------------------------------------------------------- +# Code +#----------------------------------------------------------------------------- + + +class Struct(dict): + """A dict subclass with attribute style access. + + This dict subclass has a a few extra features: + + * Attribute style access. + * Protection of class members (like keys, items) when using attribute + style access. + * The ability to restrict assignment to only existing keys. + * Intelligent merging. + * Overloaded operators. + """ + _allownew = True + def __init__(self, *args, **kw): + """Initialize with a dictionary, another Struct, or data. + + Parameters + ---------- + args : dict, Struct + Initialize with one dict or Struct + kw : dict + Initialize with key, value pairs. + + Examples + -------- + + >>> s = Struct(a=10,b=30) + >>> s.a + 10 + >>> s.b + 30 + >>> s2 = Struct(s,c=30) + >>> s2.keys() + ['a', 'c', 'b'] + """ + object.__setattr__(self, '_allownew', True) + dict.__init__(self, *args, **kw) + + def __setitem__(self, key, value): + """Set an item with check for allownew. + + Examples + -------- + + >>> s = Struct() + >>> s['a'] = 10 + >>> s.allow_new_attr(False) + >>> s['a'] = 10 + >>> s['a'] + 10 + >>> try: + ... s['b'] = 20 + ... except KeyError: + ... print 'this is not allowed' + ... + this is not allowed + """ + if not self._allownew and not self.has_key(key): + raise KeyError( + "can't create new attribute %s when allow_new_attr(False)" % key) + dict.__setitem__(self, key, value) + + def __setattr__(self, key, value): + """Set an attr with protection of class members. + + This calls :meth:`self.__setitem__` but convert :exc:`KeyError` to + :exc:`AttributeError`. + + Examples + -------- + + >>> s = Struct() + >>> s.a = 10 + >>> s.a + 10 + >>> try: + ... s.get = 10 + ... except AttributeError: + ... print "you can't set a class member" + ... + you can't set a class member + """ + # If key is an str it might be a class member or instance var + if isinstance(key, str): + # I can't simply call hasattr here because it calls getattr, which + # calls self.__getattr__, which returns True for keys in + # self._data. But I only want keys in the class and in + # self.__dict__ + if key in self.__dict__ or hasattr(Struct, key): + raise AttributeError( + 'attr %s is a protected member of class Struct.' % key + ) + try: + self.__setitem__(key, value) + except KeyError, e: + raise AttributeError(e) + + def __getattr__(self, key): + """Get an attr by calling :meth:`dict.__getitem__`. + + Like :meth:`__setattr__`, this method converts :exc:`KeyError` to + :exc:`AttributeError`. + + Examples + -------- + + >>> s = Struct(a=10) + >>> s.a + 10 + >>> type(s.get) + + >>> try: + ... s.b + ... except AttributeError: + ... print "I don't have that key" + ... + I don't have that key + """ + try: + result = self[key] + except KeyError: + raise AttributeError(key) + else: + return result + + def __iadd__(self, other): + """s += s2 is a shorthand for s.merge(s2). + + Examples + -------- + + >>> s = Struct(a=10,b=30) + >>> s2 = Struct(a=20,c=40) + >>> s += s2 + >>> s + {'a': 10, 'c': 40, 'b': 30} + """ + self.merge(other) + return self + + def __add__(self,other): + """s + s2 -> New Struct made from s.merge(s2). + + Examples + -------- + + >>> s1 = Struct(a=10,b=30) + >>> s2 = Struct(a=20,c=40) + >>> s = s1 + s2 + >>> s + {'a': 10, 'c': 40, 'b': 30} + """ + sout = self.copy() + sout.merge(other) + return sout + + def __sub__(self,other): + """s1 - s2 -> remove keys in s2 from s1. + + Examples + -------- + + >>> s1 = Struct(a=10,b=30) + >>> s2 = Struct(a=40) + >>> s = s1 - s2 + >>> s + {'b': 30} + """ + sout = self.copy() + sout -= other + return sout + + def __isub__(self,other): + """Inplace remove keys from self that are in other. + + Examples + -------- + + >>> s1 = Struct(a=10,b=30) + >>> s2 = Struct(a=40) + >>> s1 -= s2 + >>> s1 + {'b': 30} + """ + for k in other.keys(): + if self.has_key(k): + del self[k] + return self + + def __dict_invert(self, data): + """Helper function for merge. + + Takes a dictionary whose values are lists and returns a dict with + the elements of each list as keys and the original keys as values. + """ + outdict = {} + for k,lst in data.items(): + if isinstance(lst, str): + lst = lst.split() + for entry in lst: + outdict[entry] = k + return outdict + + def dict(self): + return self + + def copy(self): + """Return a copy as a Struct. + + Examples + -------- + + >>> s = Struct(a=10,b=30) + >>> s2 = s.copy() + >>> s2 + {'a': 10, 'b': 30} + >>> type(s2).__name__ + 'Struct' + """ + return Struct(dict.copy(self)) + + def hasattr(self, key): + """hasattr function available as a method. + + Implemented like has_key. + + Examples + -------- + + >>> s = Struct(a=10) + >>> s.hasattr('a') + True + >>> s.hasattr('b') + False + >>> s.hasattr('get') + False + """ + return self.has_key(key) + + def allow_new_attr(self, allow = True): + """Set whether new attributes can be created in this Struct. + + This can be used to catch typos by verifying that the attribute user + tries to change already exists in this Struct. + """ + object.__setattr__(self, '_allownew', allow) + + def merge(self, __loc_data__=None, __conflict_solve=None, **kw): + """Merge two Structs with customizable conflict resolution. + + This is similar to :meth:`update`, but much more flexible. First, a + dict is made from data+key=value pairs. When merging this dict with + the Struct S, the optional dictionary 'conflict' is used to decide + what to do. + + If conflict is not given, the default behavior is to preserve any keys + with their current value (the opposite of the :meth:`update` method's + behavior). + + Parameters + ---------- + __loc_data : dict, Struct + The data to merge into self + __conflict_solve : dict + The conflict policy dict. The keys are binary functions used to + resolve the conflict and the values are lists of strings naming + the keys the conflict resolution function applies to. Instead of + a list of strings a space separated string can be used, like + 'a b c'. + kw : dict + Additional key, value pairs to merge in + + Notes + ----- + + The `__conflict_solve` dict is a dictionary of binary functions which will be used to + solve key conflicts. Here is an example:: + + __conflict_solve = dict( + func1=['a','b','c'], + func2=['d','e'] + ) + + In this case, the function :func:`func1` will be used to resolve + keys 'a', 'b' and 'c' and the function :func:`func2` will be used for + keys 'd' and 'e'. This could also be written as:: + + __conflict_solve = dict(func1='a b c',func2='d e') + + These functions will be called for each key they apply to with the + form:: + + func1(self['a'], other['a']) + + The return value is used as the final merged value. + + As a convenience, merge() provides five (the most commonly needed) + pre-defined policies: preserve, update, add, add_flip and add_s. The + easiest explanation is their implementation:: + + preserve = lambda old,new: old + update = lambda old,new: new + add = lambda old,new: old + new + add_flip = lambda old,new: new + old # note change of order! + add_s = lambda old,new: old + ' ' + new # only for str! + + You can use those four words (as strings) as keys instead + of defining them as functions, and the merge method will substitute + the appropriate functions for you. + + For more complicated conflict resolution policies, you still need to + construct your own functions. + + Examples + -------- + + This show the default policy: + + >>> s = Struct(a=10,b=30) + >>> s2 = Struct(a=20,c=40) + >>> s.merge(s2) + >>> s + {'a': 10, 'c': 40, 'b': 30} + + Now, show how to specify a conflict dict: + + >>> s = Struct(a=10,b=30) + >>> s2 = Struct(a=20,b=40) + >>> conflict = {'update':'a','add':'b'} + >>> s.merge(s2,conflict) + >>> s + {'a': 20, 'b': 70} + """ + + data_dict = dict(__loc_data__,**kw) + + # policies for conflict resolution: two argument functions which return + # the value that will go in the new struct + preserve = lambda old,new: old + update = lambda old,new: new + add = lambda old,new: old + new + add_flip = lambda old,new: new + old # note change of order! + add_s = lambda old,new: old + ' ' + new + + # default policy is to keep current keys when there's a conflict + conflict_solve = list2dict2(self.keys(), default = preserve) + + # the conflict_solve dictionary is given by the user 'inverted': we + # need a name-function mapping, it comes as a function -> names + # dict. Make a local copy (b/c we'll make changes), replace user + # strings for the three builtin policies and invert it. + if __conflict_solve: + inv_conflict_solve_user = __conflict_solve.copy() + for name, func in [('preserve',preserve), ('update',update), + ('add',add), ('add_flip',add_flip), + ('add_s',add_s)]: + if name in inv_conflict_solve_user.keys(): + inv_conflict_solve_user[func] = inv_conflict_solve_user[name] + del inv_conflict_solve_user[name] + conflict_solve.update(self.__dict_invert(inv_conflict_solve_user)) + #print 'merge. conflict_solve: '; pprint(conflict_solve) # dbg + #print '*'*50,'in merger. conflict_solver:'; pprint(conflict_solve) + for key in data_dict: + if key not in self: + self[key] = data_dict[key] + else: + self[key] = conflict_solve[key](self[key],data_dict[key]) + diff --git a/IPython/utils/notification.py b/IPython/utils/notification.py new file mode 100644 index 0000000..e0718d2 --- /dev/null +++ b/IPython/utils/notification.py @@ -0,0 +1,143 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +The IPython Core Notification Center. + +See docs/source/development/notification_blueprint.txt for an overview of the +notification module. + +Authors: + +* Barry Wark +* Brian Granger +""" + +#----------------------------------------------------------------------------- +# Copyright (C) 2008-2009 The IPython Development Team +# +# Distributed under the terms of the BSD License. The full license is in +# the file COPYING, distributed as part of this software. +#----------------------------------------------------------------------------- + +#----------------------------------------------------------------------------- +# Code +#----------------------------------------------------------------------------- + + +class NotificationError(Exception): + pass + + +class NotificationCenter(object): + """Synchronous notification center. + + Examples + -------- + Here is a simple example of how to use this:: + + import IPython.kernel.core.notification as notification + def callback(ntype, theSender, args={}): + print ntype,theSender,args + + notification.sharedCenter.add_observer(callback, 'NOTIFICATION_TYPE', None) + notification.sharedCenter.post_notification('NOTIFICATION_TYPE', object()) # doctest:+ELLIPSIS + NOTIFICATION_TYPE ... + """ + def __init__(self): + super(NotificationCenter, self).__init__() + self._init_observers() + + def _init_observers(self): + """Initialize observer storage""" + + self.registered_types = set() #set of types that are observed + self.registered_senders = set() #set of senders that are observed + self.observers = {} #map (type,sender) => callback (callable) + + def post_notification(self, ntype, sender, *args, **kwargs): + """Post notification to all registered observers. + + The registered callback will be called as:: + + callback(ntype, sender, *args, **kwargs) + + Parameters + ---------- + ntype : hashable + The notification type. + sender : hashable + The object sending the notification. + *args : tuple + The positional arguments to be passed to the callback. + **kwargs : dict + The keyword argument to be passed to the callback. + + Notes + ----- + * If no registered observers, performance is O(1). + * Notificaiton order is undefined. + * Notifications are posted synchronously. + """ + + if(ntype==None or sender==None): + raise NotificationError( + "Notification type and sender are required.") + + # If there are no registered observers for the type/sender pair + if((ntype not in self.registered_types and + None not in self.registered_types) or + (sender not in self.registered_senders and + None not in self.registered_senders)): + return + + for o in self._observers_for_notification(ntype, sender): + o(ntype, sender, *args, **kwargs) + + def _observers_for_notification(self, ntype, sender): + """Find all registered observers that should recieve notification""" + + keys = ( + (ntype,sender), + (ntype, None), + (None, sender), + (None,None) + ) + + obs = set() + for k in keys: + obs.update(self.observers.get(k, set())) + + return obs + + def add_observer(self, callback, ntype, sender): + """Add an observer callback to this notification center. + + The given callback will be called upon posting of notifications of + the given type/sender and will receive any additional arguments passed + to post_notification. + + Parameters + ---------- + callback : callable + The callable that will be called by :meth:`post_notification` + as ``callback(ntype, sender, *args, **kwargs) + ntype : hashable + The notification type. If None, all notifications from sender + will be posted. + sender : hashable + The notification sender. If None, all notifications of ntype + will be posted. + """ + assert(callback != None) + self.registered_types.add(ntype) + self.registered_senders.add(sender) + self.observers.setdefault((ntype,sender), set()).add(callback) + + def remove_all_observers(self): + """Removes all observers from this notification center""" + + self._init_observers() + + + +shared_center = NotificationCenter() diff --git a/IPython/Extensions/pickleshare.py b/IPython/utils/pickleshare.py similarity index 100% rename from IPython/Extensions/pickleshare.py rename to IPython/utils/pickleshare.py diff --git a/IPython/platutils.py b/IPython/utils/platutils.py similarity index 96% rename from IPython/platutils.py rename to IPython/utils/platutils.py index 1551167..2c3e52a 100644 --- a/IPython/platutils.py +++ b/IPython/utils/platutils.py @@ -23,11 +23,6 @@ elif sys.platform == 'win32': import platutils_win32 as _platutils else: import platutils_dummy as _platutils - import warnings - warnings.warn("Platutils not available for platform '%s', some features may be missing" % - os.name) - del warnings - # Functionality that's logically common to all platforms goes here, each # platform-specific module only provides the bits that are OS-dependent. @@ -59,7 +54,6 @@ def toggle_set_term_title(val): def set_term_title(title): """Set terminal title using the necessary platform-dependent calls.""" - if _platutils.ignore_termtitle: return _platutils.set_term_title(title) @@ -104,6 +98,5 @@ def get_long_path_name(path): # Deprecated functions #----------------------------------------------------------------------------- def freeze_term_title(): - import warnings warnings.warn("This function is deprecated, use toggle_set_term_title()") _platutils.ignore_termtitle = True diff --git a/IPython/platutils_dummy.py b/IPython/utils/platutils_dummy.py similarity index 100% rename from IPython/platutils_dummy.py rename to IPython/utils/platutils_dummy.py diff --git a/IPython/platutils_posix.py b/IPython/utils/platutils_posix.py similarity index 95% rename from IPython/platutils_posix.py rename to IPython/utils/platutils_posix.py index 3c38dba..a618d15 100644 --- a/IPython/platutils_posix.py +++ b/IPython/utils/platutils_posix.py @@ -17,17 +17,18 @@ import os ignore_termtitle = True + def _dummy_op(*a, **b): """ A no-op function """ def _set_term_title_xterm(title): """ Change virtual terminal title in xterm-workalikes """ - sys.stdout.write('\033]0;%s\007' % title) +TERM = os.environ.get('TERM','') -if os.environ.get('TERM','') == 'xterm': +if (TERM == 'xterm') or (TERM == 'xterm-color'): set_term_title = _set_term_title_xterm else: set_term_title = _dummy_op diff --git a/IPython/platutils_win32.py b/IPython/utils/platutils_win32.py similarity index 90% rename from IPython/platutils_win32.py rename to IPython/utils/platutils_win32.py index d9f0ed3..68e5017 100644 --- a/IPython/platutils_win32.py +++ b/IPython/utils/platutils_win32.py @@ -26,6 +26,7 @@ try: """Set terminal title using ctypes to access the Win32 APIs.""" SetConsoleTitleW(title) + except ImportError: def set_term_title(title): """Set terminal title using the 'title' command.""" @@ -46,15 +47,22 @@ except ImportError: def find_cmd(cmd): """Find the full path to a .bat or .exe using the win32api module.""" try: - import win32api + from win32api import SearchPath except ImportError: raise ImportError('you need to have pywin32 installed for this to work') else: - try: - (path, offest) = win32api.SearchPath(os.environ['PATH'],cmd + '.exe') - except: - (path, offset) = win32api.SearchPath(os.environ['PATH'],cmd + '.bat') - return path + PATH = os.environ['PATH'] + extensions = ['.exe', '.com', '.bat', '.py'] + path = None + for ext in extensions: + try: + path = SearchPath(PATH,cmd + ext)[0] + except: + pass + if path is None: + raise OSError("command %r not found" % cmd) + else: + return path def get_long_path_name(path): diff --git a/IPython/rlineimpl.py b/IPython/utils/rlineimpl.py similarity index 96% rename from IPython/rlineimpl.py rename to IPython/utils/rlineimpl.py index aa0ba63..f13b641 100644 --- a/IPython/rlineimpl.py +++ b/IPython/utils/rlineimpl.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- """ Imports and provides the 'correct' version of readline for the platform. -Readline is used throughout IPython as 'import IPython.rlineimpl as readline'. +Readline is used throughout IPython as 'import IPython.utils.rlineimpl as readline'. In addition to normal readline stuff, this module provides have_readline boolean and _outputfile variable used in genutils. diff --git a/IPython/strdispatch.py b/IPython/utils/strdispatch.py similarity index 97% rename from IPython/strdispatch.py rename to IPython/utils/strdispatch.py index 7113537..c03c282 100644 --- a/IPython/strdispatch.py +++ b/IPython/utils/strdispatch.py @@ -5,8 +5,7 @@ import re # Our own modules -from IPython.hooks import CommandChainDispatcher -import IPython.hooks +from IPython.core.hooks import CommandChainDispatcher # Code begins class StrDispatch(object): diff --git a/IPython/utils/syspathcontext.py b/IPython/utils/syspathcontext.py new file mode 100644 index 0000000..65f202e --- /dev/null +++ b/IPython/utils/syspathcontext.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +Context managers for adding things to sys.path temporarily. + +Authors: + +* Brian Granger +""" + +#----------------------------------------------------------------------------- +# Copyright (C) 2008-2009 The IPython Development Team +# +# Distributed under the terms of the BSD License. The full license is in +# the file COPYING, distributed as part of this software. +#----------------------------------------------------------------------------- + +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- + + +import sys + +class appended_to_syspath(object): + """A context for appending a directory to sys.path for a second.""" + + def __init__(self, dir): + self.dir = dir + + def __enter__(self): + if self.dir not in sys.path: + sys.path.append(self.dir) + self.added = True + else: + self.added = False + + def __exit__(self, type, value, traceback): + if self.added: + try: + sys.path.remove(self.dir) + except ValueError: + pass + # Returning False causes any exceptions to be re-raised. + return False + +class prepended_to_syspath(object): + """A context for prepending a directory to sys.path for a second.""" + + def __init__(self, dir): + self.dir = dir + + def __enter__(self): + if self.dir not in sys.path: + sys.path.insert(0,self.dir) + self.added = True + else: + self.added = False + + def __exit__(self, type, value, traceback): + if self.added: + try: + sys.path.remove(self.dir) + except ValueError: + pass + # Returning False causes any exceptions to be re-raised. + return False diff --git a/IPython/utils/tests/__init__.py b/IPython/utils/tests/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/IPython/utils/tests/__init__.py diff --git a/IPython/tests/test_genutils.py b/IPython/utils/tests/test_genutils.py similarity index 98% rename from IPython/tests/test_genutils.py rename to IPython/utils/tests/test_genutils.py index 17d9449..b8f1995 100644 --- a/IPython/tests/test_genutils.py +++ b/IPython/utils/tests/test_genutils.py @@ -31,7 +31,7 @@ from nose.tools import raises # Our own import IPython -from IPython import genutils +from IPython.utils import genutils from IPython.testing.decorators import skipif, skip_if_not_win32 # Platform-dependent imports diff --git a/IPython/utils/tests/test_imports.py b/IPython/utils/tests/test_imports.py new file mode 100644 index 0000000..62be4cf --- /dev/null +++ b/IPython/utils/tests/test_imports.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python +# encoding: utf-8 + +def test_import_coloransi(): + from IPython.utils import coloransi + +def test_import_DPyGetOpt(): + from IPython.utils import DPyGetOpt + +def test_import_generics(): + from IPython.utils import generics + +def test_import_genutils(): + from IPython.utils import genutils + +def test_import_ipstruct(): + from IPython.utils import ipstruct + +def test_import_platutils(): + from IPython.utils import platutils + +def test_import_PyColorize(): + from IPython.utils import PyColorize + +def test_import_rlineimpl(): + from IPython.utils import rlineimpl + +def test_import_strdispatch(): + from IPython.utils import strdispatch + +def test_import_upgradedir(): + from IPython.utils import upgradedir + +def test_import_wildcard(): + from IPython.utils import wildcard + +def test_import_winconsole(): + from IPython.utils import winconsole diff --git a/IPython/kernel/core/tests/test_notification.py b/IPython/utils/tests/test_notification.py similarity index 54% rename from IPython/kernel/core/tests/test_notification.py rename to IPython/utils/tests/test_notification.py index 2744049..d012e79 100644 --- a/IPython/kernel/core/tests/test_notification.py +++ b/IPython/utils/tests/test_notification.py @@ -13,135 +13,129 @@ # Imports #----------------------------------------------------------------------------- -# Tell nose to skip this module -__test__ = {} +import unittest -from twisted.trial import unittest -import IPython.kernel.core.notification as notification +from IPython.utils.notification import ( + NotificationCenter, + NotificationError, + shared_center +) #----------------------------------------------------------------------------- # Support Classes #----------------------------------------------------------------------------- + class Observer(object): - """docstring for Observer""" - def __init__(self, expectedType, expectedSender, - center=notification.sharedCenter, **kwargs): + + def __init__(self, expected_ntype, expected_sender, + center=shared_center, *args, **kwargs): super(Observer, self).__init__() - self.expectedType = expectedType - self.expectedSender = expectedSender - self.expectedKwArgs = kwargs + self.expected_ntype = expected_ntype + self.expected_sender = expected_sender + self.expected_args = args + self.expected_kwargs = kwargs self.recieved = False center.add_observer(self.callback, - self.expectedType, - self.expectedSender) + self.expected_ntype, + self.expected_sender) - def callback(self, theType, sender, args={}): - """callback""" - - assert(theType == self.expectedType or - self.expectedType == None) - assert(sender == self.expectedSender or - self.expectedSender == None) - assert(args == self.expectedKwArgs) + def callback(self, ntype, sender, *args, **kwargs): + assert(ntype == self.expected_ntype or + self.expected_ntype == None) + assert(sender == self.expected_sender or + self.expected_sender == None) + assert(args == self.expected_args) + assert(kwargs == self.expected_kwargs) self.recieved = True def verify(self): - """verify""" - assert(self.recieved) def reset(self): - """reset""" - self.recieved = False class Notifier(object): - """docstring for Notifier""" - def __init__(self, theType, **kwargs): + + def __init__(self, ntype, **kwargs): super(Notifier, self).__init__() - self.theType = theType + self.ntype = ntype self.kwargs = kwargs - - def post(self, center=notification.sharedCenter): - """fire""" - - center.post_notification(self.theType, self, + + def post(self, center=shared_center): + + center.post_notification(self.ntype, self, **self.kwargs) + #----------------------------------------------------------------------------- # Tests #----------------------------------------------------------------------------- + class NotificationTests(unittest.TestCase): - """docstring for NotificationTests""" - + def tearDown(self): - notification.sharedCenter.remove_all_observers() + shared_center.remove_all_observers() def test_notification_delivered(self): """Test that notifications are delivered""" - expectedType = 'EXPECTED_TYPE' - sender = Notifier(expectedType) - observer = Observer(expectedType, sender) - + + expected_ntype = 'EXPECTED_TYPE' + sender = Notifier(expected_ntype) + observer = Observer(expected_ntype, sender) + sender.post() - observer.verify() - + def test_type_specificity(self): """Test that observers are registered by type""" - - expectedType = 1 - unexpectedType = "UNEXPECTED_TYPE" - sender = Notifier(expectedType) - unexpectedSender = Notifier(unexpectedType) - observer = Observer(expectedType, sender) - + + expected_ntype = 1 + unexpected_ntype = "UNEXPECTED_TYPE" + sender = Notifier(expected_ntype) + unexpected_sender = Notifier(unexpected_ntype) + observer = Observer(expected_ntype, sender) + sender.post() - unexpectedSender.post() - + unexpected_sender.post() observer.verify() - + def test_sender_specificity(self): """Test that observers are registered by sender""" - expectedType = "EXPECTED_TYPE" - sender1 = Notifier(expectedType) - sender2 = Notifier(expectedType) - observer = Observer(expectedType, sender1) - + expected_ntype = "EXPECTED_TYPE" + sender1 = Notifier(expected_ntype) + sender2 = Notifier(expected_ntype) + observer = Observer(expected_ntype, sender1) + sender1.post() sender2.post() - + observer.verify() - + def test_remove_all_observers(self): """White-box test for remove_all_observers""" - + for i in xrange(10): - Observer('TYPE', None, center=notification.sharedCenter) - - self.assert_(len(notification.sharedCenter.observers[('TYPE',None)]) >= 10, + Observer('TYPE', None, center=shared_center) + + self.assert_(len(shared_center.observers[('TYPE',None)]) >= 10, "observers registered") - - notification.sharedCenter.remove_all_observers() - - self.assert_(len(notification.sharedCenter.observers) == 0, "observers removed") + + shared_center.remove_all_observers() + self.assert_(len(shared_center.observers) == 0, "observers removed") def test_any_sender(self): - """test_any_sender""" - - expectedType = "EXPECTED_TYPE" - sender1 = Notifier(expectedType) - sender2 = Notifier(expectedType) - observer = Observer(expectedType, None) - - + expected_ntype = "EXPECTED_TYPE" + sender1 = Notifier(expected_ntype) + sender2 = Notifier(expected_ntype) + observer = Observer(expected_ntype, None) + sender1.post() observer.verify() - + observer.reset() sender2.post() observer.verify() @@ -152,10 +146,9 @@ class NotificationTests(unittest.TestCase): for i in xrange(10): Observer("UNRELATED_TYPE", None) - + o = Observer('EXPECTED_TYPE', None) - - notification.sharedCenter.post_notification('EXPECTED_TYPE', self) - + shared_center.post_notification('EXPECTED_TYPE', self) o.verify() + diff --git a/IPython/tests/test_platutils.py b/IPython/utils/tests/test_platutils.py similarity index 97% rename from IPython/tests/test_platutils.py rename to IPython/utils/tests/test_platutils.py index 36d189f..4446789 100644 --- a/IPython/tests/test_platutils.py +++ b/IPython/utils/tests/test_platutils.py @@ -20,7 +20,7 @@ import sys import nose.tools as nt -from IPython.platutils import find_cmd, FindCmdError, get_long_path_name +from IPython.utils.platutils import find_cmd, FindCmdError, get_long_path_name from IPython.testing import decorators as dec #----------------------------------------------------------------------------- diff --git a/IPython/utils/tests/test_traitlets.py b/IPython/utils/tests/test_traitlets.py new file mode 100644 index 0000000..e7c0e75 --- /dev/null +++ b/IPython/utils/tests/test_traitlets.py @@ -0,0 +1,690 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +Tests for IPython.utils.traitlets. + +Authors: + +* Brian Granger +* Enthought, Inc. Some of the code in this file comes from enthought.traits + and is licensed under the BSD license. Also, many of the ideas also come + from enthought.traits even though our implementation is very different. +""" + +#----------------------------------------------------------------------------- +# Copyright (C) 2008-2009 The IPython Development Team +# +# Distributed under the terms of the BSD License. The full license is in +# the file COPYING, distributed as part of this software. +#----------------------------------------------------------------------------- + +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- + +import sys +import os + + +from unittest import TestCase + +from IPython.utils.traitlets import ( + HasTraitlets, MetaHasTraitlets, TraitletType, Any, + Int, Long, Float, Complex, Str, Unicode, Bool, TraitletError, + Undefined, Type, This, Instance +) + + +#----------------------------------------------------------------------------- +# Helper classes for testing +#----------------------------------------------------------------------------- + + +class HasTraitletsStub(HasTraitlets): + + def _notify_traitlet(self, name, old, new): + self._notify_name = name + self._notify_old = old + self._notify_new = new + + +#----------------------------------------------------------------------------- +# Test classes +#----------------------------------------------------------------------------- + + +class TestTraitletType(TestCase): + + def test_get_undefined(self): + class A(HasTraitlets): + a = TraitletType + a = A() + self.assertEquals(a.a, Undefined) + + def test_set(self): + class A(HasTraitletsStub): + a = TraitletType + + a = A() + a.a = 10 + self.assertEquals(a.a, 10) + self.assertEquals(a._notify_name, 'a') + self.assertEquals(a._notify_old, Undefined) + self.assertEquals(a._notify_new, 10) + + def test_validate(self): + class MyTT(TraitletType): + def validate(self, inst, value): + return -1 + class A(HasTraitletsStub): + tt = MyTT + + a = A() + a.tt = 10 + self.assertEquals(a.tt, -1) + + def test_default_validate(self): + class MyIntTT(TraitletType): + def validate(self, obj, value): + if isinstance(value, int): + return value + self.error(obj, value) + class A(HasTraitlets): + tt = MyIntTT(10) + a = A() + self.assertEquals(a.tt, 10) + + # Defaults are validated when the HasTraitlets is instantiated + class B(HasTraitlets): + tt = MyIntTT('bad default') + self.assertRaises(TraitletError, B) + + def test_is_valid_for(self): + class MyTT(TraitletType): + def is_valid_for(self, value): + return True + class A(HasTraitlets): + tt = MyTT + + a = A() + a.tt = 10 + self.assertEquals(a.tt, 10) + + def test_value_for(self): + class MyTT(TraitletType): + def value_for(self, value): + return 20 + class A(HasTraitlets): + tt = MyTT + + a = A() + a.tt = 10 + self.assertEquals(a.tt, 20) + + def test_info(self): + class A(HasTraitlets): + tt = TraitletType + a = A() + self.assertEquals(A.tt.info(), 'any value') + + def test_error(self): + class A(HasTraitlets): + tt = TraitletType + a = A() + self.assertRaises(TraitletError, A.tt.error, a, 10) + + +class TestHasTraitletsMeta(TestCase): + + def test_metaclass(self): + self.assertEquals(type(HasTraitlets), MetaHasTraitlets) + + class A(HasTraitlets): + a = Int + + a = A() + self.assertEquals(type(a.__class__), MetaHasTraitlets) + self.assertEquals(a.a,0) + a.a = 10 + self.assertEquals(a.a,10) + + class B(HasTraitlets): + b = Int() + + b = B() + self.assertEquals(b.b,0) + b.b = 10 + self.assertEquals(b.b,10) + + class C(HasTraitlets): + c = Int(30) + + c = C() + self.assertEquals(c.c,30) + c.c = 10 + self.assertEquals(c.c,10) + + def test_this_class(self): + class A(HasTraitlets): + t = This() + tt = This() + class B(A): + tt = This() + ttt = This() + self.assertEquals(A.t.this_class, A) + self.assertEquals(B.t.this_class, A) + self.assertEquals(B.tt.this_class, B) + self.assertEquals(B.ttt.this_class, B) + +class TestHasTraitletsNotify(TestCase): + + def setUp(self): + self._notify1 = [] + self._notify2 = [] + + def notify1(self, name, old, new): + self._notify1.append((name, old, new)) + + def notify2(self, name, old, new): + self._notify2.append((name, old, new)) + + def test_notify_all(self): + + class A(HasTraitlets): + a = Int + b = Float + + a = A() + a.on_traitlet_change(self.notify1) + a.a = 0 + self.assertEquals(len(self._notify1),0) + a.b = 0.0 + self.assertEquals(len(self._notify1),0) + a.a = 10 + self.assert_(('a',0,10) in self._notify1) + a.b = 10.0 + self.assert_(('b',0.0,10.0) in self._notify1) + self.assertRaises(TraitletError,setattr,a,'a','bad string') + self.assertRaises(TraitletError,setattr,a,'b','bad string') + self._notify1 = [] + a.on_traitlet_change(self.notify1,remove=True) + a.a = 20 + a.b = 20.0 + self.assertEquals(len(self._notify1),0) + + def test_notify_one(self): + + class A(HasTraitlets): + a = Int + b = Float + + a = A() + a.on_traitlet_change(self.notify1, 'a') + a.a = 0 + self.assertEquals(len(self._notify1),0) + a.a = 10 + self.assert_(('a',0,10) in self._notify1) + self.assertRaises(TraitletError,setattr,a,'a','bad string') + + def test_subclass(self): + + class A(HasTraitlets): + a = Int + + class B(A): + b = Float + + b = B() + self.assertEquals(b.a,0) + self.assertEquals(b.b,0.0) + b.a = 100 + b.b = 100.0 + self.assertEquals(b.a,100) + self.assertEquals(b.b,100.0) + + def test_notify_subclass(self): + + class A(HasTraitlets): + a = Int + + class B(A): + b = Float + + b = B() + b.on_traitlet_change(self.notify1, 'a') + b.on_traitlet_change(self.notify2, 'b') + b.a = 0 + b.b = 0.0 + self.assertEquals(len(self._notify1),0) + self.assertEquals(len(self._notify2),0) + b.a = 10 + b.b = 10.0 + self.assert_(('a',0,10) in self._notify1) + self.assert_(('b',0.0,10.0) in self._notify2) + + def test_static_notify(self): + + class A(HasTraitlets): + a = Int + _notify1 = [] + def _a_changed(self, name, old, new): + self._notify1.append((name, old, new)) + + a = A() + a.a = 0 + # This is broken!!! + self.assertEquals(len(a._notify1),0) + a.a = 10 + self.assert_(('a',0,10) in a._notify1) + + class B(A): + b = Float + _notify2 = [] + def _b_changed(self, name, old, new): + self._notify2.append((name, old, new)) + + b = B() + b.a = 10 + b.b = 10.0 + self.assert_(('a',0,10) in b._notify1) + self.assert_(('b',0.0,10.0) in b._notify2) + + def test_notify_args(self): + + def callback0(): + self.cb = () + def callback1(name): + self.cb = (name,) + def callback2(name, new): + self.cb = (name, new) + def callback3(name, old, new): + self.cb = (name, old, new) + + class A(HasTraitlets): + a = Int + + a = A() + a.on_traitlet_change(callback0, 'a') + a.a = 10 + self.assertEquals(self.cb,()) + a.on_traitlet_change(callback0, 'a', remove=True) + + a.on_traitlet_change(callback1, 'a') + a.a = 100 + self.assertEquals(self.cb,('a',)) + a.on_traitlet_change(callback1, 'a', remove=True) + + a.on_traitlet_change(callback2, 'a') + a.a = 1000 + self.assertEquals(self.cb,('a',1000)) + a.on_traitlet_change(callback2, 'a', remove=True) + + a.on_traitlet_change(callback3, 'a') + a.a = 10000 + self.assertEquals(self.cb,('a',1000,10000)) + a.on_traitlet_change(callback3, 'a', remove=True) + + self.assertEquals(len(a._traitlet_notifiers['a']),0) + + +class TestHasTraitlets(TestCase): + + def test_traitlet_names(self): + class A(HasTraitlets): + i = Int + f = Float + a = A() + self.assertEquals(a.traitlet_names(),['i','f']) + + def test_traitlet_metadata(self): + class A(HasTraitlets): + i = Int(config_key='MY_VALUE') + a = A() + self.assertEquals(a.traitlet_metadata('i','config_key'), 'MY_VALUE') + + def test_traitlets(self): + class A(HasTraitlets): + i = Int + f = Float + a = A() + self.assertEquals(a.traitlets(), dict(i=A.i, f=A.f)) + + def test_traitlets_metadata(self): + class A(HasTraitlets): + i = Int(config_key='VALUE1', other_thing='VALUE2') + f = Float(config_key='VALUE3', other_thing='VALUE2') + j = Int(0) + a = A() + self.assertEquals(a.traitlets(), dict(i=A.i, f=A.f, j=A.j)) + traitlets = a.traitlets(config_key='VALUE1', other_thing='VALUE2') + self.assertEquals(traitlets, dict(i=A.i)) + + # This passes, but it shouldn't because I am replicating a bug in + # traits. + traitlets = a.traitlets(config_key=lambda v: True) + self.assertEquals(traitlets, dict(i=A.i, f=A.f, j=A.j)) + + +#----------------------------------------------------------------------------- +# Tests for specific traitlet types +#----------------------------------------------------------------------------- + + +class TestType(TestCase): + + def test_default(self): + + class B(object): pass + class A(HasTraitlets): + klass = Type + + a = A() + self.assertEquals(a.klass, None) + + a.klass = B + self.assertEquals(a.klass, B) + self.assertRaises(TraitletError, setattr, a, 'klass', 10) + + def test_value(self): + + class B(object): pass + class C(object): pass + class A(HasTraitlets): + klass = Type(B) + + a = A() + self.assertEquals(a.klass, B) + self.assertRaises(TraitletError, setattr, a, 'klass', C) + self.assertRaises(TraitletError, setattr, a, 'klass', object) + a.klass = B + + def test_allow_none(self): + + class B(object): pass + class C(B): pass + class A(HasTraitlets): + klass = Type(B, allow_none=False) + + a = A() + self.assertEquals(a.klass, B) + self.assertRaises(TraitletError, setattr, a, 'klass', None) + a.klass = C + self.assertEquals(a.klass, C) + + def test_validate_klass(self): + + class A(HasTraitlets): + klass = Type('no strings allowed') + + self.assertRaises(ImportError, A) + + class A(HasTraitlets): + klass = Type('rub.adub.Duck') + + self.assertRaises(ImportError, A) + + def test_validate_default(self): + + class B(object): pass + class A(HasTraitlets): + klass = Type('bad default', B) + + self.assertRaises(ImportError, A) + + class C(HasTraitlets): + klass = Type(None, B, allow_none=False) + + self.assertRaises(TraitletError, C) + + def test_str_klass(self): + + class A(HasTraitlets): + klass = Type('IPython.utils.ipstruct.Struct') + + from IPython.utils.ipstruct import Struct + a = A() + a.klass = Struct + self.assertEquals(a.klass, Struct) + + self.assertRaises(TraitletError, setattr, a, 'klass', 10) + +class TestInstance(TestCase): + + def test_basic(self): + class Foo(object): pass + class Bar(Foo): pass + class Bah(object): pass + + class A(HasTraitlets): + inst = Instance(Foo) + + a = A() + self.assert_(a.inst is None) + a.inst = Foo() + self.assert_(isinstance(a.inst, Foo)) + a.inst = Bar() + self.assert_(isinstance(a.inst, Foo)) + self.assertRaises(TraitletError, setattr, a, 'inst', Foo) + self.assertRaises(TraitletError, setattr, a, 'inst', Bar) + self.assertRaises(TraitletError, setattr, a, 'inst', Bah()) + + def test_unique_default_value(self): + class Foo(object): pass + class A(HasTraitlets): + inst = Instance(Foo,(),{}) + + a = A() + b = A() + self.assert_(a.inst is not b.inst) + + def test_args_kw(self): + class Foo(object): + def __init__(self, c): self.c = c + class Bar(object): pass + class Bah(object): + def __init__(self, c, d): + self.c = c; self.d = d + + class A(HasTraitlets): + inst = Instance(Foo, (10,)) + a = A() + self.assertEquals(a.inst.c, 10) + + class B(HasTraitlets): + inst = Instance(Bah, args=(10,), kw=dict(d=20)) + b = B() + self.assertEquals(b.inst.c, 10) + self.assertEquals(b.inst.d, 20) + + class C(HasTraitlets): + inst = Instance(Foo) + c = C() + self.assert_(c.inst is None) + + def test_bad_default(self): + class Foo(object): pass + + class A(HasTraitlets): + inst = Instance(Foo, allow_none=False) + + self.assertRaises(TraitletError, A) + + def test_instance(self): + class Foo(object): pass + + def inner(): + class A(HasTraitlets): + inst = Instance(Foo()) + + self.assertRaises(TraitletError, inner) + + +class TestThis(TestCase): + + def test_this_class(self): + class Foo(HasTraitlets): + this = This + + f = Foo() + self.assertEquals(f.this, None) + g = Foo() + f.this = g + self.assertEquals(f.this, g) + self.assertRaises(TraitletError, setattr, f, 'this', 10) + + def test_this_inst(self): + class Foo(HasTraitlets): + this = This() + + f = Foo() + f.this = Foo() + self.assert_(isinstance(f.this, Foo)) + + def test_subclass(self): + class Foo(HasTraitlets): + t = This() + class Bar(Foo): + pass + f = Foo() + b = Bar() + f.t = b + b.t = f + self.assertEquals(f.t, b) + self.assertEquals(b.t, f) + + def test_subclass_override(self): + class Foo(HasTraitlets): + t = This() + class Bar(Foo): + t = This() + f = Foo() + b = Bar() + f.t = b + self.assertEquals(f.t, b) + self.assertRaises(TraitletError, setattr, b, 't', f) + +class TraitletTestBase(TestCase): + """A best testing class for basic traitlet types.""" + + def assign(self, value): + self.obj.value = value + + def coerce(self, value): + return value + + def test_good_values(self): + if hasattr(self, '_good_values'): + for value in self._good_values: + self.assign(value) + self.assertEquals(self.obj.value, self.coerce(value)) + + def test_bad_values(self): + if hasattr(self, '_bad_values'): + for value in self._bad_values: + self.assertRaises(TraitletError, self.assign, value) + + def test_default_value(self): + if hasattr(self, '_default_value'): + self.assertEquals(self._default_value, self.obj.value) + + +class AnyTraitlet(HasTraitlets): + + value = Any + +class AnyTraitTest(TraitletTestBase): + + obj = AnyTraitlet() + + _default_value = None + _good_values = [10.0, 'ten', u'ten', [10], {'ten': 10},(10,), None, 1j] + _bad_values = [] + + +class IntTraitlet(HasTraitlets): + + value = Int(99) + +class TestInt(TraitletTestBase): + + obj = IntTraitlet() + _default_value = 99 + _good_values = [10, -10] + _bad_values = ['ten', u'ten', [10], {'ten': 10},(10,), None, 1j, 10L, + -10L, 10.1, -10.1, '10L', '-10L', '10.1', '-10.1', u'10L', + u'-10L', u'10.1', u'-10.1', '10', '-10', u'10', u'-10'] + + +class LongTraitlet(HasTraitlets): + + value = Long(99L) + +class TestLong(TraitletTestBase): + + obj = LongTraitlet() + + _default_value = 99L + _good_values = [10, -10, 10L, -10L] + _bad_values = ['ten', u'ten', [10], [10l], {'ten': 10},(10,),(10L,), + None, 1j, 10.1, -10.1, '10', '-10', '10L', '-10L', '10.1', + '-10.1', u'10', u'-10', u'10L', u'-10L', u'10.1', + u'-10.1'] + + +class FloatTraitlet(HasTraitlets): + + value = Float(99.0) + +class TestFloat(TraitletTestBase): + + obj = FloatTraitlet() + + _default_value = 99.0 + _good_values = [10, -10, 10.1, -10.1] + _bad_values = [10L, -10L, 'ten', u'ten', [10], {'ten': 10},(10,), None, + 1j, '10', '-10', '10L', '-10L', '10.1', '-10.1', u'10', + u'-10', u'10L', u'-10L', u'10.1', u'-10.1'] + + +class ComplexTraitlet(HasTraitlets): + + value = Complex(99.0-99.0j) + +class TestComplex(TraitletTestBase): + + obj = ComplexTraitlet() + + _default_value = 99.0-99.0j + _good_values = [10, -10, 10.1, -10.1, 10j, 10+10j, 10-10j, + 10.1j, 10.1+10.1j, 10.1-10.1j] + _bad_values = [10L, -10L, u'10L', u'-10L', 'ten', [10], {'ten': 10},(10,), None] + + +class StringTraitlet(HasTraitlets): + + value = Str('string') + +class TestString(TraitletTestBase): + + obj = StringTraitlet() + + _default_value = 'string' + _good_values = ['10', '-10', '10L', + '-10L', '10.1', '-10.1', 'string'] + _bad_values = [10, -10, 10L, -10L, 10.1, -10.1, 1j, [10], + ['ten'],{'ten': 10},(10,), None, u'string'] + + +class UnicodeTraitlet(HasTraitlets): + + value = Unicode(u'unicode') + +class TestUnicode(TraitletTestBase): + + obj = UnicodeTraitlet() + + _default_value = u'unicode' + _good_values = ['10', '-10', '10L', '-10L', '10.1', + '-10.1', '', u'', 'string', u'string', ] + _bad_values = [10, -10, 10L, -10L, 10.1, -10.1, 1j, + [10], ['ten'], [u'ten'], {'ten': 10},(10,), None] diff --git a/IPython/utils/traitlets.py b/IPython/utils/traitlets.py new file mode 100644 index 0000000..30d6211 --- /dev/null +++ b/IPython/utils/traitlets.py @@ -0,0 +1,1013 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +A lightweight Traits like module. + +This is designed to provide a lightweight, simple, pure Python version of +many of the capabilities of enthought.traits. This includes: + +* Validation +* Type specification with defaults +* Static and dynamic notification +* Basic predefined types +* An API that is similar to enthought.traits + +We don't support: + +* Delegation +* Automatic GUI generation +* A full set of trait types. Most importantly, we don't provide container + traitlets (list, dict, tuple) that can trigger notifications if their + contents change. +* API compatibility with enthought.traits + +There are also some important difference in our design: + +* enthought.traits does not validate default values. We do. + +We choose to create this module because we need these capabilities, but +we need them to be pure Python so they work in all Python implementations, +including Jython and IronPython. + +Authors: + +* Brian Granger +* Enthought, Inc. Some of the code in this file comes from enthought.traits + and is licensed under the BSD license. Also, many of the ideas also come + from enthought.traits even though our implementation is very different. +""" + +#----------------------------------------------------------------------------- +# Copyright (C) 2008-2009 The IPython Development Team +# +# Distributed under the terms of the BSD License. The full license is in +# the file COPYING, distributed as part of this software. +#----------------------------------------------------------------------------- + +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- + + +import inspect +import sys +import types +from types import ( + InstanceType, ClassType, FunctionType, + ListType, TupleType +) + +from IPython.utils.importstring import import_item + +ClassTypes = (ClassType, type) + +SequenceTypes = (ListType, TupleType) + +#----------------------------------------------------------------------------- +# Basic classes +#----------------------------------------------------------------------------- + + +class NoDefaultSpecified ( object ): pass +NoDefaultSpecified = NoDefaultSpecified() + + +class Undefined ( object ): pass +Undefined = Undefined() + + +class TraitletError(Exception): + pass + + +#----------------------------------------------------------------------------- +# Utilities +#----------------------------------------------------------------------------- + + +def class_of ( object ): + """ Returns a string containing the class name of an object with the + correct indefinite article ('a' or 'an') preceding it (e.g., 'an Image', + 'a PlotValue'). + """ + if isinstance( object, basestring ): + return add_article( object ) + + return add_article( object.__class__.__name__ ) + + +def add_article ( name ): + """ Returns a string containing the correct indefinite article ('a' or 'an') + prefixed to the specified string. + """ + if name[:1].lower() in 'aeiou': + return 'an ' + name + + return 'a ' + name + + +def repr_type(obj): + """ Return a string representation of a value and its type for readable + error messages. + """ + the_type = type(obj) + if the_type is InstanceType: + # Old-style class. + the_type = obj.__class__ + msg = '%r %r' % (obj, the_type) + return msg + + +def parse_notifier_name(name): + """Convert the name argument to a list of names. + + Examples + -------- + + >>> parse_notifier_name('a') + ['a'] + >>> parse_notifier_name(['a','b']) + ['a', 'b'] + >>> parse_notifier_name(None) + ['anytraitlet'] + """ + if isinstance(name, str): + return [name] + elif name is None: + return ['anytraitlet'] + elif isinstance(name, (list, tuple)): + for n in name: + assert isinstance(n, str), "names must be strings" + return name + + +class _SimpleTest: + def __init__ ( self, value ): self.value = value + def __call__ ( self, test ): + return test == self.value + def __repr__(self): + return " 0: + if len(self.metadata) > 0: + self._metadata = self.metadata.copy() + self._metadata.update(metadata) + else: + self._metadata = metadata + else: + self._metadata = self.metadata + + self.init() + + def init(self): + pass + + def get_default_value(self): + """Create a new instance of the default value.""" + dv = self.default_value + return dv + + def instance_init(self, obj): + """This is called by :meth:`HasTraitlets.__new__` to finish init'ing. + + Some stages of initialization must be delayed until the parent + :class:`HasTraitlets` instance has been created. This method is + called in :meth:`HasTraitlets.__new__` after the instance has been + created. + + This method trigger the creation and validation of default values + and also things like the resolution of str given class names in + :class:`Type` and :class`Instance`. + + Parameters + ---------- + obj : :class:`HasTraitlets` instance + The parent :class:`HasTraitlets` instance that has just been + created. + """ + self.set_default_value(obj) + + def set_default_value(self, obj): + """Set the default value on a per instance basis. + + This method is called by :meth:`instance_init` to create and + validate the default value. The creation and validation of + default values must be delayed until the parent :class:`HasTraitlets` + class has been instantiated. + """ + dv = self.get_default_value() + newdv = self._validate(obj, dv) + obj._traitlet_values[self.name] = newdv + + def __get__(self, obj, cls=None): + """Get the value of the traitlet by self.name for the instance. + + Default values are instantiated when :meth:`HasTraitlets.__new__` + is called. Thus by the time this method gets called either the + default value or a user defined value (they called :meth:`__set__`) + is in the :class:`HasTraitlets` instance. + """ + if obj is None: + return self + else: + try: + value = obj._traitlet_values[self.name] + except: + # HasTraitlets should call set_default_value to populate + # this. So this should never be reached. + raise TraitletError('Unexpected error in TraitletType: ' + 'default value not set properly') + else: + return value + + def __set__(self, obj, value): + new_value = self._validate(obj, value) + old_value = self.__get__(obj) + if old_value != new_value: + obj._traitlet_values[self.name] = new_value + obj._notify_traitlet(self.name, old_value, new_value) + + def _validate(self, obj, value): + if hasattr(self, 'validate'): + return self.validate(obj, value) + elif hasattr(self, 'is_valid_for'): + valid = self.is_valid_for(value) + if valid: + return value + else: + raise TraitletError('invalid value for type: %r' % value) + elif hasattr(self, 'value_for'): + return self.value_for(value) + else: + return value + + def info(self): + return self.info_text + + def error(self, obj, value): + if obj is not None: + e = "The '%s' traitlet of %s instance must be %s, but a value of %s was specified." \ + % (self.name, class_of(obj), + self.info(), repr_type(value)) + else: + e = "The '%s' traitlet must be %s, but a value of %r was specified." \ + % (self.name, self.info(), repr_type(value)) + raise TraitletError(e) + + def get_metadata(self, key): + return getattr(self, '_metadata', {}).get(key, None) + + def set_metadata(self, key, value): + getattr(self, '_metadata', {})[key] = value + + +#----------------------------------------------------------------------------- +# The HasTraitlets implementation +#----------------------------------------------------------------------------- + + +class MetaHasTraitlets(type): + """A metaclass for HasTraitlets. + + This metaclass makes sure that any TraitletType class attributes are + instantiated and sets their name attribute. + """ + + def __new__(mcls, name, bases, classdict): + """Create the HasTraitlets class. + + This instantiates all TraitletTypes in the class dict and sets their + :attr:`name` attribute. + """ + # print "MetaHasTraitlets (mcls, name): ", mcls, name + # print "MetaHasTraitlets (bases): ", bases + # print "MetaHasTraitlets (classdict): ", classdict + for k,v in classdict.iteritems(): + if isinstance(v, TraitletType): + v.name = k + elif inspect.isclass(v): + if issubclass(v, TraitletType): + vinst = v() + vinst.name = k + classdict[k] = vinst + return super(MetaHasTraitlets, mcls).__new__(mcls, name, bases, classdict) + + def __init__(cls, name, bases, classdict): + """Finish initializing the HasTraitlets class. + + This sets the :attr:`this_class` attribute of each TraitletType in the + class dict to the newly created class ``cls``. + """ + for k, v in classdict.iteritems(): + if isinstance(v, TraitletType): + v.this_class = cls + super(MetaHasTraitlets, cls).__init__(name, bases, classdict) + +class HasTraitlets(object): + + __metaclass__ = MetaHasTraitlets + + def __new__(cls, *args, **kw): + # This is needed because in Python 2.6 object.__new__ only accepts + # the cls argument. + new_meth = super(HasTraitlets, cls).__new__ + if new_meth is object.__new__: + inst = new_meth(cls) + else: + inst = new_meth(cls, *args, **kw) + inst._traitlet_values = {} + inst._traitlet_notifiers = {} + # Here we tell all the TraitletType instances to set their default + # values on the instance. + for key in dir(cls): + # Some descriptors raise AttributeError like zope.interface's + # __provides__ attributes even though they exist. This causes + # AttributeErrors even though they are listed in dir(cls). + try: + value = getattr(cls, key) + except AttributeError: + pass + else: + if isinstance(value, TraitletType): + value.instance_init(inst) + return inst + + # def __init__(self): + # self._traitlet_values = {} + # self._traitlet_notifiers = {} + + def _notify_traitlet(self, name, old_value, new_value): + + # First dynamic ones + callables = self._traitlet_notifiers.get(name,[]) + more_callables = self._traitlet_notifiers.get('anytraitlet',[]) + callables.extend(more_callables) + + # Now static ones + try: + cb = getattr(self, '_%s_changed' % name) + except: + pass + else: + callables.append(cb) + + # Call them all now + for c in callables: + # Traits catches and logs errors here. I allow them to raise + if callable(c): + argspec = inspect.getargspec(c) + nargs = len(argspec[0]) + # Bound methods have an additional 'self' argument + # I don't know how to treat unbound methods, but they + # can't really be used for callbacks. + if isinstance(c, types.MethodType): + offset = -1 + else: + offset = 0 + if nargs + offset == 0: + c() + elif nargs + offset == 1: + c(name) + elif nargs + offset == 2: + c(name, new_value) + elif nargs + offset == 3: + c(name, old_value, new_value) + else: + raise TraitletError('a traitlet changed callback ' + 'must have 0-3 arguments.') + else: + raise TraitletError('a traitlet changed callback ' + 'must be callable.') + + + def _add_notifiers(self, handler, name): + if not self._traitlet_notifiers.has_key(name): + nlist = [] + self._traitlet_notifiers[name] = nlist + else: + nlist = self._traitlet_notifiers[name] + if handler not in nlist: + nlist.append(handler) + + def _remove_notifiers(self, handler, name): + if self._traitlet_notifiers.has_key(name): + nlist = self._traitlet_notifiers[name] + try: + index = nlist.index(handler) + except ValueError: + pass + else: + del nlist[index] + + def on_traitlet_change(self, handler, name=None, remove=False): + """Setup a handler to be called when a traitlet changes. + + This is used to setup dynamic notifications of traitlet changes. + + Static handlers can be created by creating methods on a HasTraitlets + subclass with the naming convention '_[traitletname]_changed'. Thus, + to create static handler for the traitlet 'a', create the method + _a_changed(self, name, old, new) (fewer arguments can be used, see + below). + + Parameters + ---------- + handler : callable + A callable that is called when a traitlet changes. Its + signature can be handler(), handler(name), handler(name, new) + or handler(name, old, new). + name : list, str, None + If None, the handler will apply to all traitlets. If a list + of str, handler will apply to all names in the list. If a + str, the handler will apply just to that name. + remove : bool + If False (the default), then install the handler. If True + then unintall it. + """ + if remove: + names = parse_notifier_name(name) + for n in names: + self._remove_notifiers(handler, n) + else: + names = parse_notifier_name(name) + for n in names: + self._add_notifiers(handler, n) + + def traitlet_names(self, **metadata): + """Get a list of all the names of this classes traitlets.""" + return self.traitlets(**metadata).keys() + + def traitlets(self, **metadata): + """Get a list of all the traitlets of this class. + + The TraitletTypes returned don't know anything about the values + that the various HasTraitlet's instances are holding. + + This follows the same algorithm as traits does and does not allow + for any simple way of specifying merely that a metadata name + exists, but has any value. This is because get_metadata returns + None if a metadata key doesn't exist. + """ + traitlets = dict([memb for memb in getmembers(self.__class__) if \ + isinstance(memb[1], TraitletType)]) + + if len(metadata) == 0: + return traitlets + + for meta_name, meta_eval in metadata.items(): + if type(meta_eval) is not FunctionType: + metadata[meta_name] = _SimpleTest(meta_eval) + + result = {} + for name, traitlet in traitlets.items(): + for meta_name, meta_eval in metadata.items(): + if not meta_eval(traitlet.get_metadata(meta_name)): + break + else: + result[name] = traitlet + + return result + + def traitlet_metadata(self, traitletname, key): + """Get metadata values for traitlet by key.""" + try: + traitlet = getattr(self.__class__, traitletname) + except AttributeError: + raise TraitletError("Class %s does not have a traitlet named %s" % + (self.__class__.__name__, traitletname)) + else: + return traitlet.get_metadata(key) + +#----------------------------------------------------------------------------- +# Actual TraitletTypes implementations/subclasses +#----------------------------------------------------------------------------- + +#----------------------------------------------------------------------------- +# TraitletTypes subclasses for handling classes and instances of classes +#----------------------------------------------------------------------------- + + +class ClassBasedTraitletType(TraitletType): + """A traitlet with error reporting for Type, Instance and This.""" + + def error(self, obj, value): + kind = type(value) + if kind is InstanceType: + msg = 'class %s' % value.__class__.__name__ + else: + msg = '%s (i.e. %s)' % ( str( kind )[1:-1], repr( value ) ) + + super(ClassBasedTraitletType, self).error(obj, msg) + + +class Type(ClassBasedTraitletType): + """A traitlet whose value must be a subclass of a specified class.""" + + def __init__ (self, default_value=None, klass=None, allow_none=True, **metadata ): + """Construct a Type traitlet + + A Type traitlet specifies that its values must be subclasses of + a particular class. + + If only ``default_value`` is given, it is used for the ``klass`` as + well. + + Parameters + ---------- + default_value : class, str or None + The default value must be a subclass of klass. If an str, + the str must be a fully specified class name, like 'foo.bar.Bah'. + The string is resolved into real class, when the parent + :class:`HasTraitlets` class is instantiated. + klass : class, str, None + Values of this traitlet must be a subclass of klass. The klass + may be specified in a string like: 'foo.bar.MyClass'. + The string is resolved into real class, when the parent + :class:`HasTraitlets` class is instantiated. + allow_none : boolean + Indicates whether None is allowed as an assignable value. Even if + ``False``, the default value may be ``None``. + """ + if default_value is None: + if klass is None: + klass = object + elif klass is None: + klass = default_value + + if not (inspect.isclass(klass) or isinstance(klass, basestring)): + raise TraitletError("A Type traitlet must specify a class.") + + self.klass = klass + self._allow_none = allow_none + + super(Type, self).__init__(default_value, **metadata) + + def validate(self, obj, value): + """Validates that the value is a valid object instance.""" + try: + if issubclass(value, self.klass): + return value + except: + if (value is None) and (self._allow_none): + return value + + self.error(obj, value) + + def info(self): + """ Returns a description of the trait.""" + if isinstance(self.klass, basestring): + klass = self.klass + else: + klass = self.klass.__name__ + result = 'a subclass of ' + klass + if self._allow_none: + return result + ' or None' + return result + + def instance_init(self, obj): + self._resolve_classes() + super(Type, self).instance_init(obj) + + def _resolve_classes(self): + if isinstance(self.klass, basestring): + self.klass = import_item(self.klass) + if isinstance(self.default_value, basestring): + self.default_value = import_item(self.default_value) + + def get_default_value(self): + return self.default_value + + +class DefaultValueGenerator(object): + """A class for generating new default value instances.""" + + def __init__(self, *args, **kw): + self.args = args + self.kw = kw + + def generate(self, klass): + return klass(*self.args, **self.kw) + + +class Instance(ClassBasedTraitletType): + """A trait whose value must be an instance of a specified class. + + The value can also be an instance of a subclass of the specified class. + """ + + def __init__(self, klass=None, args=None, kw=None, + allow_none=True, **metadata ): + """Construct an Instance traitlet. + + This traitlet allows values that are instances of a particular + class or its sublclasses. Our implementation is quite different + from that of enthough.traits as we don't allow instances to be used + for klass and we handle the ``args`` and ``kw`` arguments differently. + + Parameters + ---------- + klass : class, str + The class that forms the basis for the traitlet. Class names + can also be specified as strings, like 'foo.bar.Bar'. + args : tuple + Positional arguments for generating the default value. + kw : dict + Keyword arguments for generating the default value. + allow_none : bool + Indicates whether None is allowed as a value. + + Default Value + ------------- + If both ``args`` and ``kw`` are None, then the default value is None. + If ``args`` is a tuple and ``kw`` is a dict, then the default is + created as ``klass(*args, **kw)``. If either ``args`` or ``kw`` is + not (but not both), None is replace by ``()`` or ``{}``. + """ + + self._allow_none = allow_none + + if (klass is None) or (not (inspect.isclass(klass) or isinstance(klass, basestring))): + raise TraitletError('The klass argument must be a class' + ' you gave: %r' % klass) + self.klass = klass + + # self.klass is a class, so handle default_value + if args is None and kw is None: + default_value = None + else: + if args is None: + # kw is not None + args = () + elif kw is None: + # args is not None + kw = {} + + if not isinstance(kw, dict): + raise TraitletError("The 'kw' argument must be a dict or None.") + if not isinstance(args, tuple): + raise TraitletError("The 'args' argument must be a tuple or None.") + + default_value = DefaultValueGenerator(*args, **kw) + + super(Instance, self).__init__(default_value, **metadata) + + def validate(self, obj, value): + if value is None: + if self._allow_none: + return value + self.error(obj, value) + + if isinstance(value, self.klass): + return value + else: + self.error(obj, value) + + def info(self): + if isinstance(self.klass, basestring): + klass = self.klass + else: + klass = self.klass.__name__ + result = class_of(klass) + if self._allow_none: + return result + ' or None' + + return result + + def instance_init(self, obj): + self._resolve_classes() + super(Instance, self).instance_init(obj) + + def _resolve_classes(self): + if isinstance(self.klass, basestring): + self.klass = import_item(self.klass) + + def get_default_value(self): + """Instantiate a default value instance. + + This is called when the containing HasTraitlets classes' + :meth:`__new__` method is called to ensure that a unique instance + is created for each HasTraitlets instance. + """ + dv = self.default_value + if isinstance(dv, DefaultValueGenerator): + return dv.generate(self.klass) + else: + return dv + + +class This(ClassBasedTraitletType): + """A traitlet for instances of the class containing this trait. + + Because how how and when class bodies are executed, the ``This`` + traitlet can only have a default value of None. This, and because we + always validate default values, ``allow_none`` is *always* true. + """ + + info_text = 'an instance of the same type as the receiver or None' + + def __init__(self, **metadata): + super(This, self).__init__(None, **metadata) + + def validate(self, obj, value): + # What if value is a superclass of obj.__class__? This is + # complicated if it was the superclass that defined the This + # traitlet. + if isinstance(value, self.this_class) or (value is None): + return value + else: + self.error(obj, value) + + +#----------------------------------------------------------------------------- +# Basic TraitletTypes implementations/subclasses +#----------------------------------------------------------------------------- + + +class Any(TraitletType): + default_value = None + info_text = 'any value' + + +class Int(TraitletType): + """A integer traitlet.""" + + evaluate = int + default_value = 0 + info_text = 'an integer' + + def validate(self, obj, value): + if isinstance(value, int): + return value + self.error(obj, value) + +class CInt(Int): + """A casting version of the int traitlet.""" + + def validate(self, obj, value): + try: + return int(value) + except: + self.error(obj, value) + + +class Long(TraitletType): + """A long integer traitlet.""" + + evaluate = long + default_value = 0L + info_text = 'a long' + + def validate(self, obj, value): + if isinstance(value, long): + return value + if isinstance(value, int): + return long(value) + self.error(obj, value) + + +class CLong(Long): + """A casting version of the long integer traitlet.""" + + def validate(self, obj, value): + try: + return long(value) + except: + self.error(obj, value) + + +class Float(TraitletType): + """A float traitlet.""" + + evaluate = float + default_value = 0.0 + info_text = 'a float' + + def validate(self, obj, value): + if isinstance(value, float): + return value + if isinstance(value, int): + return float(value) + self.error(obj, value) + + +class CFloat(Float): + """A casting version of the float traitlet.""" + + def validate(self, obj, value): + try: + return float(value) + except: + self.error(obj, value) + +class Complex(TraitletType): + """A traitlet for complex numbers.""" + + evaluate = complex + default_value = 0.0 + 0.0j + info_text = 'a complex number' + + def validate(self, obj, value): + if isinstance(value, complex): + return value + if isinstance(value, (float, int)): + return complex(value) + self.error(obj, value) + + +class CComplex(Complex): + """A casting version of the complex number traitlet.""" + + def validate (self, obj, value): + try: + return complex(value) + except: + self.error(obj, value) + + +class Str(TraitletType): + """A traitlet for strings.""" + + evaluate = lambda x: x + default_value = '' + info_text = 'a string' + + def validate(self, obj, value): + if isinstance(value, str): + return value + self.error(obj, value) + + +class CStr(Str): + """A casting version of the string traitlet.""" + + def validate(self, obj, value): + try: + return str(value) + except: + try: + return unicode(value) + except: + self.error(obj, value) + + +class Unicode(TraitletType): + """A traitlet for unicode strings.""" + + evaluate = unicode + default_value = u'' + info_text = 'a unicode string' + + def validate(self, obj, value): + if isinstance(value, unicode): + return value + if isinstance(value, str): + return unicode(value) + self.error(obj, value) + + +class CUnicode(Unicode): + """A casting version of the unicode traitlet.""" + + def validate(self, obj, value): + try: + return unicode(value) + except: + self.error(obj, value) + + +class Bool(TraitletType): + """A boolean (True, False) traitlet.""" + evaluate = bool + default_value = False + info_text = 'a boolean' + + def validate(self, obj, value): + if isinstance(value, bool): + return value + self.error(obj, value) + + +class CBool(Bool): + """A casting version of the boolean traitlet.""" + + def validate(self, obj, value): + try: + return bool(value) + except: + self.error(obj, value) + + +class Enum(TraitletType): + """An enum that whose value must be in a given sequence.""" + + def __init__(self, values, default_value=None, allow_none=True, **metadata): + self.values = values + self._allow_none = allow_none + super(Enum, self).__init__(default_value, **metadata) + + def validate(self, obj, value): + if value is None: + if self._allow_none: + return value + + if value in self.values: + return value + self.error(obj, value) + + def info(self): + """ Returns a description of the trait.""" + result = 'any of ' + repr(self.values) + if self._allow_none: + return result + ' or None' + return result + +class CaselessStrEnum(Enum): + """An enum of strings that are caseless in validate.""" + + def validate(self, obj, value): + if value is None: + if self._allow_none: + return value + + if not isinstance(value, str): + self.error(obj, value) + + for v in self.values: + if v.lower() == value.lower(): + return v + self.error(obj, value) + + +class List(Instance): + """An instance of a Python list.""" + + def __init__(self, default_value=None, allow_none=True, **metadata): + """Create a list traitlet type from a list or tuple. + + The default value is created by doing ``list(default_value)``, + which creates a copy of the ``default_value``. + """ + if default_value is None: + args = ((),) + elif isinstance(default_value, SequenceTypes): + args = (default_value,) + + super(List,self).__init__(klass=list, args=args, + allow_none=allow_none, **metadata) diff --git a/IPython/upgrade_dir.py b/IPython/utils/upgradedir.py similarity index 100% rename from IPython/upgrade_dir.py rename to IPython/utils/upgradedir.py diff --git a/IPython/wildcard.py b/IPython/utils/wildcard.py similarity index 99% rename from IPython/wildcard.py rename to IPython/utils/wildcard.py index 3e62d8a..a82b9da 100644 --- a/IPython/wildcard.py +++ b/IPython/utils/wildcard.py @@ -20,7 +20,7 @@ import pprint import re import types -from IPython.genutils import dir2 +from IPython.utils.genutils import dir2 def create_typestr2type_dicts(dont_include_in_type2type2str=["lambda"]): """Return dictionaries mapping lower case typename to type objects, from diff --git a/IPython/winconsole.py b/IPython/utils/winconsole.py similarity index 100% rename from IPython/winconsole.py rename to IPython/utils/winconsole.py diff --git a/MANIFEST.in b/MANIFEST.in index 880dfd5..841d4ea 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -2,18 +2,22 @@ include ipython.py include setupbase.py include setupegg.py -graft scripts - graft setupext -graft IPython/UserConfig - +graft scripts graft IPython/kernel graft IPython/config +graft IPython/core +graft IPython/deathrow +graft IPython/external +graft IPython/frontend +graft IPython/gui +graft IPython/lib +graft IPython/quarantine +graft IPython/scripts graft IPython/testing -graft IPython/tools +graft IPython/utils -recursive-include IPython/Extensions igrid_help* graft docs exclude docs/\#* diff --git a/docs/Makefile b/docs/Makefile index d7e9363..9d34c01 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -14,6 +14,8 @@ ALLSPHINXOPTS = -d build/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) $(SRCDIR) .PHONY: help clean html web pickle htmlhelp latex changes linkcheck api +default: html + help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" diff --git a/docs/autogen_api.py b/docs/autogen_api.py index c7c54de..3a40acc 100755 --- a/docs/autogen_api.py +++ b/docs/autogen_api.py @@ -15,17 +15,26 @@ if __name__ == '__main__': package = 'IPython' outdir = pjoin('source','api','generated') docwriter = ApiDocWriter(package,rst_extension='.txt') + # You have to escape the . here because . is a special char for regexps. + # You must do make clean if you change this! docwriter.package_skip_patterns += [r'\.fixes$', - r'\.externals$', - r'\.Extensions', - r'\.kernel.config', + r'\.external$', + r'\.extensions', + r'\.kernel\.config', r'\.attic', + r'\.quarantine', + r'\.deathrow', + r'\.config\.default', + r'\.config\.profile', + r'\.frontend', + r'\.gui' ] - docwriter.module_skip_patterns += [ r'\.FakeModule', + docwriter.module_skip_patterns += [ r'\.core\.fakemodule', r'\.cocoa', r'\.ipdoctest', r'\.Gnuplot', - r'\.frontend.process.winprocess', + r'\.frontend\.process\.winprocess', + r'\.Shell', ] docwriter.write_api_docs(outdir) docwriter.write_index(outdir, 'gen', diff --git a/docs/examples/core/example-embed.py b/docs/examples/core/example-embed.py index 137fbb1..3c451ef 100755 --- a/docs/examples/core/example-embed.py +++ b/docs/examples/core/example-embed.py @@ -31,7 +31,7 @@ else: '-po','Out<\\#>: ','-nosep'] # First import the embeddable shell class -from IPython.Shell import IPShellEmbed +from IPython.core.shell import IPShellEmbed # Now create an instance of the embeddable shell. The first argument is a # string with options exactly as you would type them if you were starting diff --git a/docs/examples/core/new-embed.py b/docs/examples/core/new-embed.py new file mode 100644 index 0000000..f0eddf5 --- /dev/null +++ b/docs/examples/core/new-embed.py @@ -0,0 +1,17 @@ +# This shows how to use the new top-level embed function. It is a simpler +# API that manages the creation of the embedded shell. + +from IPython import embed + +a = 10 +b = 20 + +embed('First time') + +c = 30 +d = 40 + +try: + raise Exception('adsfasdf') +except: + embed('The second time') diff --git a/docs/examples/kernel/fractal.py b/docs/examples/kernel/fractal.py new file mode 100644 index 0000000..715d30a --- /dev/null +++ b/docs/examples/kernel/fractal.py @@ -0,0 +1,90 @@ +from numpy import * + +def mandel(n, m, itermax, xmin, xmax, ymin, ymax): + ''' + Fast mandelbrot computation using numpy. + + (n, m) are the output image dimensions + itermax is the maximum number of iterations to do + xmin, xmax, ymin, ymax specify the region of the + set to compute. + ''' + # The point of ix and iy is that they are 2D arrays + # giving the x-coord and y-coord at each point in + # the array. The reason for doing this will become + # clear below... + ix, iy = mgrid[0:n, 0:m] + # Now x and y are the x-values and y-values at each + # point in the array, linspace(start, end, n) + # is an array of n linearly spaced points between + # start and end, and we then index this array using + # numpy fancy indexing. If A is an array and I is + # an array of indices, then A[I] has the same shape + # as I and at each place i in I has the value A[i]. + x = linspace(xmin, xmax, n)[ix] + y = linspace(ymin, ymax, m)[iy] + # c is the complex number with the given x, y coords + c = x+complex(0,1)*y + del x, y # save a bit of memory, we only need z + # the output image coloured according to the number + # of iterations it takes to get to the boundary + # abs(z)>2 + img = zeros(c.shape, dtype=int) + # Here is where the improvement over the standard + # algorithm for drawing fractals in numpy comes in. + # We flatten all the arrays ix, iy and c. This + # flattening doesn't use any more memory because + # we are just changing the shape of the array, the + # data in memory stays the same. It also affects + # each array in the same way, so that index i in + # array c has x, y coords ix[i], iy[i]. The way the + # algorithm works is that whenever abs(z)>2 we + # remove the corresponding index from each of the + # arrays ix, iy and c. Since we do the same thing + # to each array, the correspondence between c and + # the x, y coords stored in ix and iy is kept. + ix.shape = n*m + iy.shape = n*m + c.shape = n*m + # we iterate z->z^2+c with z starting at 0, but the + # first iteration makes z=c so we just start there. + # We need to copy c because otherwise the operation + # z->z^2 will send c->c^2. + z = copy(c) + for i in xrange(itermax): + if not len(z): break # all points have escaped + # equivalent to z = z*z+c but quicker and uses + # less memory + multiply(z, z, z) + add(z, c, z) + # these are the points that have escaped + rem = abs(z)>2.0 + # colour them with the iteration number, we + # add one so that points which haven't + # escaped have 0 as their iteration number, + # this is why we keep the arrays ix and iy + # because we need to know which point in img + # to colour + img[ix[rem], iy[rem]] = i+1 + # -rem is the array of points which haven't + # escaped, in numpy -A for a boolean array A + # is the NOT operation. + rem = -rem + # So we select out the points in + # z, ix, iy and c which are still to be + # iterated on in the next step + z = z[rem] + ix, iy = ix[rem], iy[rem] + c = c[rem] + return img + +if __name__=='__main__': + from pylab import * + import time + start = time.time() + I = mandel(400, 400, 100, -2, .5, -1.25, 1.25) + print 'Time taken:', time.time()-start + I[I==0] = 101 + img = imshow(I.T, origin='lower left') + img.write_png('mandel.png', noscale=True) + show() diff --git a/docs/examples/kernel/mcdriver.py b/docs/examples/kernel/mcdriver.py index 2a5a8e1..6f493ee 100644 --- a/docs/examples/kernel/mcdriver.py +++ b/docs/examples/kernel/mcdriver.py @@ -1,71 +1,71 @@ #!/usr/bin/env python -# encoding: utf-8 """Run a Monte-Carlo options pricer in parallel.""" from IPython.kernel import client -import numpy as N -from mcpricer import MCOptionPricer +import numpy as np +from mcpricer import price_options +# The MultiEngineClient is used to setup the calculation and works with all +# engine. +mec = client.MultiEngineClient(profile='mycluster') -tc = client.TaskClient() -rc = client.MultiEngineClient() +# The TaskClient is an interface to the engines that provides dynamic load +# balancing at the expense of not knowing which engine will execute the code. +tc = client.TaskClient(profile='mycluster') -# Initialize the common code on the engines -rc.run('mcpricer.py') +# Initialize the common code on the engines. This Python module has the +# price_options function that prices the options. +mec.run('mcpricer.py') -# Push the variables that won't change -#(stock print, interest rate, days and MC paths) -rc.push(dict(S=100.0, r=0.05, days=260, paths=10000)) - -task_string = """\ -op = MCOptionPricer(S,K,sigma,r,days,paths) -op.run() -vp, ap, vc, ac = op.vanilla_put, op.asian_put, op.vanilla_call, op.asian_call -""" +# Define the function that will make up our tasks. We basically want to +# call the price_options function with all but two arguments (K, sigma) +# fixed. +def my_prices(K, sigma): + S = 100.0 + r = 0.05 + days = 260 + paths = 100000 + return price_options(S, K, sigma, r, days, paths) # Create arrays of strike prices and volatilities -K_vals = N.linspace(90.0,100.0,5) -sigma_vals = N.linspace(0.0, 0.2,5) +nK = 10 +nsigma = 10 +K_vals = np.linspace(90.0, 100.0, nK) +sigma_vals = np.linspace(0.1, 0.4, nsigma) -# Submit tasks +# Submit tasks to the TaskClient for each (K, sigma) pair as a MapTask. +# The MapTask simply applies a function (my_prices) to the arguments: +# my_prices(K, sigma) and returns the result. taskids = [] for K in K_vals: for sigma in sigma_vals: - t = client.StringTask(task_string, - push=dict(sigma=sigma,K=K), - pull=('vp','ap','vc','ac','sigma','K')) + t = client.MapTask(my_prices, args=(K, sigma)) taskids.append(tc.run(t)) -print "Submitted tasks: ", taskids +print "Submitted tasks: ", len(taskids) -# Block until tasks are completed +# Block until all tasks are completed. tc.barrier(taskids) -# Get the results +# Get the results using TaskClient.get_task_result. results = [tc.get_task_result(tid) for tid in taskids] -# Assemble the result -vc = N.empty(K_vals.shape[0]*sigma_vals.shape[0],dtype='float64') -vp = N.empty(K_vals.shape[0]*sigma_vals.shape[0],dtype='float64') -ac = N.empty(K_vals.shape[0]*sigma_vals.shape[0],dtype='float64') -ap = N.empty(K_vals.shape[0]*sigma_vals.shape[0],dtype='float64') -for i, tr in enumerate(results): - ns = tr.ns - vc[i] = ns.vc - vp[i] = ns.vp - ac[i] = ns.ac - ap[i] = ns.ap -vc.shape = (K_vals.shape[0],sigma_vals.shape[0]) -vp.shape = (K_vals.shape[0],sigma_vals.shape[0]) -ac.shape = (K_vals.shape[0],sigma_vals.shape[0]) -ap.shape = (K_vals.shape[0],sigma_vals.shape[0]) - +# Assemble the result into a structured NumPy array. +prices = np.empty(nK*nsigma, + dtype=[('ecall',float),('eput',float),('acall',float),('aput',float)] +) +for i, price_tuple in enumerate(results): + prices[i] = price_tuple +prices.shape = (nK, nsigma) +K_vals, sigma_vals = np.meshgrid(K_vals, sigma_vals) -def plot_options(K_vals, sigma_vals, prices): - """Make a contour plot of the option prices.""" - import pylab - pylab.contourf(sigma_vals, K_vals, prices) - pylab.colorbar() - pylab.title("Option Price") - pylab.xlabel("Volatility") - pylab.ylabel("Strike Price") +def plot_options(sigma_vals, K_vals, prices): + """ + Make a contour plot of the option price in (sigma, K) space. + """ + from matplotlib import pyplot as plt + plt.contourf(sigma_vals, K_vals, prices) + plt.colorbar() + plt.title("Option Price") + plt.xlabel("Volatility") + plt.ylabel("Strike Price") diff --git a/docs/examples/kernel/mcpricer.py b/docs/examples/kernel/mcpricer.py index 2dac9f1..29772ff 100644 --- a/docs/examples/kernel/mcpricer.py +++ b/docs/examples/kernel/mcpricer.py @@ -1,43 +1,45 @@ -import numpy as N +import numpy as np from math import * -class MCOptionPricer(object): - def __init__(self, S=100.0, K=100.0, sigma=0.25, r=0.05, days=260, paths=10000): - self.S = S - self.K = K - self.sigma = sigma - self.r = r - self.days = days - self.paths = paths - self.h = 1.0/self.days - self.const1 = exp((self.r-0.5*self.sigma**2)*self.h) - self.const2 = self.sigma*sqrt(self.h) - - def run(self): - stock_price = self.S*N.ones(self.paths, dtype='float64') - stock_price_sum = N.zeros(self.paths, dtype='float64') - for j in range(self.days): - growth_factor = self.const1*N.exp(self.const2*N.random.standard_normal(self.paths)) - stock_price = stock_price*growth_factor - stock_price_sum = stock_price_sum + stock_price - stock_price_avg = stock_price_sum/self.days - zeros = N.zeros(self.paths, dtype='float64') - r_factor = exp(-self.r*self.h*self.days) - self.vanilla_put = r_factor*N.mean(N.maximum(zeros,self.K-stock_price)) - self.asian_put = r_factor*N.mean(N.maximum(zeros,self.K-stock_price_avg)) - self.vanilla_call = r_factor*N.mean(N.maximum(zeros,stock_price-self.K)) - self.asian_call = r_factor*N.mean(N.maximum(zeros,stock_price_avg-self.K)) +def price_options(S=100.0, K=100.0, sigma=0.25, r=0.05, days=260, paths=10000): + """ + Price European and Asian options using a Monte Carlo method. -def main(): - op = MCOptionPricer() - op.run() - print "Vanilla Put Price = ", op.vanilla_put - print "Asian Put Price = ", op.asian_put - print "Vanilla Call Price = ", op.vanilla_call - print "Asian Call Price = ", op.asian_call + Parameters + ---------- + S : float + The initial price of the stock. + K : float + The strike price of the option. + sigma : float + The volatility of the stock. + r : float + The risk free interest rate. + days : int + The number of days until the option expires. + paths : int + The number of Monte Carlo paths used to price the option. - -if __name__ == '__main__': - main() + Returns + ------- + A tuple of (E. call, E. put, A. call, A. put) option prices. + """ + h = 1.0/days + const1 = exp((r-0.5*sigma**2)*h) + const2 = sigma*sqrt(h) + stock_price = S*np.ones(paths, dtype='float64') + stock_price_sum = np.zeros(paths, dtype='float64') + for j in range(days): + growth_factor = const1*np.exp(const2*np.random.standard_normal(paths)) + stock_price = stock_price*growth_factor + stock_price_sum = stock_price_sum + stock_price + stock_price_avg = stock_price_sum/days + zeros = np.zeros(paths, dtype='float64') + r_factor = exp(-r*h*days) + euro_put = r_factor*np.mean(np.maximum(zeros, K-stock_price)) + asian_put = r_factor*np.mean(np.maximum(zeros, K-stock_price_avg)) + euro_call = r_factor*np.mean(np.maximum(zeros, stock_price-K)) + asian_call = r_factor*np.mean(np.maximum(zeros, stock_price_avg-K)) + return (euro_call, euro_put, asian_call, asian_put) diff --git a/docs/examples/kernel/parallelpi.py b/docs/examples/kernel/parallelpi.py new file mode 100644 index 0000000..1254407 --- /dev/null +++ b/docs/examples/kernel/parallelpi.py @@ -0,0 +1,54 @@ +"""Calculate statistics on the digits of pi in parallel. + +This program uses the functions in :file:`pidigits.py` to calculate +the frequencies of 2 digit sequences in the digits of pi. The +results are plotted using matplotlib. + +To run, text files from http://www.super-computing.org/ +must be installed in the working directory of the IPython engines. +The actual filenames to be used can be set with the ``filestring`` +variable below. + +The dataset we have been using for this is the 200 million digit one here: +ftp://pi.super-computing.org/.2/pi200m/ +""" + +from IPython.kernel import client +from matplotlib import pyplot as plt +import numpy as np +from pidigits import * +from timeit import default_timer as clock + + +# Files with digits of pi (10m digits each) +filestring = 'pi200m-ascii-%(i)02dof20.txt' +files = [filestring % {'i':i} for i in range(1,16)] + + +# Connect to the IPython cluster +mec = client.MultiEngineClient(profile='mycluster') +mec.run('pidigits.py') + + +# Run 10m digits on 1 engine +mapper = mec.mapper(targets=0) +t1 = clock() +freqs10m = mapper.map(compute_two_digit_freqs, files[:1])[0] +t2 = clock() +digits_per_second1 = 10.0e6/(t2-t1) +print "Digits per second (1 core, 10m digits): ", digits_per_second1 + + +# Run 150m digits on 15 engines (8 cores) +t1 = clock() +freqs_all = mec.map(compute_two_digit_freqs, files[:len(mec)]) +freqs150m = reduce_freqs(freqs_all) +t2 = clock() +digits_per_second8 = 150.0e6/(t2-t1) +print "Digits per second (8 cores, 150m digits): ", digits_per_second8 + +print "Speedup: ", digits_per_second8/digits_per_second1 + +plot_two_digit_freqs(freqs150m) +plt.title("2 digit sequences in 150m digits of pi") + diff --git a/docs/examples/kernel/pidigits.py b/docs/examples/kernel/pidigits.py new file mode 100644 index 0000000..7234d50 --- /dev/null +++ b/docs/examples/kernel/pidigits.py @@ -0,0 +1,144 @@ +"""Compute statistics on the digits of pi. + +This uses precomputed digits of pi from the website +of Professor Yasumasa Kanada at the University of +Tokoyo: http://www.super-computing.org/ + +Currently, there are only functions to read the +.txt (non-compressed, non-binary) files, but adding +support for compression and binary files would be +straightforward. + +This focuses on computing the number of times that +all 1, 2, n digits sequences occur in the digits of pi. +If the digits of pi are truly random, these frequencies +should be equal. +""" + +# Import statements + +from __future__ import division, with_statement +import numpy as np +from matplotlib import pyplot as plt + +# Top-level functions + +def compute_one_digit_freqs(filename): + """ + Read digits of pi from a file and compute the 1 digit frequencies. + """ + d = txt_file_to_digits(filename) + freqs = one_digit_freqs(d) + return freqs + +def compute_two_digit_freqs(filename): + """ + Read digits of pi from a file and compute the 2 digit frequencies. + """ + d = txt_file_to_digits(filename) + freqs = two_digit_freqs(d) + return freqs + +def reduce_freqs(freqlist): + """ + Add up a list of freq counts to get the total counts. + """ + allfreqs = np.zeros_like(freqlist[0]) + for f in freqlist: + allfreqs += f + return allfreqs + +def compute_n_digit_freqs(filename, n): + """ + Read digits of pi from a file and compute the n digit frequencies. + """ + d = txt_file_to_digits(filename) + freqs = n_digit_freqs(d, n) + return freqs + +# Read digits from a txt file + +def txt_file_to_digits(filename, the_type=str): + """ + Yield the digits of pi read from a .txt file. + """ + with open(filename, 'r') as f: + for line in f.readlines(): + for c in line: + if c != '\n' and c!= ' ': + yield the_type(c) + +# Actual counting functions + +def one_digit_freqs(digits, normalize=False): + """ + Consume digits of pi and compute 1 digit freq. counts. + """ + freqs = np.zeros(10, dtype='i4') + for d in digits: + freqs[int(d)] += 1 + if normalize: + freqs = freqs/freqs.sum() + return freqs + +def two_digit_freqs(digits, normalize=False): + """ + Consume digits of pi and compute 2 digits freq. counts. + """ + freqs = np.zeros(100, dtype='i4') + last = digits.next() + this = digits.next() + for d in digits: + index = int(last + this) + freqs[index] += 1 + last = this + this = d + if normalize: + freqs = freqs/freqs.sum() + return freqs + +def n_digit_freqs(digits, n, normalize=False): + """ + Consume digits of pi and compute n digits freq. counts. + + This should only be used for 1-6 digits. + """ + freqs = np.zeros(pow(10,n), dtype='i4') + current = np.zeros(n, dtype=int) + for i in range(n): + current[i] = digits.next() + for d in digits: + index = int(''.join(map(str, current))) + freqs[index] += 1 + current[0:-1] = current[1:] + current[-1] = d + if normalize: + freqs = freqs/freqs.sum() + return freqs + +# Plotting functions + +def plot_two_digit_freqs(f2): + """ + Plot two digits frequency counts using matplotlib. + """ + f2_copy = f2.copy() + f2_copy.shape = (10,10) + ax = plt.matshow(f2_copy) + plt.colorbar() + for i in range(10): + for j in range(10): + plt.text(i-0.2, j+0.2, str(j)+str(i)) + plt.ylabel('First digit') + plt.xlabel('Second digit') + return ax + +def plot_one_digit_freqs(f1): + """ + Plot one digit frequency counts using matplotlib. + """ + ax = plt.plot(f1,'bo-') + plt.title('Single digit counts in pi') + plt.xlabel('Digit') + plt.ylabel('Count') + return ax diff --git a/docs/examples/kernel/wordfreq.py b/docs/examples/kernel/wordfreq.py index e76f39f..d762f18 100644 --- a/docs/examples/kernel/wordfreq.py +++ b/docs/examples/kernel/wordfreq.py @@ -1,13 +1,20 @@ """Count the frequencies of words in a string""" +from __future__ import division + +import cmath as math + + def wordfreq(text): """Return a dictionary of words and word counts in a string.""" freqs = {} for word in text.split(): - freqs[word] = freqs.get(word, 0) + 1 + lword = word.lower() + freqs[lword] = freqs.get(lword, 0) + 1 return freqs + def print_wordfreq(freqs, n=10): """Print the n most common words and counts in the freqs dict.""" @@ -17,7 +24,43 @@ def print_wordfreq(freqs, n=10): for (count, word) in items[:n]: print word, count -if __name__ == '__main__': - import gzip - text = gzip.open('HISTORY.gz').read() - freqs = wordfreq(text) \ No newline at end of file + +def wordfreq_to_weightsize(worddict, minsize=25, maxsize=50, minalpha=0.5, maxalpha=1.0): + mincount = min(worddict.itervalues()) + maxcount = max(worddict.itervalues()) + weights = {} + for k, v in worddict.iteritems(): + w = (v-mincount)/(maxcount-mincount) + alpha = minalpha + (maxalpha-minalpha)*w + size = minsize + (maxsize-minsize)*w + weights[k] = (alpha, size) + return weights + + +def tagcloud(worddict, n=10, minsize=25, maxsize=50, minalpha=0.5, maxalpha=1.0): + from matplotlib import pyplot as plt + import random + + worddict = wordfreq_to_weightsize(worddict, minsize, maxsize, minalpha, maxalpha) + + fig = plt.figure() + ax = fig.add_subplot(111) + ax.set_position([0.0,0.0,1.0,1.0]) + plt.xticks([]) + plt.yticks([]) + + words = worddict.keys() + alphas = [v[0] for v in worddict.values()] + sizes = [v[1] for v in worddict.values()] + items = zip(alphas, sizes, words) + items.sort(reverse=True) + for alpha, size, word in items[:n]: + # xpos = random.normalvariate(0.5, 0.3) + # ypos = random.normalvariate(0.5, 0.3) + xpos = random.uniform(0.0,1.0) + ypos = random.uniform(0.0,1.0) + ax.text(xpos, ypos, word.lower(), alpha=alpha, fontsize=size) + ax.autoscale_view() + return ax + + \ No newline at end of file diff --git a/docs/examples/lib/gui-gtk.py b/docs/examples/lib/gui-gtk.py new file mode 100755 index 0000000..6b45ef1 --- /dev/null +++ b/docs/examples/lib/gui-gtk.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python +"""Simple GTK example to manually test event loop integration. + +This is meant to run tests manually in ipython as: + +In [5]: %gui gtk + +In [6]: %run gui-gtk.py +""" + + +import pygtk +pygtk.require('2.0') +import gtk + + +def hello_world(wigdet, data=None): + print "Hello World" + +def delete_event(widget, event, data=None): + return False + +def destroy(widget, data=None): + gtk.main_quit() + +window = gtk.Window(gtk.WINDOW_TOPLEVEL) +window.connect("delete_event", delete_event) +window.connect("destroy", destroy) +button = gtk.Button("Hello World") +button.connect("clicked", hello_world, None) + +window.add(button) +button.show() +window.show() + +try: + from IPython.lib.inputhook import appstart_gtk + appstart_gtk() +except ImportError: + gtk.main() + + + diff --git a/docs/examples/lib/gui-mpl.py b/docs/examples/lib/gui-mpl.py new file mode 100644 index 0000000..c77c70a --- /dev/null +++ b/docs/examples/lib/gui-mpl.py @@ -0,0 +1,54 @@ +"""Test the new %gui command. Run this in ipython as + +In [1]: %gui [backend] + +In [2]: %run switchgui [backend] + +where the optional backend can be one of: qt4, gtk, tk, wx. + +Because of subtle difference in how Matplotlib handles the different GUI +toolkits (in things like draw and show), minor modifications to this script +have to be made for Tk to get it to work with the 0.99 and below releases +of Matplotlib. However, in the future, Matplotlib should be able to have +similar logic for all the toolkits, as they are all now using PyOS_InputHook. +""" + +import sys +import time + +from IPython.lib import inputhook + +gui = inputhook.current_gui() +if gui is None: + gui = 'qt4' + inputhook.enable_qt4(app=True) + +backends = dict(wx='wxagg', qt4='qt4agg', gtk='gtkagg', tk='tkagg') + +import matplotlib +matplotlib.use(backends[gui]) +matplotlib.interactive(True) + +import matplotlib +from matplotlib import pyplot as plt, mlab, pylab +import numpy as np + +from numpy import * +from matplotlib.pyplot import * + +x = np.linspace(0,pi,500) + +print "A plot has been created" +line, = plot(x,sin(2*x)) +inputhook.spin() # This has to be removed for Tk + + +print "Now, we will update the plot..." +print +for i in range(1,51): + print i, + sys.stdout.flush() + line.set_data(x,sin(x*i)) + plt.title('i=%d' % i) + plt.draw() + inputhook.spin() # This has to be removed for Tk diff --git a/docs/examples/lib/gui-qt.py b/docs/examples/lib/gui-qt.py new file mode 100755 index 0000000..4a0b935 --- /dev/null +++ b/docs/examples/lib/gui-qt.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python +"""Simple Qt4 example to manually test event loop integration. + +This is meant to run tests manually in ipython as: + +In [5]: %gui qt + +In [6]: %run gui-qt.py + +Ref: Modified from http://zetcode.com/tutorials/pyqt4/firstprograms/ +""" + +import sys +from PyQt4 import QtGui, QtCore + +class SimpleWindow(QtGui.QWidget): + def __init__(self, parent=None): + QtGui.QWidget.__init__(self, parent) + + self.setGeometry(300, 300, 200, 80) + self.setWindowTitle('Hello World') + + quit = QtGui.QPushButton('Close', self) + quit.setGeometry(10, 10, 60, 35) + + self.connect(quit, QtCore.SIGNAL('clicked()'), + self, QtCore.SLOT('close()')) + +if __name__ == '__main__': + app = QtCore.QCoreApplication.instance() + if app is None: + app = QtGui.QApplication([]) + + sw = SimpleWindow() + sw.show() + + try: + from IPython import appstart_qt4; appstart_qt4(app) + except ImportError: + app.exec_() diff --git a/docs/examples/lib/gui-tk.py b/docs/examples/lib/gui-tk.py new file mode 100644 index 0000000..f7366aa --- /dev/null +++ b/docs/examples/lib/gui-tk.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python +"""Simple Tk example to manually test event loop integration. + +This is meant to run tests manually in ipython as: + +In [5]: %gui tk + +In [6]: %run gui-tk.py +""" + +from Tkinter import * + +class MyApp: + + def __init__(self, root): + frame = Frame(root) + frame.pack() + + self.button = Button(frame, text="Hello", command=self.hello_world) + self.button.pack(side=LEFT) + + def hello_world(self): + print "Hello World!" + +root = Tk() + +app = MyApp(root) + +try: + from IPython import appstart_tk; appstart_tk(root) +except ImportError: + root.mainloop() diff --git a/docs/examples/lib/gui-wx.py b/docs/examples/lib/gui-wx.py new file mode 100644 index 0000000..1ddaf55 --- /dev/null +++ b/docs/examples/lib/gui-wx.py @@ -0,0 +1,109 @@ +"""A Simple wx example to test IPython's event loop integration. + +To run this do: + +In [5]: %gui wx + +In [6]: %run gui-wx.py + +Ref: Modified from wxPython source code wxPython/samples/simple/simple.py + +This example can only be run once in a given IPython session because when +the frame is closed, wx goes through its shutdown sequence, killing further +attempts. I am sure someone who knows wx can fix this issue. + +Furthermore, once this example is run, the Wx event loop is mostly dead, so +even other new uses of Wx may not work correctly. If you know how to better +handle this, please contact the ipython developers and let us know. + +Note however that we will work with the Matplotlib and Enthought developers so +that the main interactive uses of Wx we are aware of, namely these tools, will +continue to work well with IPython interactively. +""" + +import wx + + +class MyFrame(wx.Frame): + """ + This is MyFrame. It just shows a few controls on a wxPanel, + and has a simple menu. + """ + def __init__(self, parent, title): + wx.Frame.__init__(self, parent, -1, title, + pos=(150, 150), size=(350, 200)) + + # Create the menubar + menuBar = wx.MenuBar() + + # and a menu + menu = wx.Menu() + + # add an item to the menu, using \tKeyName automatically + # creates an accelerator, the third param is some help text + # that will show up in the statusbar + menu.Append(wx.ID_EXIT, "E&xit\tAlt-X", "Exit this simple sample") + + # bind the menu event to an event handler + self.Bind(wx.EVT_MENU, self.OnTimeToClose, id=wx.ID_EXIT) + + # and put the menu on the menubar + menuBar.Append(menu, "&File") + self.SetMenuBar(menuBar) + + self.CreateStatusBar() + + # Now create the Panel to put the other controls on. + panel = wx.Panel(self) + + # and a few controls + text = wx.StaticText(panel, -1, "Hello World!") + text.SetFont(wx.Font(14, wx.SWISS, wx.NORMAL, wx.BOLD)) + text.SetSize(text.GetBestSize()) + btn = wx.Button(panel, -1, "Close") + funbtn = wx.Button(panel, -1, "Just for fun...") + + # bind the button events to handlers + self.Bind(wx.EVT_BUTTON, self.OnTimeToClose, btn) + self.Bind(wx.EVT_BUTTON, self.OnFunButton, funbtn) + + # Use a sizer to layout the controls, stacked vertically and with + # a 10 pixel border around each + sizer = wx.BoxSizer(wx.VERTICAL) + sizer.Add(text, 0, wx.ALL, 10) + sizer.Add(btn, 0, wx.ALL, 10) + sizer.Add(funbtn, 0, wx.ALL, 10) + panel.SetSizer(sizer) + panel.Layout() + + + def OnTimeToClose(self, evt): + """Event handler for the button click.""" + print "See ya later!" + self.Close() + + def OnFunButton(self, evt): + """Event handler for the button click.""" + print "Having fun yet?" + + +class MyApp(wx.App): + def OnInit(self): + frame = MyFrame(None, "Simple wxPython App") + self.SetTopWindow(frame) + + print "Print statements go to this stdout window by default." + + frame.Show(True) + return True + +app = wx.GetApp() +if app is None: + app = MyApp(redirect=False, clearSigInt=False) + +try: + from IPython.lib.inputhook import appstart_wx + appstart_wx(app) +except ImportError: + app.MainLoop() + diff --git a/docs/man/ipcluster.1 b/docs/man/ipcluster.1 index d04adad..418edd7 100644 --- a/docs/man/ipcluster.1 +++ b/docs/man/ipcluster.1 @@ -11,7 +11,7 @@ ipcluster is a control tool for IPython's parallel computing functions. IPython cluster startup. This starts a controller and engines using various -approaches. Use the IPYTHONDIR environment variable to change your IPython +approaches. Use the IPYTHON_DIR environment variable to change your IPython directory from the default of .ipython or _ipython. The log and security subdirectories of your IPython directory will be used by this script for log files and security files. diff --git a/docs/man/ipython.1 b/docs/man/ipython.1 index 74b9f98..091041e 100644 --- a/docs/man/ipython.1 +++ b/docs/man/ipython.1 @@ -31,62 +31,6 @@ An interactive Python shell with automatic history (input and output), dynamic object introspection, easier configuration, command completion, access to the system shell, integration with numerical and scientific computing tools, and more. -.SH SPECIAL THREADING OPTIONS -The following special options are ONLY valid at the beginning of the command -line, and not later. This is because they control the initialization of -ipython itself, before the normal option-handling mechanism is active. -.TP -.B \-gthread, \-qthread, \-q4thread, \-wthread, \-pylab -Only ONE of these can be given, and it can only be given as the first option -passed to IPython (it will have no effect in any other position). They provide -threading support for the GTK, QT3, QT4 and WXWidgets toolkits, for the -matplotlib library and Twisted reactor. -.br -.sp 1 -With any of the first four options, IPython starts running a separate thread -for the graphical toolkit's operation, so that you can open and control -graphical elements from within an IPython command line, without blocking. All -four provide essentially the same functionality, respectively for GTK, QT3, QT4 -and WXWidgets (via their Python interfaces). -.br -.sp 1 -Note that with \-wthread, you can additionally use the \-wxversion option to -request a specific version of wx to be used. This requires that you have the -wxversion Python module installed, which is part of recent wxPython -distributions. -.br -.sp 1 -If \-pylab is given, IPython loads special support for the matplotlib library -(http://matplotlib.sourceforge.net), allowing interactive usage of any of its -backends as defined in the user's .matplotlibrc file. It automatically -activates GTK, QT or WX threading for IPyhton if the choice of matplotlib -backend requires it. It also modifies the %run command to correctly execute -(without blocking) any matplotlib-based script which calls show() at the end. -.TP -.B \-tk -The \-g/q/q4/wthread options, and \-pylab (if matplotlib is configured to use -GTK, QT or WX), will normally block Tk graphical interfaces. This means that -when GTK, QT or WX threading is active, any attempt to open a Tk GUI will -result in a dead window, and possibly cause the Python interpreter to crash. -An extra option, \-tk, is available to address this issue. It can ONLY be -given as a SECOND option after any of the above (\-gthread, \-qthread, -\-wthread or \-pylab). -.br -.sp 1 -If \-tk is given, IPython will try to coordinate Tk threading with GTK, QT or -WX. This is however potentially unreliable, and you will have to test on your -platform and Python configuration to determine whether it works for you. -Debian users have reported success, apparently due to the fact that Debian -builds all of Tcl, Tk, Tkinter and Python with pthreads support. Under other -Linux environments (such as Fedora Core 2), this option has caused random -crashes and lockups of the Python interpreter. Under other operating systems -(Mac OSX and Windows), you'll need to try it to find out, since currently no -user reports are available. -.br -.sp 1 -There is unfortunately no way for IPython to determine at runtime whether \-tk -will work reliably or not, so you will need to do some experiments before -relying on it for regular work. . .SH REGULAR OPTIONS After the above threading options have been given, regular options can follow @@ -197,8 +141,8 @@ may want to use a small, lightweight editor here (in case your default EDITOR is something like Emacs). .TP .B \-ipythondir -The name of your IPython configuration directory IPYTHONDIR. This can -also be specified through the environment variable IPYTHONDIR. +The name of your IPython configuration directory IPYTHON_DIR. This can +also be specified through the environment variable IPYTHON_DIR. .TP .B \-log|l Generate a log file of all input. The file is named ipython_log.py in your @@ -253,10 +197,10 @@ your config file (default off). .TP .B \-profile|p Assume that your config file is ipythonrc- (looks in current dir -first, then in IPYTHONDIR). This is a quick way to keep and load +first, then in IPYTHON_DIR). This is a quick way to keep and load multiple config files for different tasks, especially if you use the include option of config files. You can keep a basic -IPYTHONDIR/ipythonrc file and then have other 'profiles' which include +IPYTHON_DIR/ipythonrc file and then have other 'profiles' which include this one and load extra things for particular tasks. For example: .br .sp 1 @@ -300,7 +244,7 @@ Start in bare bones mode (no config file loaded). .TP .B \-rcfile Name of your IPython resource configuration file. normally IPython -loads ipythonrc (from current directory) or IPYTHONDIR/ipythonrc. If +loads ipythonrc (from current directory) or IPYTHON_DIR/ipythonrc. If the loading of your config file fails, IPython starts with a bare bones configuration (no modules loaded at all). .TP @@ -342,7 +286,7 @@ Shorthand for '\-separate_in 0 \-separate_out 0 \-separate_out2 0'. Simply removes all input/output separators. .TP .B \-upgrade -Allows you to upgrade your IPYTHONDIR configuration when you install a +Allows you to upgrade your IPYTHON_DIR configuration when you install a new version of IPython. Since new versions may include new command lines options or example files, this copies updated ipythonrc-type files. However, it backs up (with a .old extension) all files which diff --git a/docs/source/_static/default.css b/docs/source/_static/default.css new file mode 100644 index 0000000..7aa01b7 --- /dev/null +++ b/docs/source/_static/default.css @@ -0,0 +1,507 @@ +/** + * Alternate Sphinx design + * Originally created by Armin Ronacher for Werkzeug, adapted by Georg Brandl. + */ + +body { + font-family: 'Lucida Grande', 'Lucida Sans Unicode', 'Geneva', 'Verdana', sans-serif; + font-size: 14px; + letter-spacing: -0.01em; + line-height: 150%; + text-align: center; + /*background-color: #AFC1C4; */ + background-color: #BFD1D4; + color: black; + padding: 0; + border: 1px solid #aaa; + + margin: 0px 80px 0px 80px; + min-width: 740px; +} + +a { + color: #CA7900; + text-decoration: none; +} + +a:hover { + color: #2491CF; +} + +pre { + font-family: 'Consolas', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace; + font-size: 0.95em; + letter-spacing: 0.015em; + padding: 0.5em; + border: 1px solid #ccc; + background-color: #f8f8f8; +} + +td.linenos pre { + padding: 0.5em 0; + border: 0; + background-color: transparent; + color: #aaa; +} + +table.highlighttable { + margin-left: 0.5em; +} + +table.highlighttable td { + padding: 0 0.5em 0 0.5em; +} + +cite, code, tt { + font-family: 'Consolas', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace; + font-size: 0.95em; + letter-spacing: 0.01em; +} + +hr { + border: 1px solid #abc; + margin: 2em; +} + +tt { + background-color: #f2f2f2; + border-bottom: 1px solid #ddd; + color: #333; +} + +tt.descname { + background-color: transparent; + font-weight: bold; + font-size: 1.2em; + border: 0; +} + +tt.descclassname { + background-color: transparent; + border: 0; +} + +tt.xref { + background-color: transparent; + font-weight: bold; + border: 0; +} + +a tt { + background-color: transparent; + font-weight: bold; + border: 0; + color: #CA7900; +} + +a tt:hover { + color: #2491CF; +} + +dl { + margin-bottom: 15px; +} + +dd p { + margin-top: 0px; +} + +dd ul, dd table { + margin-bottom: 10px; +} + +dd { + margin-top: 3px; + margin-bottom: 10px; + margin-left: 30px; +} + +.refcount { + color: #060; +} + +dt:target, +.highlight { + background-color: #fbe54e; +} + +dl.class, dl.function { + border-top: 2px solid #888; +} + +dl.method, dl.attribute { + border-top: 1px solid #aaa; +} + +dl.glossary dt { + font-weight: bold; + font-size: 1.1em; +} + +pre { + line-height: 120%; +} + +pre a { + color: inherit; + text-decoration: underline; +} + +.first { + margin-top: 0 !important; +} + +div.document { + background-color: white; + text-align: left; + background-image: url(contents.png); + background-repeat: repeat-x; +} + +/* +div.documentwrapper { + width: 100%; +} +*/ + +div.clearer { + clear: both; +} + +div.related h3 { + display: none; +} + +div.related ul { + background-image: url(navigation.png); + height: 2em; + list-style: none; + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 0; + padding-left: 10px; +} + +div.related ul li { + margin: 0; + padding: 0; + height: 2em; + float: left; +} + +div.related ul li.right { + float: right; + margin-right: 5px; +} + +div.related ul li a { + margin: 0; + padding: 0 5px 0 5px; + line-height: 1.75em; + color: #EE9816; +} + +div.related ul li a:hover { + color: #3CA8E7; +} + +div.body { + margin: 0; + padding: 0.5em 20px 20px 20px; +} + +div.bodywrapper { + margin: 0 240px 0 0; + border-right: 1px solid #ccc; +} + +div.body a { + text-decoration: underline; +} + +div.sphinxsidebar { + margin: 0; + padding: 0.5em 15px 15px 0; + width: 210px; + float: right; + text-align: left; +/* margin-left: -100%; */ +} + +div.sphinxsidebar h4, div.sphinxsidebar h3 { + margin: 1em 0 0.5em 0; + font-size: 0.9em; + padding: 0.1em 0 0.1em 0.5em; + color: white; + border: 1px solid #86989B; + background-color: #AFC1C4; +} + +div.sphinxsidebar ul { + padding-left: 1.5em; + margin-top: 7px; + list-style: none; + padding: 0; + line-height: 130%; +} + +div.sphinxsidebar ul ul { + list-style: square; + margin-left: 20px; +} + +p { + margin: 0.8em 0 0.5em 0; +} + +p.rubric { + font-weight: bold; +} + +h1 { + margin: 0; + padding: 0.7em 0 0.3em 0; + font-size: 1.5em; + color: #11557C; +} + +h2 { + margin: 1.3em 0 0.2em 0; + font-size: 1.35em; + padding: 0; +} + +h3 { + margin: 1em 0 -0.3em 0; + font-size: 1.2em; +} + +h1 a, h2 a, h3 a, h4 a, h5 a, h6 a { + color: black!important; +} + +h1 a.anchor, h2 a.anchor, h3 a.anchor, h4 a.anchor, h5 a.anchor, h6 a.anchor { + display: none; + margin: 0 0 0 0.3em; + padding: 0 0.2em 0 0.2em; + color: #aaa!important; +} + +h1:hover a.anchor, h2:hover a.anchor, h3:hover a.anchor, h4:hover a.anchor, +h5:hover a.anchor, h6:hover a.anchor { + display: inline; +} + +h1 a.anchor:hover, h2 a.anchor:hover, h3 a.anchor:hover, h4 a.anchor:hover, +h5 a.anchor:hover, h6 a.anchor:hover { + color: #777; + background-color: #eee; +} + +table { + border-collapse: collapse; + margin: 0 -0.5em 0 -0.5em; +} + +table td, table th { + padding: 0.2em 0.5em 0.2em 0.5em; +} + +div.footer { + background-color: #E3EFF1; + color: #86989B; + padding: 3px 8px 3px 0; + clear: both; + font-size: 0.8em; + text-align: right; +} + +div.footer a { + color: #86989B; + text-decoration: underline; +} + +div.pagination { + margin-top: 2em; + padding-top: 0.5em; + border-top: 1px solid black; + text-align: center; +} + +div.sphinxsidebar ul.toc { + margin: 1em 0 1em 0; + padding: 0 0 0 0.5em; + list-style: none; +} + +div.sphinxsidebar ul.toc li { + margin: 0.5em 0 0.5em 0; + font-size: 0.9em; + line-height: 130%; +} + +div.sphinxsidebar ul.toc li p { + margin: 0; + padding: 0; +} + +div.sphinxsidebar ul.toc ul { + margin: 0.2em 0 0.2em 0; + padding: 0 0 0 1.8em; +} + +div.sphinxsidebar ul.toc ul li { + padding: 0; +} + +div.admonition, div.warning { + font-size: 0.9em; + margin: 1em 0 0 0; + border: 1px solid #86989B; + background-color: #f7f7f7; +} + +div.admonition p, div.warning p { + margin: 0.5em 1em 0.5em 1em; + padding: 0; +} + +div.admonition pre, div.warning pre { + margin: 0.4em 1em 0.4em 1em; +} + +div.admonition p.admonition-title, +div.warning p.admonition-title { + margin: 0; + padding: 0.1em 0 0.1em 0.5em; + color: white; + border-bottom: 1px solid #86989B; + font-weight: bold; + background-color: #AFC1C4; +} + +div.warning { + border: 1px solid #940000; +} + +div.warning p.admonition-title { + background-color: #CF0000; + border-bottom-color: #940000; +} + +div.admonition ul, div.admonition ol, +div.warning ul, div.warning ol { + margin: 0.1em 0.5em 0.5em 3em; + padding: 0; +} + +div.versioninfo { + margin: 1em 0 0 0; + border: 1px solid #ccc; + background-color: #DDEAF0; + padding: 8px; + line-height: 1.3em; + font-size: 0.9em; +} + + +a.headerlink { + color: #c60f0f!important; + font-size: 1em; + margin-left: 6px; + padding: 0 4px 0 4px; + text-decoration: none!important; + visibility: hidden; +} + +h1:hover > a.headerlink, +h2:hover > a.headerlink, +h3:hover > a.headerlink, +h4:hover > a.headerlink, +h5:hover > a.headerlink, +h6:hover > a.headerlink, +dt:hover > a.headerlink { + visibility: visible; +} + +a.headerlink:hover { + background-color: #ccc; + color: white!important; +} + +table.indextable td { + text-align: left; + vertical-align: top; +} + +table.indextable dl, table.indextable dd { + margin-top: 0; + margin-bottom: 0; +} + +table.indextable tr.pcap { + height: 10px; +} + +table.indextable tr.cap { + margin-top: 10px; + background-color: #f2f2f2; +} + +img.toggler { + margin-right: 3px; + margin-top: 3px; + cursor: pointer; +} + +img.inheritance { + border: 0px +} + +form.pfform { + margin: 10px 0 20px 0; +} + +table.contentstable { + width: 90%; +} + +table.contentstable p.biglink { + line-height: 150%; +} + +a.biglink { + font-size: 1.3em; +} + +span.linkdescr { + font-style: italic; + padding-top: 5px; + font-size: 90%; +} + +ul.search { + margin: 10px 0 0 20px; + padding: 0; +} + +ul.search li { + padding: 5px 0 5px 20px; + background-image: url(file.png); + background-repeat: no-repeat; + background-position: 0 7px; +} + +ul.search li a { + font-weight: bold; +} + +ul.search li div.context { + color: #888; + margin: 2px 0 0 30px; + text-align: left; +} + +ul.keywordmatches li.goodmatch a { + font-weight: bold; +} diff --git a/docs/source/_templates/layout.html b/docs/source/_templates/layout.html new file mode 100644 index 0000000..0667e7e --- /dev/null +++ b/docs/source/_templates/layout.html @@ -0,0 +1,23 @@ +{% extends "!layout.html" %} + + +{% block rootrellink %} +
  • home
  • +
  • search
  • +
  • documentation »
  • +{% endblock %} + + +{% block relbar1 %} + +
    +IPython Documentation +
    +{{ super() }} +{% endblock %} + +{# put the sidebar before the body #} +{% block sidebar1 %}{{ sidebar() }}{% endblock %} +{% block sidebar2 %}{% endblock %} + diff --git a/docs/source/about/credits.txt b/docs/source/about/credits.txt new file mode 100644 index 0000000..1f4c945 --- /dev/null +++ b/docs/source/about/credits.txt @@ -0,0 +1,209 @@ +.. _credits: + +======= +Credits +======= + +IPython was started and continues to be led by Fernando Pérez. + +Core developers +=============== + +As of this writing, core development team consists of the following +developers: + +* **Fernando Pérez** Project creator and leader, + IPython core, parallel computing infrastructure, testing, release manager. + +* **Robert Kern** Co-mentored the 2005 Google Summer of + Code project, work on IPython's core. + +* **Brian Granger** Parallel computing + infrastructure, IPython core. + +* **Benjamin (Min) Ragan-Kelley** Parallel computing + infrastructure. + +* **Ville Vainio** IPython core, maintainer of IPython + trunk from version 0.7.2 to 0.8.4. + +* **Gael Varoquaux** wxPython IPython GUI, + frontend architecture. + +* **Barry Wark** Cocoa GUI, frontend architecture. + +* **Laurent Dufrechou** wxPython IPython GUI. + +* **Jörgen Stenarson** Maintainer of the + PyReadline project, which is needed for IPython under windows. + +Special thanks +============== + +The IPython project is also very grateful to: + +Bill Bumgarner , for providing the DPyGetOpt module that +IPython used for parsing command line options through version 0.10. + +Ka-Ping Yee , for providing the Itpl module for convenient +and powerful string interpolation with a much nicer syntax than formatting +through the '%' operator. + +Arnd Baecker , for his many very useful +suggestions and comments, and lots of help with testing and documentation +checking. Many of IPython's newer features are a result of discussions with +him. + +Obviously Guido van Rossum and the whole Python development team, for creating +a great language for interactive computing. + +Enthought (http://www.enthought.com), for hosting IPython's website and +supporting the project in various ways over the years. + +Fernando would also like to thank Stephen Figgins , +an O'Reilly Python editor. His October 11, 2001 article about IPP and +LazyPython, was what got this project started. You can read it at +http://www.onlamp.com/pub/a/python/2001/10/11/pythonnews.html. + +Contributors +============ + +And last but not least, all the kind IPython contributors who have contributed +new code, bug reports, fixes, comments and ideas. A brief list follows, please +let us know if we have omitted your name by accident: + +* Dan Milstein A bold refactor of the core prefilter + machinery in the IPython interpreter. + +* Jack Moffit Bug fixes, including the infamous color + problem. This bug alone caused many lost hours and frustration, many thanks + to him for the fix. I've always been a fan of Ogg & friends, now I have one + more reason to like these folks. Jack is also contributing with Debian + packaging and many other things. + +* Alexander Schmolck Emacs work, bug reports, bug + fixes, ideas, lots more. The ipython.el mode for (X)Emacs is Alex's code, + providing full support for IPython under (X)Emacs. + +* Andrea Riciputi Mac OSX information, Fink + package management. + +* Gary Bishop Bug reports, and patches to work around the + exception handling idiosyncracies of WxPython. Readline and color support + for Windows. + +* Jeffrey Collins . Bug reports. Much improved + readline support, including fixes for Python 2.3. + +* Dryice Liu FreeBSD port. + +* Mike Heeter + +* Christopher Hart PDB integration. + +* Milan Zamazal Emacs info. + +* Philip Hisley + +* Holger Krekel Tab completion, lots more. + +* Robin Siebler + +* Ralf Ahlbrink + +* Thorsten Kampe + +* Fredrik Kant Windows setup. + +* Syver Enstad Windows setup. + +* Richard Global embedding. + +* Hayden Callow Gnuplot.py 1.6 + compatibility. + +* Leonardo Santagada Fixes for Windows + installation. + +* Christopher Armstrong Bugfixes. + +* Francois Pinard Code and + documentation fixes. + +* Cory Dodt Bug reports and Windows + ideas. Patches for Windows installer. + +* Olivier Aubert New magics. + +* King C. Shu Autoindent patch. + +* Chris Drexler Readline packages for + Win32/CygWin. + +* Gustavo Cordova Avila EvalDict code for + nice, lightweight string interpolation. + +* Kasper Souren Bug reports, ideas. + +* Gever Tulley Code contributions. + +* Ralf Schmitt Bug reports & fixes. + +* Oliver Sander Bug reports. + +* Rod Holland Bug reports and fixes to + logging module. + +* Daniel 'Dang' Griffith + Fixes, enhancement suggestions for system shell use. + +* Viktor Ransmayr Tests and + reports on Windows installation issues. Contributed a true Windows + binary installer. + +* Mike Salib Help fixing a subtle bug related + to traceback printing. + +* W.J. van der Laan Bash-like + prompt specials. + +* Antoon Pardon Critical fix for + the multithreaded IPython. + +* John Hunter Matplotlib + author, helped with all the development of support for matplotlib + in IPyhton, including making necessary changes to matplotlib itself. + +* Matthew Arnison Bug reports, '%run -d' idea. + +* Prabhu Ramachandran Help + with (X)Emacs support, threading patches, ideas... + +* Norbert Tretkowski help with Debian + packaging and distribution. + +* George Sakkis New matcher for + tab-completing named arguments of user-defined functions. + +* Jörgen Stenarson Wildcard + support implementation for searching namespaces. + +* Vivian De Smedt Debugger enhancements, + so that when pdb is activated from within IPython, coloring, tab + completion and other features continue to work seamlessly. + +* Scott Tsai Support for automatic + editor invocation on syntax errors (see + http://www.scipy.net/roundup/ipython/issue36). + +* Alexander Belchenko Improvements for win32 + paging system. + +* Will Maier Official OpenBSD port. + +* Ondrej Certik Set up the IPython docs to use the new + Sphinx system used by Python, Matplotlib and many more projects. + +* Stefan van der Walt Design and prototype of the + Traits based config system. + diff --git a/docs/source/history.txt b/docs/source/about/history.txt similarity index 60% rename from docs/source/history.txt rename to docs/source/about/history.txt index 439f8e4..d0a34cd 100644 --- a/docs/source/history.txt +++ b/docs/source/about/history.txt @@ -7,21 +7,22 @@ History Origins ======= -IPython was starting in 2001 by Fernando Perez. IPython as we know it -today grew out of the following three projects: - -* ipython by Fernando Pérez. I was working on adding - Mathematica-type prompts and a flexible configuration system - (something better than $PYTHONSTARTUP) to the standard Python - interactive interpreter. +IPython was starting in 2001 by Fernando Perez while he was a graduate student +at the University of Colorado, Boulder. IPython as we know it today grew out +of the following three projects: + +* ipython by Fernando Pérez. Fernando began using Python and ipython began as + an outgrowth of his desire for things like Mathematica-style prompts, access + to previous output (again like Mathematica's % syntax) and a flexible + configuration system (something better than :envvar:`PYTHONSTARTUP`). * IPP by Janko Hauser. Very well organized, great usability. Had - an old help system. IPP was used as the 'container' code into - which I added the functionality from ipython and LazyPython. + an old help system. IPP was used as the "container" code into + which Fernando added the functionality from ipython and LazyPython. * LazyPython by Nathan Gray. Simple but very powerful. The quick syntax (auto parens, auto quotes) and verbose/colored tracebacks were all taken from here. -Here is how Fernando describes it: +Here is how Fernando describes the early history of IPython: When I found out about IPP and LazyPython I tried to join all three into a unified system. I thought this could provide a very nice @@ -31,8 +32,3 @@ Here is how Fernando describes it: think it worked reasonably well, though it was a lot more work than I had initially planned. -Today and how we got here -========================= - -This needs to be filled in. - diff --git a/docs/source/about/index.txt b/docs/source/about/index.txt new file mode 100644 index 0000000..3abd31b --- /dev/null +++ b/docs/source/about/index.txt @@ -0,0 +1,13 @@ +.. _about_index: + +============= +About IPython +============= + +.. toctree:: + :maxdepth: 1 + + credits + history + license_and_copyright + diff --git a/docs/source/license_and_copyright.txt b/docs/source/about/license_and_copyright.txt similarity index 83% rename from docs/source/license_and_copyright.txt rename to docs/source/about/license_and_copyright.txt index 9c61f1e..e88da84 100644 --- a/docs/source/license_and_copyright.txt +++ b/docs/source/about/license_and_copyright.txt @@ -7,7 +7,8 @@ License and Copyright License ======= -IPython is licensed under the terms of the new or revised BSD license, as follows:: +IPython is licensed under the terms of the new or revised BSD license, as +follows:: Copyright (c) 2008, IPython Development Team @@ -44,25 +45,24 @@ About the IPython Development Team ================================== Fernando Perez began IPython in 2001 based on code from Janko Hauser - and Nathaniel Gray . Fernando is still + and Nathaniel Gray . Fernando is still the project lead. The IPython Development Team is the set of all contributors to the IPython project. This includes all of the IPython subprojects. Here is a list of the currently active contributors: - * Matthieu Brucher - * Ondrej Certik - * Laurent Dufrechou - * Robert Kern - * Brian E. Granger - * Fernando Perez (project leader) - * Benjamin Ragan-Kelley - * Ville M. Vainio - * Gael Varoququx - * Stefan van der Walt - * Tech-X Corporation - * Barry Wark +* Matthieu Brucher +* Ondrej Certik +* Laurent Dufrechou +* Robert Kern +* Brian E. Granger +* Fernando Perez (project leader) +* Benjamin Ragan-Kelley +* Ville M. Vainio +* Gael Varoququx +* Stefan van der Walt +* Barry Wark If your name is missing, please add it. @@ -71,13 +71,16 @@ Our Copyright Policy IPython uses a shared copyright model. Each contributor maintains copyright over their contributions to IPython. But, it is important to note that these -contributions are typically only changes to the repositories. Thus, the -IPython source code, in its entirety is not the copyright of any single person -or institution. Instead, it is the collective copyright of the entire IPython -Development Team. If individual contributors want to maintain a record of what -changes/contributions they have specific copyright on, they should indicate -their copyright in the commit message of the change, when they commit the -change to one of the IPython repositories. +contributions are typically only changes (diffs/commits) to the repositories. +Thus, the IPython source code, in its entirety is not the copyright of any +single person or institution. Instead, it is the collective copyright of the +entire IPython Development Team. If individual contributors want to maintain a +record of what changes/contributions they have specific copyright on, they +should indicate their copyright in the commit message of the change, when they +commit the change to one of the IPython repositories. + +Any new code contributed to IPython must be licensed under the BSD license or +a similar (MIT) open source license. Miscellaneous ============= @@ -88,4 +91,5 @@ its author/authors have decided to publish the code. Versions of IPython up to and including 0.6.3 were released under the GNU Lesser General Public License (LGPL), available at -http://www.gnu.org/copyleft/lesser.html. \ No newline at end of file +http://www.gnu.org/copyleft/lesser.html. + diff --git a/docs/source/conf.py b/docs/source/conf.py index 198b195..b955056 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -28,22 +28,23 @@ import ipython_console_highlighting # We load the ipython release info into a dict by explicit execution iprelease = {} -execfile('../../IPython/Release.py',iprelease) +execfile('../../IPython/core/release.py',iprelease) # General configuration # --------------------- # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = ['sphinx.ext.autodoc', - 'sphinx.ext.doctest', - - 'only_directives', - 'inheritance_diagram', - 'ipython_console_highlighting', - # 'plot_directive', # disabled for now, needs matplotlib - 'numpydoc', # to preprocess docstrings - ] +extensions = [ + # 'matplotlib.sphinxext.mathmpl', + 'matplotlib.sphinxext.only_directives', + # 'matplotlib.sphinxext.plot_directive', + 'sphinx.ext.autodoc', + 'sphinx.ext.doctest', + 'inheritance_diagram', + 'ipython_console_highlighting', + 'numpydoc', # to preprocess docstrings +] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] @@ -161,10 +162,13 @@ latex_font_size = '11pt' # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, document class [howto/manual]). -latex_documents = [ ('index', 'ipython.tex', 'IPython Documentation', - ur"""The IPython Development Team""", - 'manual'), - ] +latex_documents = [ + ('index', 'ipython.tex', 'IPython Documentation', + ur"""The IPython Development Team""", 'manual', True), + ('parallel/winhpc_index', 'winhpc_whitepaper.tex', + 'Using IPython on Windows HPC Server 2008', + ur"Brian E. Granger", 'manual', True) +] # The name of an image file (relative to this directory) to place at the top of # the title page. @@ -181,7 +185,7 @@ latex_documents = [ ('index', 'ipython.tex', 'IPython Documentation', #latex_appendices = [] # If false, no module index is generated. -#latex_use_modindex = True +latex_use_modindex = True # Cleanup diff --git a/docs/source/config/customization.txt b/docs/source/config/customization.txt deleted file mode 100644 index 8211b4c..0000000 --- a/docs/source/config/customization.txt +++ /dev/null @@ -1,286 +0,0 @@ -.. _customization: - -======================== -Customization of IPython -======================== - -There are 2 ways to configure IPython - the old way of using ipythonrc -files (an INI-file like format), and the new way that involves editing -your ipy_user_conf.py. Both configuration systems work at the same -time, so you can set your options in both, but if you are hesitating -about which alternative to choose, we recommend the ipy_user_conf.py -approach, as it will give you more power and control in the long -run. However, there are few options such as pylab_import_all that can -only be specified in ipythonrc file or command line - the reason for -this is that they are needed before IPython has been started up, and -the IPApi object used in ipy_user_conf.py is not yet available at that -time. A hybrid approach of specifying a few options in ipythonrc and -doing the more advanced configuration in ipy_user_conf.py is also -possible. - -.. _ipythonrc: - -The ipythonrc approach -====================== - -As we've already mentioned, IPython reads a configuration file which can -be specified at the command line (-rcfile) or which by default is -assumed to be called ipythonrc. Such a file is looked for in the current -directory where IPython is started and then in your IPYTHONDIR, which -allows you to have local configuration files for specific projects. In -this section we will call these types of configuration files simply -rcfiles (short for resource configuration file). - -The syntax of an rcfile is one of key-value pairs separated by -whitespace, one per line. Lines beginning with a # are ignored as -comments, but comments can not be put on lines with data (the parser is -fairly primitive). Note that these are not python files, and this is -deliberate, because it allows us to do some things which would be quite -tricky to implement if they were normal python files. - -First, an rcfile can contain permanent default values for almost all command -line options (except things like -help or -Version). :ref:`This section -` contains a description of all command-line -options. However, values you explicitly specify at the command line override -the values defined in the rcfile. - -Besides command line option values, the rcfile can specify values for -certain extra special options which are not available at the command -line. These options are briefly described below. - -Each of these options may appear as many times as you need it in the file. - - * include ...: you can name other rcfiles you want - to recursively load up to 15 levels (don't use the <> brackets in - your names!). This feature allows you to define a 'base' rcfile - with general options and special-purpose files which can be loaded - only when needed with particular configuration options. To make - this more convenient, IPython accepts the -profile option - (abbreviates to -p ) which tells it to look for an rcfile - named ipythonrc-. - * import_mod ...: import modules with 'import - ,,...' - * import_some ...: import functions with 'from - import ,,...' - * import_all ...: for each module listed import - functions with ``from import *``. - * execute : give any single-line python code to be - executed. - * execfile : execute the python file given with an - 'execfile(filename)' command. Username expansion is performed on - the given names. So if you need any amount of extra fancy - customization that won't fit in any of the above 'canned' options, - you can just put it in a separate python file and execute it. - * alias : this is equivalent to calling - '%alias ' at the IPython command line. This way, from - within IPython you can do common system tasks without having to - exit it or use the ! escape. IPython isn't meant to be a shell - replacement, but it is often very useful to be able to do things - with files while testing code. This gives you the flexibility to - have within IPython any aliases you may be used to under your - normal system shell. - -ipy_user_conf.py -================ - -There should be a simple template ipy_user_conf.py file in your -~/.ipython directory. It is a plain python module that is imported -during IPython startup, so you can do pretty much what you want there -- import modules, configure extensions, change options, define magic -commands, put variables and functions in the IPython namespace, -etc. You use the IPython extension api object, acquired by -IPython.ipapi.get() and documented in the "IPython extension API" -chapter, to interact with IPython. A sample ipy_user_conf.py is listed -below for reference:: - - # Most of your config files and extensions will probably start - # with this import - - import IPython.ipapi - ip = IPython.ipapi.get() - - # You probably want to uncomment this if you did %upgrade -nolegacy - # import ipy_defaults - - import os - - def main(): - - #ip.dbg.debugmode = True - ip.dbg.debug_stack() - - # uncomment if you want to get ipython -p sh behaviour - # without having to use command line switches - import ipy_profile_sh - import jobctrl - - # Configure your favourite editor? - # Good idea e.g. for %edit os.path.isfile - - #import ipy_editors - - # Choose one of these: - - #ipy_editors.scite() - #ipy_editors.scite('c:/opt/scite/scite.exe') - #ipy_editors.komodo() - #ipy_editors.idle() - # ... or many others, try 'ipy_editors??' after import to see them - - # Or roll your own: - #ipy_editors.install_editor("c:/opt/jed +$line $file") - - - o = ip.options - # An example on how to set options - #o.autocall = 1 - o.system_verbose = 0 - - #import_all("os sys") - #execf('~/_ipython/ns.py') - - - # -- prompt - # A different, more compact set of prompts from the default ones, that - # always show your current location in the filesystem: - - #o.prompt_in1 = r'\C_LightBlue[\C_LightCyan\Y2\C_LightBlue]\C_Normal\n\C_Green|\#>' - #o.prompt_in2 = r'.\D: ' - #o.prompt_out = r'[\#] ' - - # Try one of these color settings if you can't read the text easily - # autoexec is a list of IPython commands to execute on startup - #o.autoexec.append('%colors LightBG') - #o.autoexec.append('%colors NoColor') - o.autoexec.append('%colors Linux') - - - # some config helper functions you can use - def import_all(modules): - """ Usage: import_all("os sys") """ - for m in modules.split(): - ip.ex("from %s import *" % m) - - def execf(fname): - """ Execute a file in user namespace """ - ip.ex('execfile("%s")' % os.path.expanduser(fname)) - - main() - -.. _Prompts: - -Fine-tuning your prompt -======================= - -IPython's prompts can be customized using a syntax similar to that of -the bash shell. Many of bash's escapes are supported, as well as a few -additional ones. We list them below:: - - \# - the prompt/history count number. This escape is automatically - wrapped in the coloring codes for the currently active color scheme. - \N - the 'naked' prompt/history count number: this is just the number - itself, without any coloring applied to it. This lets you produce - numbered prompts with your own colors. - \D - the prompt/history count, with the actual digits replaced by dots. - Used mainly in continuation prompts (prompt_in2) - \w - the current working directory - \W - the basename of current working directory - \Xn - where $n=0\ldots5.$ The current working directory, with $HOME - replaced by ~, and filtered out to contain only $n$ path elements - \Yn - Similar to \Xn, but with the $n+1$ element included if it is ~ (this - is similar to the behavior of the %cn escapes in tcsh) - \u - the username of the current user - \$ - if the effective UID is 0, a #, otherwise a $ - \h - the hostname up to the first '.' - \H - the hostname - \n - a newline - \r - a carriage return - \v - IPython version string - -In addition to these, ANSI color escapes can be insterted into the -prompts, as \C_ColorName. The list of valid color names is: Black, Blue, -Brown, Cyan, DarkGray, Green, LightBlue, LightCyan, LightGray, -LightGreen, LightPurple, LightRed, NoColor, Normal, Purple, Red, White, -Yellow. - -Finally, IPython supports the evaluation of arbitrary expressions in -your prompt string. The prompt strings are evaluated through the syntax -of PEP 215, but basically you can use $x.y to expand the value of x.y, -and for more complicated expressions you can use braces: ${foo()+x} will -call function foo and add to it the value of x, before putting the -result into your prompt. For example, using -prompt_in1 '${commands.getoutput("uptime")}\nIn [\#]: ' -will print the result of the uptime command on each prompt (assuming the -commands module has been imported in your ipythonrc file). - - - Prompt examples - -The following options in an ipythonrc file will give you IPython's -default prompts:: - - prompt_in1 'In [\#]:' - prompt_in2 ' .\D.:' - prompt_out 'Out[\#]:' - -which look like this:: - - In [1]: 1+2 - Out[1]: 3 - - In [2]: for i in (1,2,3): - ...: print i, - ...: - 1 2 3 - -These will give you a very colorful prompt with path information:: - - #prompt_in1 '\C_Red\u\C_Blue[\C_Cyan\Y1\C_Blue]\C_LightGreen\#>' - prompt_in2 ' ..\D>' - prompt_out '<\#>' - -which look like this:: - - fperez[~/ipython]1> 1+2 - <1> 3 - fperez[~/ipython]2> for i in (1,2,3): - ...> print i, - ...> - 1 2 3 - - -.. _Profiles: - -IPython profiles -================ - -As we already mentioned, IPython supports the -profile command-line option (see -:ref:`here `). A profile is nothing more than a -particular configuration file like your basic ipythonrc one, but with -particular customizations for a specific purpose. When you start IPython with -'ipython -profile ', it assumes that in your IPYTHONDIR there is a file -called ipythonrc- or ipy_profile_.py, and loads it instead of the -normal ipythonrc. - -This system allows you to maintain multiple configurations which load -modules, set options, define functions, etc. suitable for different -tasks and activate them in a very simple manner. In order to avoid -having to repeat all of your basic options (common things that don't -change such as your color preferences, for example), any profile can -include another configuration file. The most common way to use profiles -is then to have each one include your basic ipythonrc file as a starting -point, and then add further customizations. \ No newline at end of file diff --git a/docs/source/config/editors.txt b/docs/source/config/editors.txt new file mode 100644 index 0000000..bda308b --- /dev/null +++ b/docs/source/config/editors.txt @@ -0,0 +1,117 @@ +.. _editors: + +==================== +Editor configuration +==================== + +IPython can integrate with text editors in a number of different ways: + +* Editors (such as (X)Emacs [Emacs]_, vim [vim]_ and TextMate [TextMate]_) can + send code to IPython for execution. + +* IPython's ``%edit`` magic command can open an editor of choice to edit + a code block. + +The %edit command (and its alias %ed) will invoke the editor set in your +environment as :envvar:`EDITOR`. If this variable is not set, it will default +to vi under Linux/Unix and to notepad under Windows. You may want to set this +variable properly and to a lightweight editor which doesn't take too long to +start (that is, something other than a new instance of Emacs). This way you +can edit multi-line code quickly and with the power of a real editor right +inside IPython. + +You can also control the editor via the commmand-line option '-editor' or in +your configuration file, by setting the :attr:`InteractiveShell.editor` +configuration attribute. + +TextMate +======== + +Currently, TextMate support in IPython is broken. It used to work well, +but the code has been moved to :mod:`IPython.quarantine` until it is updated. + +vim configuration +================= + +Currently, vim support in IPython is broken. Like the TextMate code, +the vim support code has been moved to :mod:`IPython.quarantine` until it +is updated. + +.. _emacs: + +(X)Emacs +======== + +Editor +====== + +If you are a dedicated Emacs user, and want to use Emacs when IPython's +``%edit`` magic command is called you should set up the Emacs server so that +new requests are handled by the original process. This means that almost no +time is spent in handling the request (assuming an Emacs process is already +running). For this to work, you need to set your EDITOR environment variable +to 'emacsclient'. The code below, supplied by Francois Pinard, can then be +used in your :file:`.emacs` file to enable the server:: + + (defvar server-buffer-clients) + (when (and (fboundp 'server-start) (string-equal (getenv "TERM") 'xterm)) + (server-start) + (defun fp-kill-server-with-buffer-routine () + (and server-buffer-clients (server-done))) + (add-hook 'kill-buffer-hook 'fp-kill-server-with-buffer-routine)) + +Thanks to the work of Alexander Schmolck and Prabhu Ramachandran, +currently (X)Emacs and IPython get along very well in other ways. + +.. note:: + + You will need to use a recent enough version of :file:`python-mode.el`, + along with the file :file:`ipython.el`. You can check that the version you + have of :file:`python-mode.el` is new enough by either looking at the + revision number in the file itself, or asking for it in (X)Emacs via ``M-x + py-version``. Versions 4.68 and newer contain the necessary fixes for + proper IPython support. + +The file :file:`ipython.el` is included with the IPython distribution, in the +directory :file:`docs/emacs`. Once you put these files in your Emacs path, all +you need in your :file:`.emacs` file is:: + + (require 'ipython) + +This should give you full support for executing code snippets via +IPython, opening IPython as your Python shell via ``C-c !``, etc. + +You can customize the arguments passed to the IPython instance at startup by +setting the ``py-python-command-args`` variable. For example, to start always +in ``pylab`` mode with hardcoded light-background colors, you can use:: + + (setq py-python-command-args '("-pylab" "-colors" "LightBG")) + +If you happen to get garbage instead of colored prompts as described in +the previous section, you may need to set also in your :file:`.emacs` file:: + + (setq ansi-color-for-comint-mode t) + +Notes on emacs support: + +* There is one caveat you should be aware of: you must start the IPython shell + before attempting to execute any code regions via ``C-c |``. Simply type + ``C-c !`` to start IPython before passing any code regions to the + interpreter, and you shouldn't experience any problems. This is due to a bug + in Python itself, which has been fixed for Python 2.3, but exists as of + Python 2.2.2 (reported as SF bug [ 737947 ]). + +* The (X)Emacs support is maintained by Alexander Schmolck, so all + comments/requests should be directed to him through the IPython mailing + lists. + +* This code is still somewhat experimental so it's a bit rough around the + edges (although in practice, it works quite well). + +* Be aware that if you customized ``py-python-command`` previously, this value + will override what :file:`ipython.el` does (because loading the customization + variables comes later). + +.. [Emacs] Emacs. http://www.gnu.org/software/emacs/ +.. [TextMate] TextMate: the missing editor. http://macromates.com/ +.. [vim] vim. http://www.vim.org/ diff --git a/docs/source/config/extension.txt b/docs/source/config/extension.txt new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/docs/source/config/extension.txt diff --git a/docs/source/config/index.txt b/docs/source/config/index.txt index 1214811..e960d71 100644 --- a/docs/source/config/index.txt +++ b/docs/source/config/index.txt @@ -1,3 +1,5 @@ +.. _config_index: + =============================== Configuration and customization =============================== @@ -5,6 +7,7 @@ Configuration and customization .. toctree:: :maxdepth: 2 - initial_config.txt - customization.txt - new_config.txt + overview.txt + ipython.txt + editors.txt + old.txt diff --git a/docs/source/config/ipython.txt b/docs/source/config/ipython.txt new file mode 100644 index 0000000..2b36017 --- /dev/null +++ b/docs/source/config/ipython.txt @@ -0,0 +1,136 @@ +.. _configuring_ipython: + +=========================================================== +Configuring the :command:`ipython` command line application +=========================================================== + +This section contains information about how to configure the +:command:`ipython` command line application. See the :ref:`configuration +overview ` for a more general description of the +configuration system and configuration file format. + +The default configuration file for the :command:`ipython` command line application +is :file:`ipython_config.py`. By setting the attributes in this file, you +can configure the application. A sample is provided in +:mod:`IPython.config.default.ipython_config`. Simply copy this file to your +IPython directory to start using it. + +Most configuration attributes that this file accepts are associated with +classes that are subclasses of :class:`~IPython.core.component.Component`. + +A few configuration attributes are not associated with a particular +:class:`~IPython.core.component.Component` subclass. These are application +wide configuration attributes and are stored in the ``Global`` +sub-configuration section. We begin with a description of these +attributes. + +Global configuration +==================== + +Assuming that your configuration file has the following at the top:: + + c = get_config() + +the following attributes can be set in the ``Global`` section. + +:attr:`c.Global.display_banner` + A boolean that determined if the banner is printer when :command:`ipython` + is started. + +:attr:`c.Global.classic` + A boolean that determines if IPython starts in "classic" mode. In this + mode, the prompts and everything mimic that of the normal :command:`python` + shell + +:attr:`c.Global.nosep` + A boolean that determines if there should be no blank lines between + prompts. + +:attr:`c.Global.log_level` + An integer that sets the detail of the logging level during the startup + of :command:`ipython`. The default is 30 and the possible values are + (0, 10, 20, 30, 40, 50). Higher is quieter and lower is more verbose. + +:attr:`c.Global.extensions` + A list of strings, each of which is an importable IPython extension. An + IPython extension is a regular Python module or package that has a + :func:`load_ipython_extension(ip)` method. This method gets called when + the extension is loaded with the currently running + :class:`~IPython.core.iplib.InteractiveShell` as its only argument. You + can put your extensions anywhere they can be imported but we add the + :file:`extensions` subdirectory of the ipython directory to ``sys.path`` + during extension loading, so you can put them there as well. Extensions + are not executed in the user's interactive namespace and they must be pure + Python code. Extensions are the recommended way of customizing + :command:`ipython`. Extensions can provide an + :func:`unload_ipython_extension` that will be called when the extension is + unloaded. + +:attr:`c.Global.exec_lines` + A list of strings, each of which is Python code that is run in the user's + namespace after IPython start. These lines can contain full IPython syntax + with magics, etc. + +:attr:`c.Global.exec_files` + A list of strings, each of which is the full pathname of a ``.py`` or + ``.ipy`` file that will be executed as IPython starts. These files are run + in IPython in the user's namespace. Files with a ``.py`` extension need to + be pure Python. Files with a ``.ipy`` extension can have custom IPython + syntax (magics, etc.). These files need to be in the cwd, the ipythondir + or be absolute paths. + +Classes that can be configured +============================== + +The following classes can also be configured in the configuration file for +:command:`ipython`: + +* :class:`~IPython.core.iplib.InteractiveShell` + +* :class:`~IPython.core.prefilter.PrefilterManager` + +* :class:`~IPython.core.alias.AliasManager` + +To see which attributes of these classes are configurable, please see the +source code for these classes, the class docstrings or the sample +configuration file :mod:`IPython.config.default.ipython_config`. + +Example +======= + +For those who want to get a quick start, here is a sample +:file:`ipython_config.py` that sets some of the common configuration +attributes:: + + # sample ipython_config.py + c = get_config() + + c.Global.display_banner = True + c.Global.log_level = 20 + c.Global.extensions = [ + 'myextension' + ] + c.Global.exec_lines = [ + 'import numpy', + 'import scipy' + ] + c.Global.exec_files = [ + 'mycode.py', + 'fancy.ipy' + ] + c.InteractiveShell.autoindent = True + c.InteractiveShell.colors = 'LightBG' + c.InteractiveShell.confirm_exit = False + c.InteractiveShell.deep_reload = True + c.InteractiveShell.editor = 'nano' + c.InteractiveShell.prompt_in1 = 'In [\#]: ' + c.InteractiveShell.prompt_in2 = ' .\D.: ' + c.InteractiveShell.prompt_out = 'Out[\#]: ' + c.InteractiveShell.prompts_pad_left = True + c.InteractiveShell.xmode = 'Context' + + c.PrefilterManager.multi_line_specials = True + + c.AliasManager.user_aliases = [ + ('la', 'ls -al') + ] \ No newline at end of file diff --git a/docs/source/config/new_config.txt b/docs/source/config/new_config.txt deleted file mode 100644 index 569888a..0000000 --- a/docs/source/config/new_config.txt +++ /dev/null @@ -1,27 +0,0 @@ -======================== -New configuration system -======================== - -IPython has a configuration system. When running IPython for the first time, -reasonable defaults are used for the configuration. The configuration of IPython -can be changed in two ways: - - * Configuration files - * Commands line options (which override the configuration files) - -IPython has a separate configuration file for each subpackage. Thus, the main -configuration files are (in your ``~/.ipython`` directory): - - * ``ipython1.core.ini`` - * ``ipython1.kernel.ini`` - * ``ipython1.notebook.ini`` - -To create these files for the first time, do the following:: - - from IPython.kernel.config import config_manager as kernel_config - kernel_config.write_default_config_file() - -But, you should only need to do this if you need to modify the defaults. If needed -repeat this process with the ``notebook`` and ``core`` configuration as well. If you -are running into problems with IPython, you might try deleting these configuration -files. \ No newline at end of file diff --git a/docs/source/config/initial_config.txt b/docs/source/config/old.txt similarity index 51% rename from docs/source/config/initial_config.txt rename to docs/source/config/old.txt index eec4dd0..feedfa5 100644 --- a/docs/source/config/initial_config.txt +++ b/docs/source/config/old.txt @@ -1,8 +1,13 @@ .. _initial config: -========================================= -Initial configuration of your environment -========================================= +============================================================= +Outdated configuration information that might still be useful +============================================================= + +.. warning:: + + All of the information in this file is outdated. Until the new + configuration system is better documented, this material is being kept. This section will help you set various things in your environment for your IPython sessions to be as efficient as possible. All of IPython's @@ -21,69 +26,6 @@ can change to suit your taste, you can find more details :ref:`here `. Here we discuss the basic things you will want to make sure things are working properly from the beginning. - -.. _accessing_help: - -Access to the Python help system -================================ - -This is true for Python in general (not just for IPython): you should have an -environment variable called PYTHONDOCS pointing to the directory where your -HTML Python documentation lives. In my system it's -:file:`/usr/share/doc/python-doc/html`, check your local details or ask your -systems administrator. - -This is the directory which holds the HTML version of the Python -manuals. Unfortunately it seems that different Linux distributions -package these files differently, so you may have to look around a bit. -Below I show the contents of this directory on my system for reference:: - - [html]> ls - about.html dist/ icons/ lib/ python2.5.devhelp.gz whatsnew/ - acks.html doc/ index.html mac/ ref/ - api/ ext/ inst/ modindex.html tut/ - -You should really make sure this variable is correctly set so that -Python's pydoc-based help system works. It is a powerful and convenient -system with full access to the Python manuals and all modules accessible -to you. - -Under Windows it seems that pydoc finds the documentation automatically, -so no extra setup appears necessary. - - -Editor -====== - -The %edit command (and its alias %ed) will invoke the editor set in your -environment as EDITOR. If this variable is not set, it will default to -vi under Linux/Unix and to notepad under Windows. You may want to set -this variable properly and to a lightweight editor which doesn't take -too long to start (that is, something other than a new instance of -Emacs). This way you can edit multi-line code quickly and with the power -of a real editor right inside IPython. - -If you are a dedicated Emacs user, you should set up the Emacs server so -that new requests are handled by the original process. This means that -almost no time is spent in handling the request (assuming an Emacs -process is already running). For this to work, you need to set your -EDITOR environment variable to 'emacsclient'. The code below, supplied -by Francois Pinard, can then be used in your .emacs file to enable the -server:: - - (defvar server-buffer-clients) - (when (and (fboundp 'server-start) (string-equal (getenv "TERM") 'xterm)) - (server-start) - (defun fp-kill-server-with-buffer-routine () - (and server-buffer-clients (server-done))) - (add-hook 'kill-buffer-hook 'fp-kill-server-with-buffer-routine)) - -You can also set the value of this editor via the commmand-line option -'-editor' or in your ipythonrc file. This is useful if you wish to use -specifically for IPython an editor different from your typical default -(and for Windows users who tend to use fewer environment variables). - - Color ===== @@ -133,7 +75,6 @@ IPython uses colors for two main groups of things: prompts and tracebacks which are directly printed to the terminal, and the object introspection system which passes large sets of data through a pager. - Input/Output prompts and exception tracebacks ============================================= @@ -175,70 +116,115 @@ In order to configure less as your default pager, do the following: properly interpret control sequences, which is how color information is given to your terminal. +For the bash shell, add to your ~/.bashrc file the lines:: + + export PAGER=less + export LESS=-r + For the csh or tcsh shells, add to your ~/.cshrc file the lines:: setenv PAGER less setenv LESS -r - + There is similar syntax for other Unix shells, look at your system documentation for details. If you are on a system which lacks proper data pagers (such as Windows), IPython will use a very limited builtin pager. -.. _emacs: - -(X)Emacs configuration -====================== - -Thanks to the work of Alexander Schmolck and Prabhu Ramachandran, -currently (X)Emacs and IPython get along very well. - -Important note: You will need to use a recent enough version of -python-mode.el, along with the file ipython.el. You can check that the -version you have of python-mode.el is new enough by either looking at -the revision number in the file itself, or asking for it in (X)Emacs via -M-x py-version. Versions 4.68 and newer contain the necessary fixes for -proper IPython support. - -The file ipython.el is included with the IPython distribution, in the -documentation directory (where this manual resides in PDF and HTML -formats). - -Once you put these files in your Emacs path, all you need in your .emacs -file is:: - - (require 'ipython) - -This should give you full support for executing code snippets via -IPython, opening IPython as your Python shell via ``C-c !``, etc. - -You can customize the arguments passed to the IPython instance at startup by -setting the ``py-python-command-args`` variable. For example, to start always -in ``pylab`` mode with hardcoded light-background colors, you can use:: - - (setq py-python-command-args '("-pylab" "-colors" "LightBG")) - -If you happen to get garbage instead of colored prompts as described in -the previous section, you may need to set also in your .emacs file:: - - (setq ansi-color-for-comint-mode t) +.. _Prompts: + +Fine-tuning your prompt +======================= + +IPython's prompts can be customized using a syntax similar to that of +the bash shell. Many of bash's escapes are supported, as well as a few +additional ones. We list them below:: + + \# + the prompt/history count number. This escape is automatically + wrapped in the coloring codes for the currently active color scheme. + \N + the 'naked' prompt/history count number: this is just the number + itself, without any coloring applied to it. This lets you produce + numbered prompts with your own colors. + \D + the prompt/history count, with the actual digits replaced by dots. + Used mainly in continuation prompts (prompt_in2) + \w + the current working directory + \W + the basename of current working directory + \Xn + where $n=0\ldots5.$ The current working directory, with $HOME + replaced by ~, and filtered out to contain only $n$ path elements + \Yn + Similar to \Xn, but with the $n+1$ element included if it is ~ (this + is similar to the behavior of the %cn escapes in tcsh) + \u + the username of the current user + \$ + if the effective UID is 0, a #, otherwise a $ + \h + the hostname up to the first '.' + \H + the hostname + \n + a newline + \r + a carriage return + \v + IPython version string + +In addition to these, ANSI color escapes can be insterted into the +prompts, as \C_ColorName. The list of valid color names is: Black, Blue, +Brown, Cyan, DarkGray, Green, LightBlue, LightCyan, LightGray, +LightGreen, LightPurple, LightRed, NoColor, Normal, Purple, Red, White, +Yellow. + +Finally, IPython supports the evaluation of arbitrary expressions in +your prompt string. The prompt strings are evaluated through the syntax +of PEP 215, but basically you can use $x.y to expand the value of x.y, +and for more complicated expressions you can use braces: ${foo()+x} will +call function foo and add to it the value of x, before putting the +result into your prompt. For example, using +prompt_in1 '${commands.getoutput("uptime")}\nIn [\#]: ' +will print the result of the uptime command on each prompt (assuming the +commands module has been imported in your ipythonrc file). + + + Prompt examples + +The following options in an ipythonrc file will give you IPython's +default prompts:: + + prompt_in1 'In [\#]:' + prompt_in2 ' .\D.:' + prompt_out 'Out[\#]:' + +which look like this:: + + In [1]: 1+2 + Out[1]: 3 + + In [2]: for i in (1,2,3): + ...: print i, + ...: + 1 2 3 + +These will give you a very colorful prompt with path information:: + + #prompt_in1 '\C_Red\u\C_Blue[\C_Cyan\Y1\C_Blue]\C_LightGreen\#>' + prompt_in2 ' ..\D>' + prompt_out '<\#>' + +which look like this:: + + fperez[~/ipython]1> 1+2 + <1> 3 + fperez[~/ipython]2> for i in (1,2,3): + ...> print i, + ...> + 1 2 3 -Notes: - * There is one caveat you should be aware of: you must start the - IPython shell before attempting to execute any code regions via - ``C-c |``. Simply type C-c ! to start IPython before passing any code - regions to the interpreter, and you shouldn't experience any - problems. - This is due to a bug in Python itself, which has been fixed for - Python 2.3, but exists as of Python 2.2.2 (reported as SF bug [ - 737947 ]). - * The (X)Emacs support is maintained by Alexander Schmolck, so all - comments/requests should be directed to him through the IPython - mailing lists. - * This code is still somewhat experimental so it's a bit rough - around the edges (although in practice, it works quite well). - * Be aware that if you customize py-python-command previously, this - value will override what ipython.el does (because loading the - customization variables comes later). diff --git a/docs/source/config/overview.txt b/docs/source/config/overview.txt new file mode 100644 index 0000000..2b79a3b --- /dev/null +++ b/docs/source/config/overview.txt @@ -0,0 +1,331 @@ +.. _config_overview: + +============================================ +Overview of the IPython configuration system +============================================ + +This section describes the IPython configuration system. Starting with version +0.11, IPython has a completely new configuration system that is quite +different from the older :file:`ipythonrc` or :file:`ipy_user_conf.py` +approaches. The new configuration system was designed from scratch to address +the particular configuration needs of IPython. While there are many +other excellent configuration systems out there, we found that none of them +met our requirements. + +.. warning:: + + If you are upgrading to version 0.11 of IPython, you will need to migrate + your old :file:`ipythonrc` or :file:`ipy_user_conf.py` configuration files + to the new system. Read on for information on how to do this. + +The discussion that follows is focused on teaching user's how to configure +IPython to their liking. Developer's who want to know more about how they +can enable their objects to take advantage of the configuration system +should consult our :ref:`developer guide ` + +The main concepts +================= + +There are a number of abstractions that the IPython configuration system uses. +Each of these abstractions is represented by a Python class. + +Configuration object: :class:`~IPython.config.loader.Config` + A configuration object is a simple dictionary-like class that holds + configuration attributes and sub-configuration objects. These classes + support dotted attribute style access (``Foo.bar``) in addition to the + regular dictionary style access (``Foo['bar']``). Configuration objects + are smart. They know how to merge themselves with other configuration + objects and they automatically create sub-configuration objects. + +Application: :class:`~IPython.core.application.Application` + An application is a process that does a specific job. The most obvious + application is the :command:`ipython` command line program. Each + application reads a *single* configuration file and command line options + and then produces a master configuration object for the application. This + configuration object is then passed to the components that the application + creates. Components implement the actual logic of the application and know + how to configure themselves given the configuration object. + +Component: :class:`~IPython.core.component.Component` + A component is a regular Python class that serves as a base class for all + main classes in an application. The + :class:`~IPython.core.component.Component` base class is lightweight and + only does two main things. + + First, it keeps track of all instances of itself and provides an + interfaces for querying those instances. This enables components to get + references to other components, even though they are not "nearby" in the + runtime object graph. + + Second, it declares what class attributes are configurable and specifies + the default types and values of those attributes. This information is used + to automatically configure instances given the applications configuration + object. + + Developers create :class:`~IPython.core.component.Component` subclasses + that implement all of the logic in the application. Each of these + subclasses has its own configuration information that controls how + instances are created. + +Having described these main concepts, we can now state the main idea in our +configuration system: *"configuration" allows the default values of class +attributes to be controlled on a class by class basis*. Thus all instances of +a given class are configured in the same way. Furthermore, if two instances +need to be configured differently, they need to be instances of two different +classes. While this model may seem a bit restrictive, we have found that it +expresses most things that need to be configured extremely well. + +Now, we show what our configuration objects and files look like. + +Configuration objects and files +=============================== + +A configuration file is simply a pure Python file that sets the attributes +of a global, pre-created configuration object. This configuration object is a +:class:`~IPython.config.loader.Config` instance. While in a configuration +file, to get a reference to this object, simply call the :func:`get_config` +function. We inject this function into the global namespace that the +configuration file is executed in. + +Here is an example of a super simple configuration file that does nothing:: + + c = get_config() + +Once you get a reference to the configuration object, you simply set +attributes on it. All you have to know is: + +* The name of each attribute. +* The type of each attribute. + +The answers to these two questions are provided by the various +:class:`~IPython.core.component.Component` subclasses that an application +uses. Let's look at how this would work for a simple component subclass:: + + # Sample component that can be configured. + from IPython.core.component import Component + from IPython.utils.traitlets import Int, Float, Str, Bool + + class MyComponent(Component): + name = Str('defaultname', config=True) + ranking = Int(0, config=True) + value = Float(99.0) + # The rest of the class implementation would go here.. + +In this example, we see that :class:`MyComponent` has three attributes, two +of whom (``name``, ``ranking``) can be configured. All of the attributes +are given types and default values. If a :class:`MyComponent` is instantiated, +but not configured, these default values will be used. But let's see how +to configure this class in a configuration file:: + + # Sample config file + c = get_config() + + c.MyComponent.name = 'coolname' + c.MyComponent.ranking = 10 + +After this configuration file is loaded, the values set in it will override +the class defaults anytime a :class:`MyComponent` is created. Furthermore, +these attributes will be type checked and validated anytime they are set. +This type checking is handled by the :mod:`IPython.utils.traitlets` module, +which provides the :class:`Str`, :class:`Int` and :class:`Float` types. In +addition to these traitlets, the :mod:`IPython.utils.traitlets` provides +traitlets for a number of other types. + +.. note:: + + Underneath the hood, the :class:`Component` base class is a subclass of + :class:`IPython.utils.traitlets.HasTraitlets`. The + :mod:`IPython.utils.traitlets` module is a lightweight version of + :mod:`enthought.traits`. Our implementation is a pure Python subset + (mostly API compatible) of :mod:`enthought.traits` that does not have any + of the automatic GUI generation capabilities. Our plan is to achieve 100% + API compatibility to enable the actual :mod:`enthought.traits` to + eventually be used instead. Currently, we cannot use + :mod:`enthought.traits` as we are committed to the core of IPython being + pure Python. + +It should be very clear at this point what the naming convention is for +configuration attributes:: + + c.ClassName.attribute_name = attribute_value + +Here, ``ClassName`` is the name of the class whose configuration attribute you +want to set, ``attribute_name`` is the name of the attribute you want to set +and ``attribute_value`` the the value you want it to have. The ``ClassName`` +attribute of ``c`` is not the actual class, but instead is another +:class:`~IPython.config.loader.Config` instance. + +.. note:: + + The careful reader may wonder how the ``ClassName`` (``MyComponent`` in + the above example) attribute of the configuration object ``c`` gets + created. These attributes are created on the fly by the + :class:`~IPython.config.loader.Config` instance, using a simple naming + convention. Any attribute of a :class:`~IPython.config.loader.Config` + instance whose name begins with an uppercase character is assumed to be a + sub-configuration and a new empty :class:`~IPython.config.loader.Config` + instance is dynamically created for that attribute. This allows deeply + hierarchical information created easily (``c.Foo.Bar.value``) on the + fly. + +Configuration files inheritance +=============================== + +Let's say you want to have different configuration files for various purposes. +Our configuration system makes it easy for one configuration file to inherit +the information in another configuration file. The :func:`load_subconfig` +command can be used in a configuration file for this purpose. Here is a simple +example that loads all of the values from the file :file:`base_config.py`:: + + # base_config.py + c = get_config() + c.MyComponent.name = 'coolname' + c.MyComponent.ranking = 100 + +into the configuration file :file:`main_config.py`:: + + # main_config.py + c = get_config() + + # Load everything from base_config.py + load_subconfig('base_config.py') + + # Now override one of the values + c.MyComponent.name = 'bettername' + +In a situation like this the :func:`load_subconfig` makes sure that the +search path for sub-configuration files is inherited from that of the parent. +Thus, you can typically put the two in the same directory and everything will +just work. + +Class based configuration inheritance +===================================== + +There is another aspect of configuration where inheritance comes into play. +Sometimes, your classes will have an inheritance hierarchy that you want +to be reflected in the configuration system. Here is a simple example:: + + from IPython.core.component import Component + from IPython.utils.traitlets import Int, Float, Str, Bool + + class Foo(Component): + name = Str('fooname', config=True) + value = Float(100.0, config=True) + + class Bar(Foo): + name = Str('barname', config=True) + othervalue = Int(0, config=True) + +Now, we can create a configuration file to configure instances of :class:`Foo` +and :class:`Bar`:: + + # config file + c = get_config() + + c.Foo.name = 'bestname' + c.Bar.othervalue = 10 + +This class hierarchy and configuration file accomplishes the following: + +* The default value for :attr:`Foo.name` and :attr:`Bar.name` will be + 'bestname'. Because :class:`Bar` is a :class:`Foo` subclass it also + picks up the configuration information for :class:`Foo`. +* The default value for :attr:`Foo.value` and :attr:`Bar.value` will be + ``100.0``, which is the value specified as the class default. +* The default value for :attr:`Bar.othervalue` will be 10 as set in the + configuration file. Because :class:`Foo` is the parent of :class:`Bar` + it doesn't know anything about the :attr:`othervalue` attribute. + +Configuration file location +=========================== + +So where should you put your configuration files? By default, all IPython +applications look in the so called "IPython directory". The location of +this directory is determined by the following algorithm: + +* If the ``--ipython-dir`` command line flag is given, its value is used. + +* If not, the value returned by :func:`IPython.utils.genutils.get_ipython_dir` + is used. This function will first look at the :envvar:`IPYTHON_DIR` + environment variable and then default to the directory + :file:`$HOME/.ipython`. + +For most users, the default value will simply be something like +:file:`$HOME/.ipython`. + +Once the location of the IPython directory has been determined, you need to +know what filename to use for the configuration file. The basic idea is that +each application has its own default configuration filename. The default named +used by the :command:`ipython` command line program is +:file:`ipython_config.py`. This value can be overriden by the ``-config_file`` +command line flag. A sample :file:`ipython_config.py` file can be found +in :mod:`IPython.config.default.ipython_config.py`. Simple copy it to your +IPython directory to begin using it. + +.. _Profiles: + +Profiles +======== + +A profile is simply a configuration file that follows a simple naming +convention and can be loaded using a simplified syntax. The idea is +that users often want to maintain a set of configuration files for different +purposes: one for doing numerical computing with NumPy and SciPy and +another for doing symbolic computing with SymPy. Profiles make it easy +to keep a separate configuration file for each of these purposes. + +Let's start by showing how a profile is used: + +.. code-block:: bash + + $ ipython -p sympy + +This tells the :command:`ipython` command line program to get its +configuration from the "sympy" profile. The search path for profiles is the +same as that of regular configuration files. The only difference is that +profiles are named in a special way. In the case above, the "sympy" profile +would need to have the name :file:`ipython_config_sympy.py`. + +The general pattern is this: simply add ``_profilename`` to the end of the +normal configuration file name. Then load the profile by adding ``-p +profilename`` to your command line options. + +IPython ships with some sample profiles in :mod:`IPython.config.profile`. +Simply copy these to your IPython directory to begin using them. + +Design requirements +=================== + +Here are the main requirements we wanted our configuration system to have: + +* Support for hierarchical configuration information. + +* Full integration with command line option parsers. Often, you want to read + a configuration file, but then override some of the values with command line + options. Our configuration system automates this process and allows each + command line option to be linked to a particular attribute in the + configuration hierarchy that it will override. + +* Configuration files that are themselves valid Python code. This accomplishes + many things. First, it becomes possible to put logic in your configuration + files that sets attributes based on your operating system, network setup, + Python version, etc. Second, Python has a super simple syntax for accessing + hierarchical data structures, namely regular attribute access + (``Foo.Bar.Bam.name``). Third, using Python makes it easy for users to + import configuration attributes from one configuration file to another. + Forth, even though Python is dynamically typed, it does have types that can + be checked at runtime. Thus, a ``1`` in a config file is the integer '1', + while a ``'1'`` is a string. + +* A fully automated method for getting the configuration information to the + classes that need it at runtime. Writing code that walks a configuration + hierarchy to extract a particular attribute is painful. When you have + complex configuration information with hundreds of attributes, this makes + you want to cry. + +* Type checking and validation that doesn't require the entire configuration + hierarchy to be specified statically before runtime. Python is a very + dynamic language and you don't always know everything that needs to be + configured when a program starts. + + diff --git a/docs/source/credits.txt b/docs/source/credits.txt deleted file mode 100644 index 9e92e81..0000000 --- a/docs/source/credits.txt +++ /dev/null @@ -1,207 +0,0 @@ -.. _credits: - -======= -Credits -======= - -IPython is led by Fernando Pérez. - -As of this writing, the following developers have joined the core team: - -* [Robert Kern] : co-mentored the 2005 - Google Summer of Code project to develop python interactive - notebooks (XML documents) and graphical interface. This project - was awarded to the students Tzanko Matev and - Toni Alatalo . - -* [Brian Granger] : extending IPython to allow - support for interactive parallel computing. - -* [Benjamin (Min) Ragan-Kelley]: key work on IPython's parallel - computing infrastructure. - -* [Ville Vainio] : Ville has made many improvements - to the core of IPython and was the maintainer of the main IPython - trunk from version 0.7.1 to 0.8.4. - -* [Gael Varoquaux] : work on the merged - architecture for the interpreter as of version 0.9, implementing a new WX GUI - based on this system. - -* [Barry Wark] : implementing a new Cocoa GUI, as well - as work on the new interpreter architecture and Twisted support. - -* [Laurent Dufrechou] : development of the WX - GUI support. - -* [Jörgen Stenarson] : maintainer of the - PyReadline project, necessary for IPython under windows. - - -The IPython project is also very grateful to: - -Bill Bumgarner : for providing the DPyGetOpt module -which gives very powerful and convenient handling of command-line -options (light years ahead of what Python 2.1.1's getopt module does). - -Ka-Ping Yee : for providing the Itpl module for -convenient and powerful string interpolation with a much nicer syntax -than formatting through the '%' operator. - -Arnd Baecker : for his many very useful -suggestions and comments, and lots of help with testing and -documentation checking. Many of IPython's newer features are a result of -discussions with him (bugs are still my fault, not his). - -Obviously Guido van Rossum and the whole Python development team, that -goes without saying. - -IPython's website is generously hosted at http://ipython.scipy.orgby -Enthought (http://www.enthought.com). I am very grateful to them and all -of the SciPy team for their contribution. - -Fernando would also like to thank Stephen Figgins , -an O'Reilly Python editor. His Oct/11/2001 article about IPP and -LazyPython, was what got this project started. You can read it at: -http://www.onlamp.com/pub/a/python/2001/10/11/pythonnews.html. - -And last but not least, all the kind IPython users who have emailed new code, -bug reports, fixes, comments and ideas. A brief list follows, please let us -know if we have ommitted your name by accident: - -* Dan Milstein . A bold refactoring of the - core prefilter stuff in the IPython interpreter. - -* [Jack Moffit] Bug fixes, including the infamous - color problem. This bug alone caused many lost hours and - frustration, many thanks to him for the fix. I've always been a - fan of Ogg & friends, now I have one more reason to like these folks. - Jack is also contributing with Debian packaging and many other - things. - -* [Alexander Schmolck] Emacs work, bug - reports, bug fixes, ideas, lots more. The ipython.el mode for - (X)Emacs is Alex's code, providing full support for IPython under - (X)Emacs. - -* [Andrea Riciputi] Mac OSX - information, Fink package management. - -* [Gary Bishop] Bug reports, and patches to work - around the exception handling idiosyncracies of WxPython. Readline - and color support for Windows. - -* [Jeffrey Collins] Bug reports. Much - improved readline support, including fixes for Python 2.3. - -* [Dryice Liu] FreeBSD port. - -* [Mike Heeter] - -* [Christopher Hart] PDB integration. - -* [Milan Zamazal] Emacs info. - -* [Philip Hisley] - -* [Holger Krekel] Tab completion, lots - more. - -* [Robin Siebler] - -* [Ralf Ahlbrink] - -* [Thorsten Kampe] - -* [Fredrik Kant] Windows setup. - -* [Syver Enstad] Windows setup. - -* [Richard] Global embedding. - -* [Hayden Callow] Gnuplot.py 1.6 - compatibility. - -* [Leonardo Santagada] Fixes for Windows - installation. - -* [Christopher Armstrong] Bugfixes. - -* [Francois Pinard] Code and - documentation fixes. - -* [Cory Dodt] Bug reports and Windows - ideas. Patches for Windows installer. - -* [Olivier Aubert] New magics. - -* [King C. Shu] Autoindent patch. - -* [Chris Drexler] Readline packages for - Win32/CygWin. - -* [Gustavo Cordova Avila] EvalDict code for - nice, lightweight string interpolation. - -* [Kasper Souren] Bug reports, ideas. - -* [Gever Tulley] Code contributions. - -* [Ralf Schmitt] Bug reports & fixes. - -* [Oliver Sander] Bug reports. - -* [Rod Holland] Bug reports and fixes to - logging module. - -* [Daniel 'Dang' Griffith] - Fixes, enhancement suggestions for system shell use. - -* [Viktor Ransmayr] Tests and - reports on Windows installation issues. Contributed a true Windows - binary installer. - -* [Mike Salib] Help fixing a subtle bug related - to traceback printing. - -* [W.J. van der Laan] Bash-like - prompt specials. - -* [Antoon Pardon] Critical fix for - the multithreaded IPython. - -* [John Hunter] Matplotlib - author, helped with all the development of support for matplotlib - in IPyhton, including making necessary changes to matplotlib itself. - -* [Matthew Arnison] Bug reports, '%run -d' idea. - -* [Prabhu Ramachandran] Help - with (X)Emacs support, threading patches, ideas... - -* [Norbert Tretkowski] help with Debian - packaging and distribution. - -* [George Sakkis] New matcher for - tab-completing named arguments of user-defined functions. - -* [Jörgen Stenarson] Wildcard - support implementation for searching namespaces. - -* [Vivian De Smedt] Debugger enhancements, - so that when pdb is activated from within IPython, coloring, tab - completion and other features continue to work seamlessly. - -* [Scott Tsai] Support for automatic - editor invocation on syntax errors (see - http://www.scipy.net/roundup/ipython/issue36). - -* [Alexander Belchenko] Improvements for win32 - paging system. - -* [Will Maier] Official OpenBSD port. - -* [Ondrej Certik] : set up the IPython docs to use the new - Sphinx system used by Python, Matplotlib and many more projects. - -* [Stefan van der Walt] : support for the new config system. diff --git a/docs/source/development/coding_guide.txt b/docs/source/development/coding_guide.txt index 761fdbb..a6155cd 100644 --- a/docs/source/development/coding_guide.txt +++ b/docs/source/development/coding_guide.txt @@ -1,39 +1,36 @@ -============== - Coding guide -============== +============ +Coding guide +============ - -Coding conventions -================== +General coding conventions +========================== In general, we'll try to follow the standard Python style conventions as -described in Python's `PEP 8`_, the official Python Style Guide. - -.. _PEP 8: http://www.python.org/peps/pep-0008.html +described in Python's PEP 8 [PEP8]_, the official Python Style Guide. -Other comments: +Other general comments: -- In a large file, top level classes and functions should be separated by 2-3 +* In a large file, top level classes and functions should be separated by 2 lines to make it easier to separate them visually. -- Use 4 spaces for indentation, *never* use hard tabs. +* Use 4 spaces for indentation, **never** use hard tabs. -- Keep the ordering of methods the same in classes that have the same methods. +* Keep the ordering of methods the same in classes that have the same methods. This is particularly true for classes that implement similar interfaces and for interfaces that are similar. Naming conventions ------------------- +================== -In terms of naming conventions, we'll follow the guidelines of PEP 8. Some of -the existing code doesn't honor this perfectly, but for all new IPython code -(and much existing code is being refactored), we'll use: +In terms of naming conventions, we'll follow the guidelines of PEP 8 [PEP8]_. +Some of the existing code doesn't honor this perfectly, but for all new +IPython code (and much existing code is being refactored), we'll use: -- All ``lowercase`` module names. +* All ``lowercase`` module names. -- ``CamelCase`` for class names. +* ``CamelCase`` for class names. -- ``lowercase_with_underscores`` for methods, functions, variables and +* ``lowercase_with_underscores`` for methods, functions, variables and attributes. This may be confusing as some of the existing codebase uses a different @@ -48,12 +45,12 @@ standards at all times. In particular, when subclassing classes that use other naming conventions, you must follow their naming conventions. To deal with cases like this, we propose the following policy: -- If you are subclassing a class that uses different conventions, use its +* If you are subclassing a class that uses different conventions, use its naming conventions throughout your subclass. Thus, if you are creating a Twisted Protocol class, used Twisted's ``namingSchemeForMethodsAndAttributes.`` -- All IPython's official interfaces should use our conventions. In some cases +* All IPython's official interfaces should use our conventions. In some cases this will mean that you need to provide shadow names (first implement ``fooBar`` and then ``foo_bar = fooBar``). We want to avoid this at all costs, but it will probably be necessary at times. But, please use this @@ -74,68 +71,8 @@ explicit ``IP``. In Python this is mostly unnecessary, redundant and frowned upon, as namespaces offer cleaner prefixing. The only case where this approach is justified is for classes which are expected to be imported into external namespaces and a very generic name (like Shell) is too likely to clash with -something else. We'll need to revisit this issue as we clean up and refactor -the code, but in general we should remove as many unnecessary ``IP``/``ip`` -prefixes as possible. However, if a prefix seems absolutely necessary the more +something else. However, if a prefix seems absolutely necessary the more specific ``IPY`` or ``ipy`` are preferred. +.. [PEP8] Python Enhancement Proposal 8. http://www.python.org/peps/pep-0008.html -.. _devel-testing: - -Testing system -============== - -It is extremely important that all code contributed to IPython has tests. Tests -should be written as unittests, doctests or as entities that the `Nose`_ -testing package will find. Regardless of how the tests are written, we will use -`Nose`_ for discovering and running the tests. `Nose`_ will be required to run -the IPython test suite, but will not be required to simply use IPython. - -.. _Nose: http://code.google.com/p/python-nose/ - -Tests of `Twisted`__ using code should be written by subclassing the -``TestCase`` class that comes with ``twisted.trial.unittest``. When this is -done, `Nose`_ will be able to run the tests and the twisted reactor will be -handled correctly. - -.. __: http://www.twistedmatrix.com - -Each subpackage in IPython should have its own ``tests`` directory that -contains all of the tests for that subpackage. This allows each subpackage to -be self-contained. If a subpackage has any dependencies beyond the Python -standard library, the tests for that subpackage should be skipped if the -dependencies are not found. This is very important so users don't get tests -failing simply because they don't have dependencies. - -We also need to look into use Noses ability to tag tests to allow a more -modular approach of running tests. - -.. _devel-config: - -Configuration system -==================== - -IPython uses `.ini`_ files for configuration purposes. This represents a huge -improvement over the configuration system used in IPython. IPython works with -these files using the `ConfigObj`_ package, which IPython includes as -``ipython1/external/configobj.py``. - -Currently, we are using raw `ConfigObj`_ objects themselves. Each subpackage of -IPython should contain a ``config`` subdirectory that contains all of the -configuration information for the subpackage. To see how configuration -information is defined (along with defaults) see at the examples in -``ipython1/kernel/config`` and ``ipython1/core/config``. Likewise, to see how -the configuration information is used, see examples in -``ipython1/kernel/scripts/ipengine.py``. - -Eventually, we will add a new layer on top of the raw `ConfigObj`_ objects. We -are calling this new layer, ``tconfig``, as it will use a `Traits`_-like -validation model. We won't actually use `Traits`_, but will implement -something similar in pure Python. But, even in this new system, we will still -use `ConfigObj`_ and `.ini`_ files underneath the hood. Talk to Fernando if you -are interested in working on this part of IPython. The current prototype of -``tconfig`` is located in the IPython sandbox. - -.. _.ini: http://docs.python.org/lib/module-ConfigParser.html -.. _ConfigObj: http://www.voidspace.org.uk/python/configobj.html -.. _Traits: http://code.enthought.com/traits/ diff --git a/docs/source/development/config_blueprint.txt b/docs/source/development/config_blueprint.txt deleted file mode 100644 index caa5d13..0000000 --- a/docs/source/development/config_blueprint.txt +++ /dev/null @@ -1,33 +0,0 @@ -========================================= -Notes on the IPython configuration system -========================================= - -This document has some random notes on the configuration system. - -To start, an IPython process needs: - -* Configuration files -* Command line options -* Additional files (FURL files, extra scripts, etc.) - -It feeds these things into the core logic of the process, and as output, -produces: - -* Log files -* Security files - -There are a number of things that complicate this: - -* A process may need to be started on a different host that doesn't have - any of the config files or additional files. Those files need to be - moved over and put in a staging area. The process then needs to be told - about them. -* The location of the output files should somehow be set by config files or - command line options. -* Our config files are very hierarchical, but command line options are flat, - making it difficult to relate command line options to config files. -* Some processes (like ipcluster and the daemons) have to manage the input and - output files for multiple different subprocesses, each possibly on a - different host. Ahhhh! -* Our configurations are not singletons. A given user will likely have - many different configurations for different clusters. diff --git a/docs/source/development/contributing.txt b/docs/source/development/contributing.txt new file mode 100644 index 0000000..ab57554 --- /dev/null +++ b/docs/source/development/contributing.txt @@ -0,0 +1,209 @@ +.. _contributing: + +============================ +How to contribute to IPython +============================ + +Overview +======== + +IPython development is done using Bazaar [Bazaar]_ and Launchpad [Launchpad]_. +This makes it easy for people to contribute to the development of IPython. +There are several ways in which you can join in. + +If you have a small change that you want to contribute, you can edit your +Bazaar checkout of IPython (see below) in-place, and ask Bazaar for the +differences: + +.. code-block:: bash + + $ cd /path/to/your/copy/of/ipython + $ bzr diff > my_fixes.diff + +This produces a patch file with your fixes, which we can apply to the source +tree. This file should then be attached to a ticket in our `bug tracker +`_, indicating what it does. + +This model of creating small, self-contained patches works very well and there +are open source projects that do their entire development this way. However, +in IPython we have found that for tracking larger changes, making use of +Bazaar's full capabilities in conjunction with Launchpad's code hosting +services makes for a much better experience. + +Making your own branch of IPython allows you to refine your changes over time, +track the development of the main team, and propose your own full version of +the code for others to use and review, with a minimum amount of fuss. The next +parts of this document will explain how to do this. + +Install Bazaar and create a Launchpad account +--------------------------------------------- + +First make sure you have installed Bazaar (see their `website +`_). To see that Bazaar is installed and knows about +you, try the following: + +.. code-block:: bash + + $ bzr whoami + Joe Coder + +This should display your name and email. Next, you will want to create an +account on the `Launchpad website `_ and setup your +ssh keys. For more information of setting up your ssh keys, see `this link +`_. + +Get the main IPython branch from Launchpad +------------------------------------------ + +Now, you can get a copy of the main IPython development branch (we call this +the "trunk"): + +.. code-block:: bash + + $ bzr branch lp:ipython + +Create a working branch +----------------------- + +When working on IPython, you won't actually make edits directly to the +:file:`lp:ipython` branch. Instead, you will create a separate branch for your +changes. For now, let's assume you want to do your work in a branch named +"ipython-mybranch". Create this branch by doing: + +.. code-block:: bash + + $ bzr branch ipython ipython-mybranch + +When you actually create a branch, you will want to give it a name that +reflects the nature of the work that you will be doing in it, like +"install-docs-update". + +Make edits in your working branch +--------------------------------- + +Now you are ready to actually make edits in your :file:`ipython-mybranch` +branch. Before doing this, it is helpful to install this branch so you can +test your changes as you work. This is easiest if you have setuptools +installed. Then, just do: + +.. code-block:: bash + + $ cd ipython-mybranch + $ python setupegg.py develop + +Now, make some changes. After a while, you will want to commit your changes. +This let's Bazaar know that you like the changes you have made and gives you +an opportunity to keep a nice record of what you have done. This looks like +this: + +.. code-block:: bash + + $ ...do work in ipython-mybranch... + $ bzr commit -m "the commit message goes here" + +Please note that since we now don't use an old-style linear ChangeLog (that +tends to cause problems with distributed version control systems), you should +ensure that your log messages are reasonably detailed. Use a docstring-like +approach in the commit messages (including the second line being left +*blank*):: + + Single line summary of changes being committed. + + * more details when warranted ... + * including crediting outside contributors if they sent the + code/bug/idea! + +As you work, you will repeat this edit/commit cycle many times. If you work on +your branch for a long time, you will also want to get the latest changes from +the :file:`lp:ipython` branch. This can be done with the following sequence of +commands: + +.. code-block:: bash + + $ ls + ipython + ipython-mybranch + + $ cd ipython + $ bzr pull + $ cd ../ipython-mybranch + $ bzr merge ../ipython + $ bzr commit -m "Merging changes from trunk" + +Post your branch and request a code review +------------------------------------------ + +Once you are done with your edits, you should post your branch on Launchpad so +that other IPython developers can review the changes and help you merge your +changes into the main development branch. To post your branch on Launchpad, +do: + +.. code-block:: bash + + $ cd ipython-mybranch + $ bzr push lp:~yourusername/ipython/ipython-mybranch + +Then, go to the `IPython Launchpad site `_, +and you should see your branch under the "Code" tab. If you click on your +branch, you can provide a short description of the branch as well as mark its +status. Most importantly, you should click the link that reads "Propose for +merging into another branch". What does this do? + +This let's the other IPython developers know that your branch is ready to be +reviewed and merged into the main development branch. During this review +process, other developers will give you feedback and help you get your code +ready to be merged. What types of things will we be looking for: + +* All code is documented. How to document your code is described in + :ref:`this section `. +* All code has tests. How to write and run tests is described in + :ref:`this section `. +* The entire IPython test suite passes. + +You should also provide us with a list of changes that your branch contains. +See the :ref:`What's new ` section of our documentation +(:file:`docs/source/whatsnew`) for details on the format and content of this. + +Once your changes have been reviewed and approved, someone will merge them +into the main development branch. + + +Merging a branch into trunk +=========================== + +Core developers, who ultimately merge any approved branch (from themselves, +another developer, or any third-party contribution) will typically use +:command:`bzr merge` to merge the branch into the trunk and push it to the +main Launchpad site. There are a number of things to keep in mind when doing +this, so that the project history is easy to understand in the long +run, and that generating release notes is as painless and accurate as +possible. + +* When you merge any non-trivial functionality (from one small bug fix to a + big feature branch), please remember to always edit the appropriate file in + the :ref:`What's new ` section of our documentation. + Ideally, the author of the branch should provide this content when they + submit the branch for review. But if they don't it is the responsibility of + the developer doing the merge to add this information. + +* When merges are done, the practice of putting a summary commit message in + the merge is *extremely* useful. It is probably easiest if you simply use + the same list of changes that were added to the :ref:`What's new + ` section of the documentation. + +* It's important that we remember to always credit who gave us something if + it's not the committer. In general, we have been fairly good on this front, + this is just a reminder to keep things up. As a note, if you are ever + committing something that is completely (or almost so) a third-party + contribution, do the commit as:: + + $ bzr commit --author="Someone Else" + + This way it will show that name separately in the log, which makes it even + easier to spot. Obviously we often rework third party contributions + extensively, but this is still good to keep in mind for cases when we don't + touch the code too much. + + +.. [Bazaar] Bazaar. http://bazaar-vcs.org/ +.. [Launchpad] Launchpad. http://www.launchpad.net/ipython diff --git a/docs/source/development/doc_guide.txt b/docs/source/development/doc_guide.txt index 9a0e453..8f728ce 100644 --- a/docs/source/development/doc_guide.txt +++ b/docs/source/development/doc_guide.txt @@ -8,31 +8,30 @@ Standalone documentation ======================== All standalone documentation should be written in plain text (``.txt``) files -using `reStructuredText`_ for markup and formatting. All such documentation -should be placed in the top level directory ``docs`` of the IPython source -tree. Or, when appropriate, a suitably named subdirectory should be used. The -documentation in this location will serve as the main source for IPython -documentation and all existing documentation should be converted to this -format. +using reStructuredText [reStructuredText]_ for markup and formatting. All such +documentation should be placed in the directory :file:`docs/source` of the +IPython source tree. Or, when appropriate, a suitably named subdirectory +should be used. The documentation in this location will serve as the main +source for IPython documentation. -The actual HTML and PDF docs are built using the Sphinx_ documentation -generation tool. Sphinx has been adopted as the default documentation tool for -Python itself as of version 2.6, as well as by a number of projects that -IPython is related with, such as numpy, scipy, matplotlib, sage and nipy. - -.. _reStructuredText: http://docutils.sourceforge.net/rst.html -.. _Sphinx: http://sphinx.pocoo.org/ +The actual HTML and PDF docs are built using the Sphinx [Sphinx]_ +documentation generation tool. Once you have Sphinx installed, you can build +the html docs yourself by doing: +.. code-block:: bash -The rest of this document is mostly taken from the `matploblib -documentation`__; we are using a number of Sphinx tools and extensions written -by the matplotlib team and will mostly follow their conventions, which are -nicely spelled out in their guide. What follows is thus a lightly adapted -version of the matplotlib documentation guide, taken with permission from the -MPL team. + $ cd ipython-mybranch/docs + $ make html -.. __: http://matplotlib.sourceforge.net/devel/documenting_mpl.html +Our usage of Sphinx follows that of matplotlib [Matplotlib]_ closely. We are +using a number of Sphinx tools and extensions written by the matplotlib team +and will mostly follow their conventions, which are nicely spelled out in +their documentation guide [MatplotlibDocGuide]_. What follows is thus a +abridged version of the matplotlib documentation guide, taken with permission +from the matplotlib team. +If you are reading this in a web browser, you can click on the "Show Source" +link to see the original reStricturedText for the following examples. A bit of Python code:: @@ -46,7 +45,6 @@ An interactive Python session:: >>> genutils.get_ipython_dir() '/home/fperez/.ipython' - An IPython session: .. code-block:: ipython @@ -68,12 +66,11 @@ A bit of shell code: echo "My home directory is: $HOME" ls - Docstring format ================ Good docstrings are very important. Unfortunately, Python itself only provides -a rather loose standard for docstrings (`PEP 257`_), and there is no universally +a rather loose standard for docstrings [PEP257]_, and there is no universally accepted convention for all the different parts of a complete docstring. However, the NumPy project has established a very reasonable standard, and has developed some tools to support the smooth inclusion of such docstrings in @@ -82,22 +79,26 @@ IPython will be henceforth documented using the NumPy conventions; we carry copies of some of the NumPy support tools to remain self-contained, but share back upstream with NumPy any improvements or fixes we may make to the tools. -The `NumPy documentation guidelines`_ contain detailed information on this -standard, and for a quick overview, the NumPy `example docstring`_ is a useful -read. +The NumPy documentation guidelines [NumPyDocGuide]_ contain detailed +information on this standard, and for a quick overview, the NumPy example +docstring [NumPyExampleDocstring]_ is a useful read. -As in the past IPython used epydoc, currently many docstrings still use epydoc -conventions. We will update them as we go, but all new code should be fully +In the past IPython used epydoc so currently many docstrings still use epydoc +conventions. We will update them as we go, but all new code should be documented using the NumPy standard. -.. _PEP 257: http://www.python.org/peps/pep-0257.html -.. _NumPy documentation guidelines: http://projects.scipy.org/numpy/wiki/CodingStyleGuidelines +Here are two additional PEPs of interest regarding documentation of code. +While both of these were rejected, the ideas therein form much of the basis of +docutils (the machinery to process reStructuredText): + +* `Docstring Processing System Framework `_ +* `Docutils Design Specification `_ -.. _example docstring: http://projects.scipy.org/numpy/browser/trunk/doc/EXAMPLE_DOCSTRING.txt -Additional PEPs of interest regarding documentation of code. While both of -these were rejected, the ideas therein form much of the basis of docutils (the -machinery to process reStructuredText): +.. [reStructuredText] reStructuredText. http://docutils.sourceforge.net/rst.html +.. [Sphinx] Sphinx. http://sphinx.pocoo.org/ +.. [MatplotlibDocGuide] http://matplotlib.sourceforge.net/devel/documenting_mpl.html +.. [PEP257] PEP 257. http://www.python.org/peps/pep-0257.html +.. [NumPyDocGuide] NumPy documentation guide. http://projects.scipy.org/numpy/wiki/CodingStyleGuidelines +.. [NumPyExampleDocstring] NumPy example docstring. http://projects.scipy.org/numpy/browser/trunk/doc/EXAMPLE_DOCSTRING.txt -- `Docstring Processing System Framework `_ -- `Docutils Design Specification `_ diff --git a/docs/source/development/index.txt b/docs/source/development/index.txt index 8d1ba14..4671263 100644 --- a/docs/source/development/index.txt +++ b/docs/source/development/index.txt @@ -1,14 +1,19 @@ -=========================== - IPython Developer's Guide -=========================== +.. _developer_guide: + +========================= +IPython developer's guide +========================= .. toctree:: - :maxdepth: 2 + :maxdepth: 1 - overview.txt + contributing.txt coding_guide.txt doc_guide.txt + testing.txt + release.txt roadmap.txt - + reorg.txt notification_blueprint.txt - config_blueprint.txt + ipgraph.txt + diff --git a/docs/source/development/ipgraph.txt b/docs/source/development/ipgraph.txt new file mode 100644 index 0000000..f0b10fe --- /dev/null +++ b/docs/source/development/ipgraph.txt @@ -0,0 +1,59 @@ +==================================================== +Notes on code execution in :class:`InteractiveShell` +==================================================== + +Overview +======== + +This section contains information and notes about the code execution +system in :class:`InteractiveShell`. This system needs to be refactored +and we are keeping notes about this process here. + +Current design +============== + +Here is a script that shows the relationships between the various +methods in :class:`InteractiveShell` that manage code execution:: + + import networkx as nx + import matplotlib.pyplot as plt + + exec_init_cmd = 'exec_init_cmd' + interact = 'interact' + runlines = 'runlines' + runsource = 'runsource' + runcode = 'runcode' + push_line = 'push_line' + mainloop = 'mainloop' + embed_mainloop = 'embed_mainloop' + ri = 'raw_input' + prefilter = 'prefilter' + + g = nx.DiGraph() + + g.add_node(exec_init_cmd) + g.add_node(interact) + g.add_node(runlines) + g.add_node(runsource) + g.add_node(push_line) + g.add_node(mainloop) + g.add_node(embed_mainloop) + g.add_node(ri) + g.add_node(prefilter) + + g.add_edge(exec_init_cmd, push_line) + g.add_edge(exec_init_cmd, prefilter) + g.add_edge(mainloop, exec_init_cmd) + g.add_edge(mainloop, interact) + g.add_edge(embed_mainloop, interact) + g.add_edge(interact, ri) + g.add_edge(interact, push_line) + g.add_edge(push_line, runsource) + g.add_edge(runlines, push_line) + g.add_edge(runlines, prefilter) + g.add_edge(runsource, runcode) + g.add_edge(ri, prefilter) + + nx.draw_spectral(g, node_size=100, alpha=0.6, node_color='r', + font_size=10, node_shape='o') + plt.show() diff --git a/docs/source/development/notification_blueprint.txt b/docs/source/development/notification_blueprint.txt index 8b0dd75..c874de9 100644 --- a/docs/source/development/notification_blueprint.txt +++ b/docs/source/development/notification_blueprint.txt @@ -39,29 +39,38 @@ The notification center must: What's not included =================== -As written, the :mod:`IPython.kernel.core.notificaiton` module does not: +As written, the :mod:`IPython.kernel.core.notification` module does not: * Provide out-of-process or network notifications (these should be handled by a separate, Twisted aware module in :mod:`IPython.kernel`). -* Provide zope.interface-style interfaces for the notification system (these +* Provide zope.interface style interfaces for the notification system (these should also be provided by the :mod:`IPython.kernel` module). Use Cases ========= -The following use cases describe the main intended uses of the notificaiton module and illustrate the main success scenario for each use case: +The following use cases describe the main intended uses of the notification +module and illustrate the main success scenario for each use case: - 1. Dwight Schroot is writing a frontend for the IPython project. His frontend is stuck in the stone age and must communicate synchronously with an IPython.kernel.core.Interpreter instance. Because code is executed in blocks by the Interpreter, Dwight's UI freezes every time he executes a long block of code. To keep track of the progress of his long running block, Dwight adds the following code to his frontend's set-up code:: +Scenario 1 +---------- - from IPython.kernel.core.notification import NotificationCenter - center = NotificationCenter.sharedNotificationCenter - center.registerObserver(self, type=IPython.kernel.core.Interpreter.STDOUT_NOTIFICATION_TYPE, notifying_object=self.interpreter, callback=self.stdout_notification) - - and elsewhere in his front end:: +Dwight Schroot is writing a frontend for the IPython project. His frontend is +stuck in the stone age and must communicate synchronously with an +:mod:`IPython.kernel.core.Interpreter` instance. Because code is executed in blocks +by the Interpreter, Dwight's UI freezes every time he executes a long block of +code. To keep track of the progress of his long running block, Dwight adds the +following code to his frontend's set-up code:: + + from IPython.kernel.core.notification import NotificationCenter + center = NotificationCenter.sharedNotificationCenter + center.registerObserver(self, type=IPython.kernel.core.Interpreter.STDOUT_NOTIFICATION_TYPE, notifying_object=self.interpreter, callback=self.stdout_notification) + +and elsewhere in his front end:: - def stdout_notification(self, type, notifying_object, out_string=None): - self.writeStdOut(out_string) + def stdout_notification(self, type, notifying_object, out_string=None): + self.writeStdOut(out_string) If everything works, the Interpreter will (according to its published API) fire a notification via the @@ -78,6 +87,37 @@ Like magic, Dwight's frontend is able to provide output, even during long-running calculations. Now if Jim could just convince Dwight to use Twisted... - 2. Boss Hog is writing a frontend for the IPython project. Because Boss Hog is stuck in the stone age, his frontend will be written in a new Fortran-like dialect of python and will run only from the command line. Because he doesn't need any fancy notification system and is used to worrying about every cycle on his rat-wheel powered mini, Boss Hog is adamant that the new notification system not produce any performance penalty. As they say in Hazard county, there's no such thing as a free lunch. If he wanted zero overhead, he should have kept using IPython 0.8. Instead, those tricky Duke boys slide in a suped-up bridge-out jumpin' awkwardly confederate-lovin' notification module that imparts only a constant (and small) performance penalty when the Interpreter (or any other object) fires an event for which there are no registered observers. Of course, the same notificaiton-enabled Interpreter can then be used in frontends that require notifications, thus saving the IPython project from a nasty civil war. - - 3. Barry is wrting a frontend for the IPython project. Because Barry's front end is the *new hotness*, it uses an asynchronous event model to communicate with a Twisted :mod:`~IPython.kernel.engineservice` that communicates with the IPython :class:`~IPython.kernel.core.interpreter.Interpreter`. Using the :mod:`IPython.kernel.notification` module, an asynchronous wrapper on the :mod:`IPython.kernel.core.notification` module, Barry's frontend can register for notifications from the interpreter that are delivered asynchronously. Even if Barry's frontend is running on a separate process or even host from the Interpreter, the notifications are delivered, as if by dark and twisted magic. Just like Dwight's frontend, Barry's frontend can now recieve notifications of e.g. writing to stdout/stderr, opening/closing an external file, an exception in the executing code, etc. \ No newline at end of file +Scenario 2 +---------- + +Boss Hog is writing a frontend for the IPython project. Because Boss Hog is +stuck in the stone age, his frontend will be written in a new Fortran-like +dialect of python and will run only from the command line. Because he doesn't +need any fancy notification system and is used to worrying about every cycle +on his rat-wheel powered mini, Boss Hog is adamant that the new notification +system not produce any performance penalty. As they say in Hazard county, +there's no such thing as a free lunch. If he wanted zero overhead, he should +have kept using IPython 0.8. Instead, those tricky Duke boys slide in a +suped-up bridge-out jumpin' awkwardly confederate-lovin' notification module +that imparts only a constant (and small) performance penalty when the +Interpreter (or any other object) fires an event for which there are no +registered observers. Of course, the same notificaiton-enabled Interpreter can +then be used in frontends that require notifications, thus saving the IPython +project from a nasty civil war. + +Scenario 3 +---------- + +Barry is wrting a frontend for the IPython project. Because Barry's front end +is the *new hotness*, it uses an asynchronous event model to communicate with +a Twisted :mod:`IPython.kernel.engineservice` that communicates with the +IPython :class:`IPython.kernel.core.interpreter.Interpreter`. Using the +:mod:`IPython.kernel.notification` module, an asynchronous wrapper on the +:mod:`IPython.kernel.core.notification` module, Barry's frontend can register +for notifications from the interpreter that are delivered asynchronously. Even +if Barry's frontend is running on a separate process or even host from the +Interpreter, the notifications are delivered, as if by dark and twisted magic. +Just like Dwight's frontend, Barry's frontend can now receive notifications of +e.g. writing to stdout/stderr, opening/closing an external file, an exception +in the executing code, etc. + diff --git a/docs/source/development/overview.txt b/docs/source/development/overview.txt deleted file mode 100644 index 642fbbd..0000000 --- a/docs/source/development/overview.txt +++ /dev/null @@ -1,485 +0,0 @@ -.. _development: - -============================== -IPython development guidelines -============================== - - -Overview -======== - -This document describes IPython from the perspective of developers. Most -importantly, it gives information for people who want to contribute to the -development of IPython. So if you want to help out, read on! - -How to contribute to IPython -============================ - -IPython development is done using Bazaar [Bazaar]_ and Launchpad [Launchpad]_. -This makes it easy for people to contribute to the development of IPython. -There are several ways in which you can join in. - -If you have a small change that you want to send to the team, you can edit your -bazaar checkout of IPython (see below) in-place, and ask bazaar for the -differences:: - - $ cd /path/to/your/copy/of/ipython - $ bzr diff > my_fixes.diff - -This produces a patch file with your fixes, which we can apply to the source -tree. This file should then be attached to a ticket in our `bug tracker -`_, indicating what it does. - -This model of creating small, self-contained patches works very well and there -are open source projects that do their entire development this way. However, -in IPython we have found that for tracking larger changes, making use of -bazaar's full capabilities in conjunction with Launchpad's code hosting -services makes for a much better experience. - -Making your own branch of IPython allows you to refine your changes over time, -track the development of the main team, and propose your own full version of -the code for others to use and review, with a minimum amount of fuss. The next -parts of this document will explain how to do this. - -Install Bazaar and create a Launchpad account ---------------------------------------------- - -First make sure you have installed Bazaar (see their `website -`_). To see that Bazaar is installed and knows about -you, try the following:: - - $ bzr whoami - Joe Coder - -This should display your name and email. Next, you will want to create an -account on the `Launchpad website `_ and setup your -ssh keys. For more information of setting up your ssh keys, see `this link -`_. - -Get the main IPython branch from Launchpad ------------------------------------------- - -Now, you can get a copy of the main IPython development branch (we call this -the "trunk"):: - - $ bzr branch lp:ipython - -Create a working branch ------------------------ - -When working on IPython, you won't actually make edits directly to the -:file:`lp:ipython` branch. Instead, you will create a separate branch for your -changes. For now, let's assume you want to do your work in a branch named -"ipython-mybranch". Create this branch by doing:: - - $ bzr branch ipython ipython-mybranch - -When you actually create a branch, you will want to give it a name that -reflects the nature of the work that you will be doing in it, like -"install-docs-update". - -Make edits in your working branch ---------------------------------- - -Now you are ready to actually make edits in your :file:`ipython-mybranch` -branch. Before doing this, it is helpful to install this branch so you can -test your changes as you work. This is easiest if you have setuptools -installed. Then, just do:: - - $ cd ipython-mybranch - $ python setupegg.py develop - -Now, make some changes. After a while, you will want to commit your changes. -This let's Bazaar know that you like the changes you have made and gives you -an opportunity to keep a nice record of what you have done. This looks like -this:: - - $ ...do work in ipython-mybranch... - $ bzr commit -m "the commit message goes here" - -Please note that since we now don't use an old-style linear ChangeLog (that -tends to cause problems with distributed version control systems), you should -ensure that your log messages are reasonably detailed. Use a docstring-like -approach in the commit messages (including the second line being left -*blank*):: - - Single line summary of changes being committed. - - * more details when warranted ... - * including crediting outside contributors if they sent the - code/bug/idea! - -As you work, you will repeat this edit/commit cycle many times. If you work on -your branch for a long time, you will also want to get the latest changes from -the :file:`lp:ipython` branch. This can be done with the following sequence of -commands:: - - $ ls - ipython - ipython-mybranch - - $ cd ipython - $ bzr pull - $ cd ../ipython-mybranch - $ bzr merge ../ipython - $ bzr commit -m "Merging changes from trunk" - -Along the way, you should also run the IPython test suite. You can do this -using the :command:`iptest` command (which is basically a customized version of -:command:`nosetests`):: - - $ cd - $ iptest - -The :command:`iptest` command will also pick up and run any tests you have -written. See :ref:`_devel_testing` for further details on the testing system. - - -Post your branch and request a code review ------------------------------------------- - -Once you are done with your edits, you should post your branch on Launchpad so -that other IPython developers can review the changes and help you merge your -changes into the main development branch. To post your branch on Launchpad, -do:: - - $ cd ipython-mybranch - $ bzr push lp:~yourusername/ipython/ipython-mybranch - -Then, go to the `IPython Launchpad site `_, and you -should see your branch under the "Code" tab. If you click on your branch, you -can provide a short description of the branch as well as mark its status. Most -importantly, you should click the link that reads "Propose for merging into -another branch". What does this do? - -This let's the other IPython developers know that your branch is ready to be -reviewed and merged into the main development branch. During this review -process, other developers will give you feedback and help you get your code -ready to be merged. What types of things will we be looking for: - -* All code is documented. -* All code has tests. -* The entire IPython test suite passes. - -Once your changes have been reviewed and approved, someone will merge them -into the main development branch. - -Documentation -============= - -Standalone documentation ------------------------- - -All standalone documentation should be written in plain text (``.txt``) files -using reStructuredText [reStructuredText]_ for markup and formatting. All such -documentation should be placed in directory :file:`docs/source` of the IPython -source tree. The documentation in this location will serve as the main source -for IPython documentation and all existing documentation should be converted -to this format. - -To build the final documentation, we use Sphinx [Sphinx]_. Once you have -Sphinx installed, you can build the html docs yourself by doing:: - - $ cd ipython-mybranch/docs - $ make html - -Docstring format ----------------- - -Good docstrings are very important. All new code should have docstrings that -are formatted using reStructuredText for markup and formatting, since it is -understood by a wide variety of tools. Details about using reStructuredText -for docstrings can be found `here -`_. - -Additional PEPs of interest regarding documentation of code: - -* `Docstring Conventions `_ -* `Docstring Processing System Framework `_ -* `Docutils Design Specification `_ - - -Coding conventions -================== - -General -------- - -In general, we'll try to follow the standard Python style conventions as -described here: - -* `Style Guide for Python Code `_ - - -Other comments: - -* In a large file, top level classes and functions should be - separated by 2-3 lines to make it easier to separate them visually. -* Use 4 spaces for indentation. -* Keep the ordering of methods the same in classes that have the same - methods. This is particularly true for classes that implement an interface. - -Naming conventions ------------------- - -In terms of naming conventions, we'll follow the guidelines from the `Style -Guide for Python Code`_. - -For all new IPython code (and much existing code is being refactored), we'll -use: - -* All ``lowercase`` module names. - -* ``CamelCase`` for class names. - -* ``lowercase_with_underscores`` for methods, functions, variables and - attributes. - -There are, however, some important exceptions to these rules. In some cases, -IPython code will interface with packages (Twisted, Wx, Qt) that use other -conventions. At some level this makes it impossible to adhere to our own -standards at all times. In particular, when subclassing classes that use other -naming conventions, you must follow their naming conventions. To deal with -cases like this, we propose the following policy: - -* If you are subclassing a class that uses different conventions, use its - naming conventions throughout your subclass. Thus, if you are creating a - Twisted Protocol class, used Twisted's - ``namingSchemeForMethodsAndAttributes.`` - -* All IPython's official interfaces should use our conventions. In some cases - this will mean that you need to provide shadow names (first implement - ``fooBar`` and then ``foo_bar = fooBar``). We want to avoid this at all - costs, but it will probably be necessary at times. But, please use this - sparingly! - -Implementation-specific *private* methods will use -``_single_underscore_prefix``. Names with a leading double underscore will -*only* be used in special cases, as they makes subclassing difficult (such -names are not easily seen by child classes). - -Occasionally some run-in lowercase names are used, but mostly for very short -names or where we are implementing methods very similar to existing ones in a -base class (like ``runlines()`` where ``runsource()`` and ``runcode()`` had -established precedent). - -The old IPython codebase has a big mix of classes and modules prefixed with an -explicit ``IP``. In Python this is mostly unnecessary, redundant and frowned -upon, as namespaces offer cleaner prefixing. The only case where this approach -is justified is for classes which are expected to be imported into external -namespaces and a very generic name (like Shell) is too likely to clash with -something else. We'll need to revisit this issue as we clean up and refactor -the code, but in general we should remove as many unnecessary ``IP``/``ip`` -prefixes as possible. However, if a prefix seems absolutely necessary the more -specific ``IPY`` or ``ipy`` are preferred. - -.. _devel_testing: - -Testing system -============== - -It is extremely important that all code contributed to IPython has tests. -Tests should be written as unittests, doctests or as entities that the Nose -[Nose]_ testing package will find. Regardless of how the tests are written, we -will use Nose for discovering and running the tests. Nose will be required to -run the IPython test suite, but will not be required to simply use IPython. - -Tests of Twisted using code need to follow two additional guidelines: - -1. Twisted using tests should be written by subclassing the :class:`TestCase` - class that comes with :mod:`twisted.trial.unittest`. - -2. All :class:`Deferred` instances that are created in the test must be - properly chained and the final one *must* be the return value of the test - method. - -When these two things are done, Nose will be able to run the tests and the -twisted reactor will be handled correctly. - -Each subpackage in IPython should have its own :file:`tests` directory that -contains all of the tests for that subpackage. This allows each subpackage to -be self-contained. A good convention to follow is to have a file named -:file:`test_foo.py` for each module :file:`foo.py` in the package. This makes -it easy to organize the tests, though like most conventions, it's OK to break -it if logic and common sense dictate otherwise. - -If a subpackage has any dependencies beyond the Python standard library, the -tests for that subpackage should be skipped if the dependencies are not -found. This is very important so users don't get tests failing simply because -they don't have dependencies. We ship a set of decorators in the -:mod:`IPython.testing` package to tag tests that may be platform-specific or -otherwise may have restrictions; if the existing ones don't fit your needs, add -a new decorator in that location so other tests can reuse it. - -To run the IPython test suite, use the :command:`iptest` command that is -installed with IPython (if you are using IPython in-place, without installing -it, you can find this script in the :file:`scripts` directory):: - - $ iptest - -This command runs Nose with the proper options and extensions. By default, -:command:`iptest` runs the entire IPython test suite (skipping tests that may -be platform-specific or which depend on tools you may not have). But you can -also use it to run only one specific test file, or a specific test function. -For example, this will run only the :file:`test_magic` file from the test -suite:: - - $ iptest IPython.tests.test_magic - ---------------------------------------------------------------------- - Ran 10 tests in 0.348s - - OK (SKIP=3) - Deleting object: second_pass - -while the ``path:function`` syntax allows you to select a specific function in -that file to run:: - - $ iptest IPython.tests.test_magic:test_obj_del - ---------------------------------------------------------------------- - Ran 1 test in 0.204s - - OK - -Since :command:`iptest` is based on nosetests, you can pass it any regular -nosetests option. For example, you can use ``--pdb`` or ``--pdb-failures`` to -automatically activate the interactive Pdb debugger on errors or failures. See -the nosetests documentation for further details. - -.. warning:: - - Note that right now we have a nasty interaction between ipdoctest and - twisted. Until we figure this out, please use the following instructions to - ensure that at least you run all the tests. - -Right now, if you now run:: - - $ iptest [any options] [any submodules] - -it will NOT load ipdoctest but won't cause any Twisted problems. - -Once you're happy that you didn't break Twisted, run:: - - $ iptest --with-ipdoctest [any options] [any submodules] - -This MAY give a Twisted AlreadyCalledError exception at the end, but it will -also correctly load up all of the ipython-specific tests and doctests. - -The above can be made easier with a trivial shell alias:: - - $ alias iptest2='iptest --with-ipdoctest' - -So that you can run:: - - $ iptest ... - # Twisted happy - # iptest2 ... - # ignore possible Twisted error, this checks all the rest. - - -A few tips for writing tests ----------------------------- - -You can write tests either as normal test files, using all the conventions that -Nose recognizes, or as doctests. Note that *all* IPython functions should have -at least one example that serves as a doctest, whenever technically feasible. -However, example doctests should only be in the main docstring if they are *a -good example*, i.e. if they convey useful information about the function. If -you simply would like to write a test as a doctest, put it in a separate test -file and write a no-op function whose only purpose is its docstring. - -Note, however, that in a file named :file:`test_X`, functions whose only test -is their docstring (as a doctest) and which have no test functionality of their -own, should be called *doctest_foo* instead of *test_foo*, otherwise they get -double-counted (the empty function call is counted as a test, which just -inflates tests numbers artificially). This restriction does not apply to -functions in files with other names, due to how Nose discovers tests. - -You can use IPython examples in your docstrings. Those can make full use of -IPython functionality (magics, variable substitution, etc), but be careful to -keep them generic enough that they run identically on all Operating Systems. - -The prompts in your doctests can be either of the plain Python ``>>>`` variety -or ``In [1]:`` IPython style. Since this is the IPython system, after all, we -encourage you to use IPython prompts throughout, unless you are illustrating a -specific aspect of the normal prompts (such as the ``%doctest_mode`` magic). - -If a test isn't safe to run inside the main nose process (e.g. because it loads -a GUI toolkit), consider running it in a subprocess and capturing its output -for evaluation and test decision later. Here is an example of how to do it, by -relying on the builtin ``_ip`` object that contains the public IPython api as -defined in :mod:`IPython.ipapi`:: - - def test_obj_del(): - """Test that object's __del__ methods are called on exit.""" - test_dir = os.path.dirname(__file__) - del_file = os.path.join(test_dir,'obj_del.py') - out = _ip.IP.getoutput('ipython %s' % del_file) - nt.assert_equals(out,'object A deleted') - - - -If a doctest contains input whose output you don't want to verify identically -via doctest (random output, an object id, etc), you can mark a docstring with -``#random``. All of these test will have their code executed but no output -checking will be done:: - - >>> 1+3 - junk goes here... # random - - >>> 1+2 - again, anything goes #random - if multiline, the random mark is only needed once. - - >>> 1+2 - You can also put the random marker at the end: - # random - - >>> 1+2 - # random - .. or at the beginning. - -In a case where you want an *entire* docstring to be executed but not verified -(this only serves to check that the code runs without crashing, so it should be -used very sparingly), you can put ``# all-random`` in the docstring. - -.. _devel_config: - -Release checklist -================= - -Most of the release process is automated by the :file:`release` script in the -:file:`tools` directory. This is just a handy reminder for the release manager. - -#. Run the release script, which makes the tar.gz, eggs and Win32 .exe - installer. It posts them to the site and registers the release with PyPI. - -#. Updating the website with announcements and links to the updated - changes.txt in html form. Remember to put a short note both on the news - page of the site and on Launcphad. - -#. Drafting a short release announcement with i) highlights and ii) a link to - the html changes.txt. - -#. Make sure that the released version of the docs is live on the site. - -#. Celebrate! - -Porting to 3.0 -============== - -There are no definite plans for porting of IPython to python 3. The major -issue is the dependency on twisted framework for the networking/threading -stuff. It is possible that it the traditional IPython interactive console -could be ported more easily since it has no such dependency. Here are a few -things that will need to be considered when doing such a port especially -if we want to have a codebase that works directly on both 2.x and 3.x. - - 1. The syntax for exceptions changed (PEP 3110). The old - `except exc, var` changed to `except exc as var`. At last - count there was 78 occurences of this usage in the codebase - -.. [Bazaar] Bazaar. http://bazaar-vcs.org/ -.. [Launchpad] Launchpad. http://www.launchpad.net/ipython -.. [reStructuredText] reStructuredText. http://docutils.sourceforge.net/rst.html -.. [Sphinx] Sphinx. http://sphinx.pocoo.org/ -.. [Nose] Nose: a discovery based unittest extension. http://code.google.com/p/python-nose/ diff --git a/docs/source/development/release.txt b/docs/source/development/release.txt new file mode 100644 index 0000000..f857624 --- /dev/null +++ b/docs/source/development/release.txt @@ -0,0 +1,32 @@ +.. _releasing_ipython: + +================= +Releasing IPython +================= + +This section contains notes about the process that is used to release IPython. +Our release process is currently not very formal and could be improved. + +Most of the release process is automated by the :file:`release` script in the +:file:`tools` directory. This is just a handy reminder for the release manager. + +#. First, run :file:`build_release`, which does all the file checking and + building that the real release script will do. This will let you do test + installations, check that the build procedure runs OK, etc. You may want to + disable a few things like multi-version RPM building while testing, because + otherwise the build takes really long. + +#. Run the release script, which makes the tar.gz, eggs and Win32 .exe + installer. It posts them to the site and registers the release with PyPI. + +#. Update the website with announcements and links to the updated changes.txt + in html form. Remember to put a short note both on the news page of the + site and on Launcphad. + +#. Drafting a short release announcement with i) highlights and ii) a link to + the html version of the :ref:`Whats new ` section of the + documentation. + +#. Make sure that the released version of the docs is live on the site. + +#. Celebrate! \ No newline at end of file diff --git a/docs/source/development/reorg.txt b/docs/source/development/reorg.txt new file mode 100644 index 0000000..cd4b205 --- /dev/null +++ b/docs/source/development/reorg.txt @@ -0,0 +1,71 @@ +.. _module_reorg: + +=========================== +IPython module organization +=========================== + +As of the 0.11 release of IPython, the top-level packages and modules have +been completely reorganized. This section describes the purpose of the +top-level IPython subpackages. + +Subpackage descriptions +======================= + +* :mod:`IPython.config`. This package contains the configuration system of + IPython, as well as default configuration files for the different IPython + applications. + +* :mod:`IPython.core`. This sub-package contains the core of the IPython + interpreter, but none of its extended capabilities. + +* :mod:`IPython.deathrow`. This is for code that is outdated, untested, + rotting, or that belongs in a separate third party project. Eventually all + this code will either i) be revived by someone willing to maintain it with + tests and docs and re-included into IPython or 2) be removed from IPython + proper, but put into a separate third-party Python package. No new code will + be allowed here. If your favorite extension has been moved here please + contact the IPython developer mailing list to help us determine the best + course of action. + +* :mod:`IPython.extensions`. This package contains fully supported IPython + extensions. These extensions adhere to the official IPython extension API + and can be enabled by adding them to a field in the configuration file. + If your extension is no longer in this location, please look in + :mod:`IPython.quarantine` and :mod:`IPython.deathrow` and contact the + IPython developer mailing list. + +* :mod:`IPython.external`. This package contains third party packages and + modules that IPython ships internally to reduce the number of dependencies. + Usually, these are short, single file modules. + +* :mod:`IPython.frontend`. This package contains the various IPython + frontends. Currently, the code in this subpackage is very experimental and + may be broken. + +* :mod:`IPython.gui`. Another semi-experimental wxPython based IPython GUI. + +* :mod:`IPython.kernel`. This contains IPython's parallel computing system. + +* :mod:`IPython.lib`. IPython has many extended capabilities that are not part + of the IPython core. These things will go here and in. Modules in this + package are similar to extensions, but don't adhere to the official + IPython extension API. + +* :mod:`IPython.quarantine`. This is for code that doesn't meet IPython's + standards, but that we plan on keeping. To be moved out of this sub-package + a module needs to have approval of the core IPython developers, tests and + documentation. If your favorite extension has been moved here please contact + the IPython developer mailing list to help us determine the best course of + action. + +* :mod:`IPython.scripts`. This package contains a variety of top-level + command line scripts. Eventually, these should be moved to the + :file:`scripts` subdirectory of the appropriate IPython subpackage. + +* :mod:`IPython.utils`. This sub-package will contain anything that might + eventually be found in the Python standard library, like things in + :mod:`genutils`. Each sub-module in this sub-package should contain + functions and classes that serve a single purpose and that don't + depend on things in the rest of IPython. + + diff --git a/docs/source/development/roadmap.txt b/docs/source/development/roadmap.txt index 2a097a2..d7912ce 100644 --- a/docs/source/development/roadmap.txt +++ b/docs/source/development/roadmap.txt @@ -4,42 +4,37 @@ Development roadmap =================== -IPython is an ambitious project that is still under heavy development. However, we want IPython to become useful to as many people as possible, as quickly as possible. To help us accomplish this, we are laying out a roadmap of where we are headed and what needs to happen to get there. Hopefully, this will help the IPython developers figure out the best things to work on for each upcoming release. +IPython is an ambitious project that is still under heavy development. +However, we want IPython to become useful to as many people as possible, as +quickly as possible. To help us accomplish this, we are laying out a roadmap +of where we are headed and what needs to happen to get there. Hopefully, this +will help the IPython developers figure out the best things to work on for +each upcoming release. Work targeted to particular releases ==================================== -Release 0.10 ------------- - -* Initial refactor of :command:`ipcluster`. - -* Better TextMate integration. - -* Merge in the daemon branch. - Release 0.11 ------------ -* Refactor the configuration system and command line options for - :command:`ipengine` and :command:`ipcontroller`. This will include the - creation of cluster directories that encapsulate all the configuration - files, log files and security related files for a particular cluster. +* Full module and package reorganization (done). + +* Removal of the threaded shells and new implementation of GUI support + based on ``PyOSInputHook`` (done). -* Refactor :command:`ipcluster` to support the new configuration system. +* Refactor the configuration system (done). -* Refactor the daemon stuff to support the new configuration system. +* Prepare to refactor IPython's core by creating a new component and + application system (done). -* Merge back in the core of the notebook. +* Start to refactor IPython's core by turning everything into components + (started). Release 0.12 ------------ -* Fully integrate process startup with the daemons for full process - management. +* Continue to refactor IPython's core by turning everything into components. -* Make the capabilites of :command:`ipcluster` available from simple Python - classes. Major areas of work =================== @@ -47,22 +42,49 @@ Major areas of work Refactoring the main IPython core --------------------------------- +During the summer of 2009, we began refactoring IPython's core. The main +thrust in this work was to make the IPython core into a set of loosely coupled +components. The base component class for this is +:class:`IPython.core.component.Component`. This section outlines the status of +this work. + +Parts of the IPython core that have been turned into components: + +* The main :class:`~IPython.core.iplib.InteractiveShell` class. +* The aliases (:mod:`IPython.core.alias`). +* The display and builtin traps (:mod:`IPython.core.display_trap` and + :mod:`IPython.core.builtin_trap`). +* The prefilter machinery (:mod:`IPython.core.prefilter`). + +Parts of the IPythoncore that still need to be turned into components: + +* Magics. +* Input and output history management. +* Prompts. +* Tab completers. +* Logging. +* Exception handling. +* Anything else. + Process management for :mod:`IPython.kernel` -------------------------------------------- -Configuration system --------------------- +A number of things need to be done to improve how processes are started +up and managed for the parallel computing side of IPython: + +* All of the processes need to use the new configuration system, components + and application. +* We need to add support for other batch systems. Performance problems -------------------- -Currently, we have a number of performance issues that are waiting to bite users: +Currently, we have a number of performance issues in :mod:`IPython.kernel`: * The controller stores a large amount of state in Python dictionaries. Under heavy usage, these dicts with get very large, causing memory usage problems. - We need to develop more scalable solutions to this problem, such as using a - sqlite database to store this state. This will also help the controller to - be more fault tolerant. + We need to develop more scalable solutions to this problem. This will also + help the controller to be more fault tolerant. * We currently don't have a good way of handling large objects in the controller. The biggest problem is that because we don't have any way of @@ -77,5 +99,23 @@ Currently, we have a number of performance issues that are waiting to bite users separate the controller itself into multiple processes, one for the core controller and one each for the controller interfaces. - - +Porting to 3.0 +============== + +There are no definite plans for porting of IPython to Python 3. The major +issue is the dependency on Twisted framework for the networking/threading +stuff. It is possible that it the traditional IPython interactive console +could be ported more easily since it has no such dependency. Here are a few +things that will need to be considered when doing such a port especially +if we want to have a codebase that works directly on both 2.x and 3.x. + +1. The syntax for exceptions changed (PEP 3110). The old `except exc, var` + changed to `except exc as var`. At last count there was 78 occurrences of this + usage in the code base. This is a particularly problematic issue, because it's + not easy to implement it in a 2.5-compatible way. + +Because it is quite difficult to support simultaneously Python 2.5 and 3.x, we +will likely at some point put out a release that requires strictly 2.6 and +abandons 2.5 compatibility. This will then allow us to port the code to using +:func:`print` as a function, `except exc as var` syntax, etc. But as of +version 0.11 at least, we will retain Python 2.5 compatibility. diff --git a/docs/source/development/testing.txt b/docs/source/development/testing.txt new file mode 100644 index 0000000..1a5971f --- /dev/null +++ b/docs/source/development/testing.txt @@ -0,0 +1,54 @@ +.. _testing: + +========================= +Writing and running tests +========================= + +Overview +======== + +It is extremely important that all code contributed to IPython has tests. +Tests should be written as unittests, doctests or other entities that the +IPython test system can detect. See below for more details on this. + +Each subpackage in IPython should have its own :file:`tests` directory that +contains all of the tests for that subpackage. All of the files in the +:file:`tests` directory should have the word "tests" in them to enable +the testing framework to find them. + +If a subpackage has any dependencies beyond the Python standard library, the +tests for that subpackage should be skipped if the dependencies are not found. +This is very important so users don't get tests failing simply because they +don't have dependencies. We are still figuring out the best way for this +to be handled. + +Status +====== + +Currently IPython's testing system is being reworked. In the meantime, +we recommend the following testing practices: + +* To run regular tests, use the :command:`nosetests` command that Nose [Nose]_ + provides on a per file basis: + +.. code-block:: bash + + nosetests -vvs IPython.core.tests.test_component + +* To run Twisted-using tests, use the :command:`trial` command on a per file + basis: + +.. code-block:: bash + + trial IPython.kernel + +* For now, regular tests (of non-Twisted using code) should be written as + unit tests. They should be subclasses of :class:`unittest.TestCase`. + +* Tests of Twisted [Twisted]_ using code should be written by subclassing the + ``TestCase`` class that comes with ``twisted.trial.unittest``. Furthermore, + all :class:`Deferred` instances that are created in the test must be + properly chained and the final one *must* be the return value of the test + method. + +.. [Nose] Nose: a discovery based unittest extension. http://code.google.com/p/python-nose/ diff --git a/docs/source/faq.txt b/docs/source/faq.txt index 321cb06..60dc3bf 100644 --- a/docs/source/faq.txt +++ b/docs/source/faq.txt @@ -91,15 +91,3 @@ handling the data movement. Here are some ideas: 5. See if you can pass data directly between engines using MPI. -Isn't Python slow to be used for high-performance parallel computing? ---------------------------------------------------------------------- - - - - - - - - - - diff --git a/docs/source/index.txt b/docs/source/index.txt index 2180bc9..243bdec 100644 --- a/docs/source/index.txt +++ b/docs/source/index.txt @@ -7,25 +7,27 @@ IPython Documentation :Release: |release| :Date: |today| - Contents: +Welcome to the official IPython documentation. + +Contents +======== .. toctree:: - :maxdepth: 2 + :maxdepth: 1 overview.txt + whatsnew/index.txt install/index.txt interactive/index.txt parallel/index.txt config/index.txt - faq.txt - history.txt - changes.txt development/index.txt api/index.txt - license_and_copyright.txt - credits.txt + faq.txt + about/index.txt .. htmlonly:: * :ref:`genindex` * :ref:`modindex` * :ref:`search` + diff --git a/docs/source/install/index.txt b/docs/source/install/index.txt index f6cd618..9879894 100644 --- a/docs/source/install/index.txt +++ b/docs/source/install/index.txt @@ -8,3 +8,4 @@ Installation :maxdepth: 2 install.txt + diff --git a/docs/source/install/install.txt b/docs/source/install/install.txt index 43377b3..20187d0 100644 --- a/docs/source/install/install.txt +++ b/docs/source/install/install.txt @@ -9,14 +9,16 @@ install all of its dependencies. Please let us know if you have problems installing IPython or any of its -dependencies. IPython requires Python version 2.4 or greater. Light testing -has been done on version 2.6, and so far everything looks fine. We have *not* -yet started to port IPython to Python 3.0, where the language changes are much -more significant. +dependencies. Officially, IPython requires Python version 2.5 or 2.6. We +have *not* yet started to port IPython to Python 3.0. .. warning:: - IPython will not work with Python 2.4 or below. + Officially, IPython supports Python versions 2.5 and 2.6. + + IPython 0.10 has only been well tested with Python 2.5 and 2.6. Parts of + it may work with Python 2.4, but we do not officially support Python 2.4 + anymore. If you need to use 2.4, you can still run IPython 0.9. Some of the installation approaches use the :mod:`setuptools` package and its :command:`easy_install` command line program. In many scenarios, this provides @@ -32,14 +34,25 @@ Quickstart If you have :mod:`setuptools` installed and you are on OS X or Linux (not Windows), the following will download and install IPython *and* the main -optional dependencies:: +optional dependencies: + +.. code-block:: bash $ easy_install ipython[kernel,security,test] This will get Twisted, zope.interface and Foolscap, which are needed for IPython's parallel computing features as well as the nose package, which will -enable you to run IPython's test suite. To run IPython's test suite, use the -:command:`iptest` command:: +enable you to run IPython's test suite. + +.. warning:: + + IPython's test system is being refactored and currently the + :command:`iptest` shown below does not work. More details about the + testing situation can be found :ref:`here ` + +To run IPython's test suite, use the :command:`iptest` command: + +.. code-block:: bash $ iptest @@ -60,9 +73,11 @@ Installation using easy_install ------------------------------- If you have :mod:`setuptools` installed, the easiest way of getting IPython is -to simple use :command:`easy_install`:: +to simple use :command:`easy_install`: - $ easy_install ipython +.. code-block:: bash + + $ easy_install ipython That's it. @@ -71,11 +86,13 @@ Installation from source If you don't want to use :command:`easy_install`, or don't have it installed, just grab the latest stable build of IPython from `here -`_. Then do the following:: +`_. Then do the following: + +.. code-block:: bash - $ tar -xzf ipython.tar.gz - $ cd ipython - $ python setup.py install + $ tar -xzf ipython.tar.gz + $ cd ipython + $ python setup.py install If you are installing to a location (like ``/usr/local``) that requires higher permissions, you may need to run the last command with :command:`sudo`. @@ -85,8 +102,8 @@ Windows There are a few caveats for Windows users. The main issue is that a basic ``python setup.py install`` approach won't create ``.bat`` file or Start Menu -shortcuts, which most users want. To get an installation with these, there are -two choices: +shortcuts, which most users want. To get an installation with these, you can +use any of the following alternatives: 1. Install using :command:`easy_install`. @@ -96,31 +113,47 @@ two choices: 3. Install from source, but using :mod:`setuptools` (``python setupegg.py install``). +IPython by default runs in a terminal window, but the normal terminal +application supplied by Microsoft Windows is very primitive. You may want to +download the excellent and free Console_ application instead, which is a far +superior tool. You can even configure Console to give you by default an +IPython tab, which is very convenient to create new IPython sessions directly +from the working terminal. + +.. _Console: http://sourceforge.net/projects/console + + Installing the development version ---------------------------------- It is also possible to install the development version of IPython from our `Bazaar `_ source code repository. To do this you will -need to have Bazaar installed on your system. Then just do:: +need to have Bazaar installed on your system. Then just do: - $ bzr branch lp:ipython - $ cd ipython - $ python setup.py install +.. code-block:: bash + + $ bzr branch lp:ipython + $ cd ipython + $ python setup.py install Again, this last step on Windows won't create ``.bat`` files or Start Menu shortcuts, so you will have to use one of the other approaches listed above. Some users want to be able to follow the development branch as it changes. If you have :mod:`setuptools` installed, this is easy. Simply replace the last -step by:: +step by: + +.. code-block:: bash - $ python setupegg.py develop + $ python setupegg.py develop This creates links in the right places and installs the command line script to the appropriate places. Then, if you want to update your IPython at any time, -just do:: +just do: + +.. code-block:: bash - $ bzr pull + $ bzr pull Basic optional dependencies =========================== @@ -153,11 +186,13 @@ many of the issues related to the differences between readline and libedit have been resolved. For many users, libedit may be sufficient. Most users on OS X will want to get the full :mod:`readline` module. To get a -working :mod:`readline` module, just do (with :mod:`setuptools` installed):: +working :mod:`readline` module, just do (with :mod:`setuptools` installed): - $ easy_install readline +.. code-block:: bash -.. note: + $ easy_install readline + +.. note:: Other Python distributions on OS X (such as fink, MacPorts and the official python.org binaries) already have readline installed so @@ -178,28 +213,41 @@ nose To run the IPython test suite you will need the :mod:`nose` package. Nose provides a great way of sniffing out and running all of the IPython tests. The -simplest way of getting nose, is to use :command:`easy_install`:: +simplest way of getting nose, is to use :command:`easy_install`: + +.. code-block:: bash - $ easy_install nose + $ easy_install nose -Another way of getting this is to do:: +Another way of getting this is to do: + +.. code-block:: bash $ easy_install ipython[test] For more installation options, see the `nose website -`_. Once you have nose -installed, you can run IPython's test suite using the iptest command:: +`_. - $ iptest +.. warning:: + As described above, the :command:`iptest` command currently doesn't work. + +Once you have nose installed, you can run IPython's test suite using the +iptest command: + +.. code-block:: bash + + $ iptest pexpect ------- The `pexpect `_ package is used in IPython's -:command:`irunner` script. On Unix platforms (including OS X), just do:: +:command:`irunner` script. On Unix platforms (including OS X), just do: + +.. code-block:: bash - $ easy_install pexpect + $ easy_install pexpect Windows users are out of luck as pexpect does not run there. @@ -215,7 +263,10 @@ features require a number of additional packages: * Foolscap (a nice, secure network protocol) * pyOpenSSL (security for network connections) -On a Unix style platform (including OS X), if you want to use :mod:`setuptools`, you can just do:: +On a Unix style platform (including OS X), if you want to use +:mod:`setuptools`, you can just do: + +.. code-block:: bash $ easy_install ipython[kernel] # the first three $ easy_install ipython[security] # pyOpenSSL @@ -225,24 +276,30 @@ zope.interface and Twisted Twisted [Twisted]_ and zope.interface [ZopeInterface]_ are used for networking related things. On Unix style platforms (including OS X), the simplest way of -getting the these is to use :command:`easy_install`:: +getting the these is to use :command:`easy_install`: - $ easy_install zope.interface - $ easy_install Twisted +.. code-block:: bash -Of course, you can also download the source tarballs from the `Twisted website -`_ and the `zope.interface page at PyPI -`_ and do the usual ``python -setup.py install`` if you prefer. + $ easy_install zope.interface + $ easy_install Twisted -Windows is a bit different. For zope.interface and Twisted, simply get the latest binary ``.exe`` installer from the Twisted website. This installer includes both zope.interface and Twisted and should just work. +Of course, you can also download the source tarballs from the Twisted website +[Twisted]_ and the +`zope.interface page at PyPI `_ +and do the usual ``python setup.py install`` if you prefer. + +Windows is a bit different. For zope.interface and Twisted, simply get the +latest binary ``.exe`` installer from the Twisted website. This installer +includes both zope.interface and Twisted and should just work. Foolscap -------- Foolscap [Foolscap]_ uses Twisted to provide a very nice secure RPC protocol that we use to implement our parallel computing features. -On all platforms a simple:: +On all platforms a simple: + +.. code-block:: bash $ easy_install foolscap @@ -253,14 +310,16 @@ if you prefer. pyOpenSSL --------- -IPython requires an older version of pyOpenSSL [pyOpenSSL]_ (0.6 rather than -the current 0.7). There are a couple of options for getting this: +IPython does not work with version 0.7 of pyOpenSSL [pyOpenSSL]_. It is known +to work with version 0.6 and will likely work with the more recent 0.8 and 0.9 +versions. There are a couple of options for getting this: -1. Most Linux distributions have packages for pyOpenSSL. -2. The built-in Python 2.5 on OS X 10.5 already has it installed. -3. There are source tarballs on the pyOpenSSL website. On Unix-like - platforms, these can be built using ``python seutp.py install``. -4. There is also a binary ``.exe`` Windows installer on the `pyOpenSSL website `_. +1. Most Linux distributions have packages for pyOpenSSL. +2. The built-in Python 2.5 on OS X 10.5 already has it installed. +3. There are source tarballs on the pyOpenSSL website. On Unix-like + platforms, these can be built using ``python seutp.py install``. +4. There is also a binary ``.exe`` Windows installer on the + `pyOpenSSL website `_. Dependencies for IPython.frontend (the IPython GUI) =================================================== @@ -268,14 +327,15 @@ Dependencies for IPython.frontend (the IPython GUI) wxPython -------- -Starting with IPython 0.9, IPython has a new IPython.frontend package that has -a nice wxPython based IPython GUI. As you would expect, this GUI requires -wxPython. Most Linux distributions have wxPython packages available and the -built-in Python on OS X comes with wxPython preinstalled. For Windows, a -binary installer is available on the `wxPython website +Starting with IPython 0.9, IPython has a new :mod:`IPython.frontend` package +that has a nice wxPython based IPython GUI. As you would expect, this GUI +requires wxPython. Most Linux distributions have wxPython packages available +and the built-in Python on OS X comes with wxPython preinstalled. For Windows, +a binary installer is available on the `wxPython website `_. .. [Twisted] Twisted matrix. http://twistedmatrix.org .. [ZopeInterface] http://pypi.python.org/pypi/zope.interface .. [Foolscap] Foolscap network protocol. http://foolscap.lothar.com/trac .. [pyOpenSSL] pyOpenSSL. http://pyopenssl.sourceforge.net + diff --git a/docs/source/interactive/extension_api.txt b/docs/source/interactive/extension_api.txt deleted file mode 100644 index ecef5e3..0000000 --- a/docs/source/interactive/extension_api.txt +++ /dev/null @@ -1,252 +0,0 @@ -===================== -IPython extension API -===================== - -IPython api (defined in IPython/ipapi.py) is the public api that -should be used for - - * Configuration of user preferences (.ipython/ipy_user_conf.py) - * Creating new profiles (.ipython/ipy_profile_PROFILENAME.py) - * Writing extensions - -Note that by using the extension api for configuration (editing -ipy_user_conf.py instead of ipythonrc), you get better validity checks -and get richer functionality - for example, you can import an -extension and call functions in it to configure it for your purposes. - -For an example extension (the 'sh' profile), see -IPython/Extensions/ipy_profile_sh.py. - -For the last word on what's available, see the source code of -IPython/ipapi.py. - - -Getting started -=============== - -If you want to define an extension, create a normal python module that -can be imported. The module will access IPython functionality through -the 'ip' object defined below. - -If you are creating a new profile (e.g. foobar), name the module as -'ipy_profile_foobar.py' and put it in your ~/.ipython directory. Then, -when you start ipython with the '-p foobar' argument, the module is -automatically imported on ipython startup. - -If you are just doing some per-user configuration, you can either - - * Put the commands directly into ipy_user_conf.py. - - * Create a new module with your customization code and import *that* - module in ipy_user_conf.py. This is preferable to the first approach, - because now you can reuse and distribute your customization code. - -Getting a handle to the api -=========================== - -Put this in the start of your module:: - - #!python - import IPython.ipapi - ip = IPython.ipapi.get() - -The 'ip' object will then be used for accessing IPython -functionality. 'ip' will mean this api object in all the following -code snippets. The same 'ip' that we just acquired is always -accessible in interactive IPython sessions by the name _ip - play with -it like this:: - - [~\_ipython]|81> a = 10 - [~\_ipython]|82> _ip.e - _ip.ev _ip.ex _ip.expose_magic - [~\_ipython]|82> _ip.ev('a+13') - <82> 23 - -The _ip object is also used in some examples in this document - it can -be substituted by 'ip' in non-interactive use. - -Changing options -================ - -The ip object has 'options' attribute that can be used te get/set -configuration options (just as in the ipythonrc file):: - - o = ip.options - o.autocall = 2 - o.automagic = 1 - -Executing statements in IPython namespace with 'ex' and 'ev' -============================================================ - -Often, you want to e.g. import some module or define something that -should be visible in IPython namespace. Use ``ip.ev`` to -*evaluate* (calculate the value of) expression and ``ip.ex`` to -'''execute''' a statement:: - - # path module will be visible to the interactive session - ip.ex("from path import path" ) - - # define a handy function 'up' that changes the working directory - - ip.ex('import os') - ip.ex("def up(): os.chdir('..')") - - - # _i2 has the input history entry #2, print its value in uppercase. - print ip.ev('_i2.upper()') - -Accessing the IPython namespace -=============================== - -ip.user_ns attribute has a dictionary containing the IPython global -namespace (the namespace visible in the interactive session). - -:: - - [~\_ipython]|84> tauno = 555 - [~\_ipython]|85> _ip.user_ns['tauno'] - <85> 555 - -Defining new magic commands -=========================== - -The following example defines a new magic command, %impall. What the -command does should be obvious:: - - def doimp(self, arg): - ip = self.api - ip.ex("import %s; reload(%s); from %s import *" % ( - arg,arg,arg) - ) - - ip.expose_magic('impall', doimp) - -Things to observe in this example: - - * Define a function that implements the magic command using the - ipapi methods defined in this document - * The first argument of the function is 'self', i.e. the - interpreter object. It shouldn't be used directly. however. - The interpreter object is probably *not* going to remain stable - through IPython versions. - * Access the ipapi through 'self.api' instead of the global 'ip' object. - * All the text following the magic command on the command line is - contained in the second argument - * Expose the magic by ip.expose_magic() - - -Calling magic functions and system commands -=========================================== - -Use ip.magic() to execute a magic function, and ip.system() to execute -a system command:: - - # go to a bookmark - ip.magic('%cd -b relfiles') - - # execute 'ls -F' system command. Interchangeable with os.system('ls'), really. - ip.system('ls -F') - -Launching IPython instance from normal python code -================================================== - -Use ipapi.launch_new_instance() with an argument that specifies the -namespace to use. This can be useful for trivially embedding IPython -into your program. Here's an example of normal python program test.py -('''without''' an existing IPython session) that launches an IPython -interpreter and regains control when the interpreter is exited:: - - [ipython]|1> cat test.py - my_ns = dict( - kissa = 15, - koira = 16) - import IPython.ipapi - print "launching IPython instance" - IPython.ipapi.launch_new_instance(my_ns) - print "Exited IPython instance!" - print "New vals:",my_ns['kissa'], my_ns['koira'] - -And here's what it looks like when run (note how we don't start it -from an ipython session):: - - Q:\ipython>python test.py - launching IPython instance - Py 2.5 (r25:51908, Sep 19 2006, 09:52:17) [MSC v.1310 32 bit (Intel)] IPy 0.7.3b3.r1975 - [ipython]|1> kissa = 444 - [ipython]|2> koira = 555 - [ipython]|3> Exit - Exited IPython instance! - New vals: 444 555 - -Accessing unexposed functionality -================================= - -There are still many features that are not exposed via the ipapi. If -you can't avoid using them, you can use the functionality in -InteractiveShell object (central IPython session class, defined in -iplib.py) through ip.IP. - -For example:: - - [~]|7> _ip.IP.expand_aliases('np','myfile.py') - <7> 'c:/opt/Notepad++/notepad++.exe myfile.py' - [~]|8> - -Still, it's preferable that if you encounter such a feature, contact -the IPython team and request that the functionality be exposed in a -future version of IPython. Things not in ipapi are more likely to -change over time. - -Provided extensions -=================== - -You can see the list of available extensions (and profiles) by doing -``import ipy_``. Some extensions don't have the ``ipy_`` prefix in -module name, so you may need to see the contents of IPython/Extensions -folder to see what's available. - -You can see a brief documentation of an extension by looking at the -module docstring:: - - [c:p/ipython_main]|190> import ipy_fsops - [c:p/ipython_main]|191> ipy_fsops? - - ... - - Docstring: - File system operations - - Contains: Simple variants of normal unix shell commands (icp, imv, irm, - imkdir, igrep). - -You can also install your own extensions - the recommended way is to -just copy the module to ~/.ipython. Extensions are typically enabled -by just importing them (e.g. in ipy_user_conf.py), but some extensions -require additional steps, for example:: - - [c:p]|192> import ipy_traits_completer - [c:p]|193> ipy_traits_completer.activate() - -Note that extensions, even if provided in the stock IPython -installation, are not guaranteed to have the same requirements as the -rest of IPython - an extension may require external libraries or a -newer version of Python than what IPython officially requires. An -extension may also be under a more restrictive license than IPython -(e.g. ipy_bzr is under GPL). - -Just for reference, the list of bundled extensions at the time of -writing is below: - -astyle.py clearcmd.py envpersist.py ext_rescapture.py ibrowse.py -igrid.py InterpreterExec.py InterpreterPasteInput.py ipipe.py -ipy_app_completers.py ipy_autoreload.py ipy_bzr.py ipy_completers.py -ipy_constants.py ipy_defaults.py ipy_editors.py ipy_exportdb.py -ipy_extutil.py ipy_fsops.py ipy_gnuglobal.py ipy_kitcfg.py -ipy_legacy.py ipy_leo.py ipy_p4.py ipy_profile_doctest.py -ipy_profile_none.py ipy_profile_scipy.py ipy_profile_sh.py -ipy_profile_zope.py ipy_pydb.py ipy_rehashdir.py ipy_render.py -ipy_server.py ipy_signals.py ipy_stock_completers.py -ipy_system_conf.py ipy_traits_completer.py ipy_vimserver.py -ipy_which.py ipy_workdir.py jobctrl.py ledit.py numeric_formats.py -PhysicalQInput.py PhysicalQInteractive.py pickleshare.py -pspersistence.py win32clip.py __init__.py \ No newline at end of file diff --git a/docs/source/interactive/index.txt b/docs/source/interactive/index.txt index ae45bc5..7e398ef 100644 --- a/docs/source/interactive/index.txt +++ b/docs/source/interactive/index.txt @@ -8,4 +8,5 @@ Using IPython for interactive work tutorial.txt reference.txt shell.txt - extension_api.txt + + diff --git a/docs/source/interactive/reference.txt b/docs/source/interactive/reference.txt index 023a5ec..584789d 100644 --- a/docs/source/interactive/reference.txt +++ b/docs/source/interactive/reference.txt @@ -2,6 +2,14 @@ IPython reference ================= +.. warning:: + + As of the 0.11 version of IPython, some of the features and APIs + described in this section have been deprecated or are broken. Our plan + is to continue to support these features, but they need to be updated + to take advantage of recent API changes. Furthermore, this section + of the documentation need to be updated to reflect all of these changes. + .. _command_line_options: Command-line usage @@ -23,73 +31,18 @@ your ipythonrc configuration file for details on those. This file typically installed in the $HOME/.ipython directory. For Windows users, $HOME resolves to C:\\Documents and Settings\\YourUserName in most instances. In the rest of this text, we will refer to this directory as -IPYTHONDIR. +IPYTHON_DIR. -.. _Threading options: Special Threading Options ------------------------- -The following special options are ONLY valid at the beginning of the -command line, and not later. This is because they control the initial- -ization of ipython itself, before the normal option-handling mechanism -is active. - - -gthread, -qthread, -q4thread, -wthread, -pylab: - Only one of these can be given, and it can only be given as - the first option passed to IPython (it will have no effect in - any other position). They provide threading support for the - GTK, Qt (versions 3 and 4) and WXPython toolkits, and for the - matplotlib library. - - With any of the first four options, IPython starts running a - separate thread for the graphical toolkit's operation, so that - you can open and control graphical elements from within an - IPython command line, without blocking. All four provide - essentially the same functionality, respectively for GTK, Qt3, - Qt4 and WXWidgets (via their Python interfaces). - - Note that with -wthread, you can additionally use the - -wxversion option to request a specific version of wx to be - used. This requires that you have the wxversion Python module - installed, which is part of recent wxPython distributions. - - If -pylab is given, IPython loads special support for the mat - plotlib library (http://matplotlib.sourceforge.net), allowing - interactive usage of any of its backends as defined in the - user's ~/.matplotlib/matplotlibrc file. It automatically - activates GTK, Qt or WX threading for IPyhton if the choice of - matplotlib backend requires it. It also modifies the %run - command to correctly execute (without blocking) any - matplotlib-based script which calls show() at the end. - - -tk - The -g/q/q4/wthread options, and -pylab (if matplotlib is - configured to use GTK, Qt3, Qt4 or WX), will normally block Tk - graphical interfaces. This means that when either GTK, Qt or WX - threading is active, any attempt to open a Tk GUI will result in a - dead window, and possibly cause the Python interpreter to crash. - An extra option, -tk, is available to address this issue. It can - only be given as a second option after any of the above (-gthread, - -wthread or -pylab). - - If -tk is given, IPython will try to coordinate Tk threading - with GTK, Qt or WX. This is however potentially unreliable, and - you will have to test on your platform and Python configuration to - determine whether it works for you. Debian users have reported - success, apparently due to the fact that Debian builds all of Tcl, - Tk, Tkinter and Python with pthreads support. Under other Linux - environments (such as Fedora Core 2/3), this option has caused - random crashes and lockups of the Python interpreter. Under other - operating systems (Mac OSX and Windows), you'll need to try it to - find out, since currently no user reports are available. - - There is unfortunately no way for IPython to determine at run time - whether -tk will work reliably or not, so you will need to do some - experiments before relying on it for regular work. - - +Previously IPython had command line options for controlling GUI event loop +integration (-gthread, -qthread, -q4thread, -wthread, -pylab). As of IPython +version 0.11, these have been deprecated. Please see the new ``%gui`` +magic command or :ref:`this section ` for details on the new +interface. Regular Options --------------- @@ -109,16 +62,8 @@ All options with a [no] prepended can be specified in negated form -help print a help message and exit. -pylab - this can only be given as the first option passed to IPython - (it will have no effect in any other position). It adds - special support for the matplotlib library - (http://matplotlib.sourceforge.ne), allowing interactive usage - of any of its backends as defined in the user's .matplotlibrc - file. It automatically activates GTK or WX threading for - IPyhton if the choice of matplotlib backend requires it. It - also modifies the %run command to correctly execute (without - blocking) any matplotlib-based script which calls show() at - the end. See `Matplotlib support`_ for more details. + Deprecated. See :ref:`Matplotlib support ` + for more details. -autocall Make IPython automatically call any callable object even if you @@ -205,9 +150,9 @@ All options with a [no] prepended can be specified in negated form something like Emacs). -ipythondir - name of your IPython configuration directory IPYTHONDIR. This + name of your IPython configuration directory IPYTHON_DIR. This can also be specified through the environment variable - IPYTHONDIR. + IPYTHON_DIR. -log, l generate a log file of all input. The file is named @@ -266,10 +211,10 @@ All options with a [no] prepended can be specified in negated form assume that your config file is ipythonrc- or ipy_profile_.py (looks in current dir first, then in - IPYTHONDIR). This is a quick way to keep and load multiple + IPYTHON_DIR). This is a quick way to keep and load multiple config files for different tasks, especially if you use the include option of config files. You can keep a basic - IPYTHONDIR/ipythonrc file and then have other 'profiles' which + IPYTHON_DIR/ipythonrc file and then have other 'profiles' which include this one and load extra things for particular tasks. For example: @@ -307,7 +252,7 @@ All options with a [no] prepended can be specified in negated form -rcfile name of your IPython resource configuration file. Normally IPython loads ipythonrc (from current directory) or - IPYTHONDIR/ipythonrc. + IPYTHON_DIR/ipythonrc. If the loading of your config file fails, IPython starts with a bare bones configuration (no modules loaded at all). @@ -354,7 +299,7 @@ All options with a [no] prepended can be specified in negated form 0'. Simply removes all input/output separators. -upgrade - allows you to upgrade your IPYTHONDIR configuration when you + allows you to upgrade your IPYTHON_DIR configuration when you install a new version of IPython. Since new versions may include new command line options or example files, this copies updated ipythonrc-type files. However, it backs up (with a @@ -367,9 +312,7 @@ All options with a [no] prepended can be specified in negated form -Version print version information and exit. -wxversion - Select a specific version of wxPython (used in conjunction - with -wthread). Requires the wxversion module, part of recent - wxPython distributions + Deprecated. -xmode @@ -599,7 +542,7 @@ Persistent command history across sessions IPython will save your input history when it leaves and reload it next time you restart it. By default, the history file is named -$IPYTHONDIR/history, but if you've loaded a named profile, +$IPYTHON_DIR/history, but if you've loaded a named profile, '-PROFILE_NAME' is appended to the name. This allows you to keep separate histories related to various tasks: commands related to numerical work will not be clobbered by a system shell history, for @@ -693,7 +636,7 @@ follows: %logstart [log_name [log_mode]] If no name is given, it defaults to a file named 'log' in your -IPYTHONDIR directory, in 'rotate' mode (see below). +IPYTHON_DIR directory, in 'rotate' mode (see below). '%logstart name' saves to file 'name' in 'backup' mode. It saves your history up to that point and then continues logging. @@ -1329,8 +1272,9 @@ For stand-alone use of the feature in your programs which do not use IPython at all, put the following lines toward the top of your 'main' routine:: - import sys,IPython.ultraTB - sys.excepthook = IPython.ultraTB.FormattedTB(mode='Verbose', + import sys + from IPython.core import ultratb + sys.excepthook = ultratb.FormattedTB(mode='Verbose', color_scheme='Linux', call_pdb=1) The mode keyword can be either 'Verbose' or 'Plain', giving either very @@ -1351,7 +1295,7 @@ In a nutshell, you can redefine the way IPython processes the user input line to accept new, special extensions to the syntax without needing to change any of IPython's own code. -In the IPython/Extensions directory you will find some examples +In the IPython/extensions directory you will find some examples supplied, which we will briefly describe now. These can be used 'as is' (and both provide very useful functionality), or you can use them as a starting point for writing your own extensions. @@ -1369,14 +1313,14 @@ copying, carefully removing the leading extraneous characters. This extension identifies those starting characters and removes them from the input automatically, so that one can paste multi-line examples directly into IPython, saving a lot of time. Please look at the file -InterpreterPasteInput.py in the IPython/Extensions directory for details +InterpreterPasteInput.py in the IPython/extensions directory for details on how this is done. IPython comes with a special profile enabling this feature, called tutorial. Simply start IPython via 'ipython -p tutorial' and the feature will be available. In a normal IPython session you can activate the feature by importing the corresponding module with: -In [1]: import IPython.Extensions.InterpreterPasteInput +In [1]: import IPython.extensions.InterpreterPasteInput The following is a 'screenshot' of how things work when this extension is on, copying an example from the standard tutorial:: @@ -1431,80 +1375,107 @@ The physics profile supplied with IPython (enabled via 'ipython -p physics') uses these extensions, which you can also activate with: from math import * # math MUST be imported BEFORE PhysicalQInteractive -from IPython.Extensions.PhysicalQInteractive import * -import IPython.Extensions.PhysicalQInput +from IPython.extensions.PhysicalQInteractive import * +import IPython.extensions.PhysicalQInput +.. _gui_support: -Threading support -================= +GUI event loop support support +============================== + +.. versionadded:: 0.11 + The ``%gui`` magic and :mod:`IPython.lib.inputhook`. + +IPython has excellent support for working interactively with Graphical User +Interface (GUI) toolkits, such as wxPython, PyQt4, PyGTK and Tk. This is +implemented using Python's builtin ``PyOSInputHook`` hook. This implementation +is extremely robust compared to our previous threaded based version. The +advantages of this are: + +* GUIs can be enabled and disabled dynamically at runtime. +* The active GUI can be switched dynamically at runtime. +* In some cases, multiple GUIs can run simultaneously with no problems. +* There is a developer API in :mod:`IPython.lib.inputhook` for customizing + all of these things. + +For users, enabling GUI event loop integration is simple. You simple use the +``%gui`` magic as follows:: + + %gui [-a] [GUINAME] + +With no arguments, ``%gui`` removes all GUI support. Valid ``GUINAME`` +arguments are ``wx``, ``qt4``, ``gtk`` and ``tk``. The ``-a`` option will +create and return a running application object for the selected GUI toolkit. + +Thus, to use wxPython interactively and create a running :class:`wx.App` +object, do:: + + %gui -a wx + +For information on IPython's Matplotlib integration (and the ``pylab`` mode) +see :ref:`this section `. + +For developers that want to use IPython's GUI event loop integration in +the form of a library, these capabilities are exposed in library form +in the :mod:`IPython.lib.inputhook`. Interested developers should see the +module docstrings for more information, but there are a few points that +should be mentioned here. + +First, the ``PyOSInputHook`` approach only works in command line settings +where readline is activated. -WARNING: The threading support is still somewhat experimental, and it -has only seen reasonable testing under Linux. Threaded code is -particularly tricky to debug, and it tends to show extremely -platform-dependent behavior. Since I only have access to Linux machines, -I will have to rely on user's experiences and assistance for this area -of IPython to improve under other platforms. - -IPython, via the -gthread , -qthread, -q4thread and -wthread options -(described in Sec. `Threading options`_), can run in -multithreaded mode to support pyGTK, Qt3, Qt4 and WXPython applications -respectively. These GUI toolkits need to control the python main loop of -execution, so under a normal Python interpreter, starting a pyGTK, Qt3, -Qt4 or WXPython application will immediately freeze the shell. - -IPython, with one of these options (you can only use one at a time), -separates the graphical loop and IPython's code execution run into -different threads. This allows you to test interactively (with %run, for -example) your GUI code without blocking. - -A nice mini-tutorial on using IPython along with the Qt Designer -application is available at the SciPy wiki: -http://www.scipy.org/Cookbook/Matplotlib/Qt_with_IPython_and_Designer. - - -Tk issues ---------- - -As indicated in Sec. `Threading options`_, a special -tk option is -provided to try and allow Tk graphical applications to coexist -interactively with WX, Qt or GTK ones. Whether this works at all, -however, is very platform and configuration dependent. Please -experiment with simple test cases before committing to using this -combination of Tk and GTK/Qt/WX threading in a production environment. - - -I/O pitfalls ------------- - -Be mindful that the Python interpreter switches between threads every -$N$ bytecodes, where the default value as of Python 2.3 is $N=100.$ This -value can be read by using the sys.getcheckinterval() function, and it -can be reset via sys.setcheckinterval(N). This switching of threads can -cause subtly confusing effects if one of your threads is doing file I/O. -In text mode, most systems only flush file buffers when they encounter a -'\n'. An instruction as simple as:: - - print >> filehandle, ''hello world'' - -actually consists of several bytecodes, so it is possible that the -newline does not reach your file before the next thread switch. -Similarly, if you are writing to a file in binary mode, the file won't -be flushed until the buffer fills, and your other thread may see -apparently truncated files. - -For this reason, if you are using IPython's thread support and have (for -example) a GUI application which will read data generated by files -written to from the IPython thread, the safest approach is to open all -of your files in unbuffered mode (the third argument to the file/open -function is the buffering value):: - - filehandle = open(filename,mode,0) - -This is obviously a brute force way of avoiding race conditions with the -file buffering. If you want to do it cleanly, and you have a resource -which is being shared by the interactive IPython loop and your GUI -thread, you should really handle it with thread locking and -syncrhonization properties. The Python documentation discusses these. +Second, when using the ``PyOSInputHook`` approach, a GUI application should +*not* start its event loop. Instead all of this is handled by the +``PyOSInputHook``. This means that applications that are meant to be used both +in IPython and as standalone apps need to have special code to detects how the +application is being run. We highly recommend using IPython's +:func:`appstart_` functions for this. Here is a simple example that shows the +recommended code that should be at the bottom of a wxPython using GUI +application:: + + try: + from IPython import appstart_wx + appstart_wx(app) + except ImportError: + app.MainLoop() + +This pattern should be used instead of the simple ``app.MainLoop()`` code +that a standalone wxPython application would have. + +Third, unlike previous versions of IPython, we no longer "hijack" (replace +them with no-ops) the event loops. This is done to allow applications that +actually need to run the real event loops to do so. This is often needed to +process pending events at critical points. + +Finally, we also have a number of examples in our source directory +:file:`docs/examples/lib` that demonstrate these capabilities. + +.. _matplotlib_support: + +Plotting with matplotlib +======================== + + +`Matplotlib`_ provides high quality 2D and +3D plotting for Python. Matplotlib can produce plots on screen using a variety +of GUI toolkits, including Tk, PyGTK, PyQt4 and wxPython. It also provides a +number of commands useful for scientific computing, all with a syntax +compatible with that of the popular Matlab program. + +Many IPython users have come to rely on IPython's ``-pylab`` mode which +automates the integration of Matplotlib with IPython. We are still in the +process of working with the Matplotlib developers to finalize the new pylab +API, but for now you can use Matplotlib interactively using the following +commands:: + + %gui -a wx + import matplotlib + matplotlib.use('wxagg') + from matplotlib import pylab + pylab.interactive(True) + +All of this will soon be automated as Matplotlib beings to include +new logic that uses our new GUI support. .. _interactive_demos: @@ -1602,30 +1573,5 @@ divisions are allowed. If you want to be able to open an IPython instance at an arbitrary point in a program, you can use IPython's embedding facilities, described in detail in Sec. 9 +.. [Matplotlib] Matplotlib. http://matplotlib.sourceforge.net -.. _Matplotlib support: - -Plotting with matplotlib -======================== - -The matplotlib library (http://matplotlib.sourceforge.net -http://matplotlib.sourceforge.net) provides high quality 2D plotting for -Python. Matplotlib can produce plots on screen using a variety of GUI -toolkits, including Tk, GTK and WXPython. It also provides a number of -commands useful for scientific computing, all with a syntax compatible -with that of the popular Matlab program. - -IPython accepts the special option -pylab (see :ref:`here -`). This configures it to support matplotlib, honoring -the settings in the .matplotlibrc file. IPython will detect the user's choice -of matplotlib GUI backend, and automatically select the proper threading model -to prevent blocking. It also sets matplotlib in interactive mode and modifies -%run slightly, so that any matplotlib-based script can be executed using %run -and the final show() command does not block the interactive shell. - -The -pylab option must be given first in order for IPython to configure its -threading mode. However, you can still issue other options afterwards. This -allows you to have a matplotlib-based environment customized with additional -modules using the standard IPython profile mechanism (see :ref:`here -`): ``ipython -pylab -p myprofile`` will load the profile defined in -ipythonrc-myprofile after configuring matplotlib. diff --git a/docs/source/interactive/shell.txt b/docs/source/interactive/shell.txt index 91465ac..2f406f2 100644 --- a/docs/source/interactive/shell.txt +++ b/docs/source/interactive/shell.txt @@ -4,6 +4,14 @@ IPython as a system shell ========================= +.. warning:: + + As of the 0.11 version of IPython, some of the features and APIs + described in this section have been deprecated or are broken. Our plan + is to continue to support these features, but they need to be updated + to take advantage of recent API changes. Furthermore, this section + of the documentation need to be updated to reflect all of these changes. + Overview ======== @@ -251,12 +259,12 @@ First, capture output of "hg status":: [Q:/ipython]|28> out = !hg status == - ['M IPython\\Extensions\\ipy_kitcfg.py', - 'M IPython\\Extensions\\ipy_rehashdir.py', + ['M IPython\\extensions\\ipy_kitcfg.py', + 'M IPython\\extensions\\ipy_rehashdir.py', ... '? build\\lib\\IPython\\Debugger.py', - '? build\\lib\\IPython\\Extensions\\InterpreterExec.py', - '? build\\lib\\IPython\\Extensions\\InterpreterPasteInput.py', + '? build\\lib\\IPython\\extensions\\InterpreterExec.py', + '? build\\lib\\IPython\\extensions\\InterpreterPasteInput.py', ... (lines starting with ? are not under version control). @@ -281,4 +289,5 @@ single space (for convenient passing to system commands). The '.n' property return one string where the lines are separated by '\n' (i.e. the original output of the function). If the items in string list are file names, '.p' can be used to get a list of "path" objects -for convenient file manipulation. \ No newline at end of file +for convenient file manipulation. + diff --git a/docs/source/interactive/tutorial.txt b/docs/source/interactive/tutorial.txt index 23a2854..1f5ccda 100644 --- a/docs/source/interactive/tutorial.txt +++ b/docs/source/interactive/tutorial.txt @@ -4,6 +4,14 @@ Quick IPython tutorial ====================== +.. warning:: + + As of the 0.11 version of IPython, some of the features and APIs + described in this section have been deprecated or are broken. Our plan + is to continue to support these features, but they need to be updated + to take advantage of recent API changes. Furthermore, this section + of the documentation need to be updated to reflect all of these changes. + IPython can be used as an improved replacement for the Python prompt, and for that you don't really need to read any more of this manual. But in this section we'll try to summarize a few tips on how to make the @@ -313,3 +321,5 @@ organized by project and date. Contribute your own: If you have your own favorite tip on using IPython efficiently for a certain task (especially things which can't be done in the normal Python interpreter), don't hesitate to send it! + + diff --git a/docs/source/overview.txt b/docs/source/overview.txt index 184a0ea..228d2a3 100644 --- a/docs/source/overview.txt +++ b/docs/source/overview.txt @@ -218,9 +218,10 @@ for parallel computing. Portability and Python requirements ----------------------------------- -As of the 0.9 release, IPython requires Python 2.4 or greater. We have -not begun to test IPython on Python 2.6 or 3.0, but we expect it will -work with some minor changes. +As of the 0.11 release, IPython works with either Python 2.5 or 2.6. +Versions 0.9 and 0.10 worked with Python 2.4 as well. We have not yet begun +to test and port IPython to Python 3. Our plan is to gradually drop Python 2.5 +support and then begin the transition to strict 2.6 and 3. IPython is known to work on the following operating systems: @@ -229,4 +230,5 @@ IPython is known to work on the following operating systems: * Mac OS X * Windows (CygWin, XP, Vista, etc.) -See :ref:`here ` for instructions on how to install IPython. \ No newline at end of file +See :ref:`here ` for instructions on how to install IPython. + diff --git a/docs/source/parallel/asian_call.pdf b/docs/source/parallel/asian_call.pdf new file mode 100644 index 0000000000000000000000000000000000000000..3e5fddddd138567702eee6b4f1f4d5a21b7b2750 GIT binary patch literal 41078 zc%1CJg}T-Ul-!}0 z_tFYaObp7SV)pu#vjtQDxTFc?k+ZaMHgg2dHZPpbWXw$LOwFL;;&@KZj%F`x@!YXt z+7N9H?Y=t-+)8TP9QRazXmE6a@${&uIDE6H6V!Ti`c8fbXxo3kReT4GmGFRY3(xGe z=|4{cpRSn-{KH(}!GEss{YxBe4|_8x&(jyL{`_?`d+iK;_*XPdGbcM2M-wwAsNmlV z(sr+%f$yE5{D1#fH8Zt*@x;y@`kWWIAk51P73AeL!~?|thmYXD_&jyAGtn}0hCT;A z%E&=^6m0>PPyRVe|8rJ^8bW#W)L&YenK(nQpHj4a02TVfO1EMHkTUyFRLnT?&Jmi-G8Gdz*MbQA$q9qsL| zy#jT1bO9dKQhniM4Sm4-_xmT7&Q4Fw9Hs4S?d@J)8%_BC>Px(T`*JhmmKWLZIa-=S zQDL=FiTJ6^nhsP-7H6p#&Si{U0lk~#>#J#E0jeNi)Wl2Er!7`g6>t zHpehYbDSshAjBU%s{gX^PT1k4si|p7Rojj+9v+^8w&%&VVWs)c!a@!j*U8Ve^3e?5 zn_~~qqm;UeTHF1(A?LQdNODF-b+>yG{2O@WoX*=*9wVjCK5><7opSg2uAR$ZfHwe^a0GdEx)V z20BXsO3-GKcHgdDQA*?-uC|6}vL~jddTdpX4p_)X(WSnBkD^~sefaA9Xaio#Pg6~r zhR6_gQQA76Zx46b7|F{Qd_7gDoAV#Jp-^GJ?dketu4i$W#i&QyQ1vYSaLSk)w{B1Q zTwNyc+{F%$jAU)~m=9&&-``K@_DUX}L0)W4+MyfeB5CrJlgkZi_ibyYj|bHyEGIui z4-qf`c zNdnX|L>teR(FT+2(YS`c&FHfRqczt{B3?U%PnM^v7zn%lGsC3QH>s`q6WKM--R6Ns4U zJu!P7iWQx|n$Fi@)z8{dp2wTi^z`1nk53|~9|qGDY$@s#Yu7rj#d)UM`FVxjf8=>s zxC1v5VBy}Htnpb+ef2GpR{pkDff{`pjcvk!k(@yA=`cP|gQV)ucOvmLaEq9pSf1D* zk_Y~hM_~xd6NK&fr=aG6$eJ0?jcy*a*vmEuUj>_LS|Kyb%SFRL&)H_bA+=vk-nce{ z`Kp71gTvNOvHlzRF<9JH8gH+5TklxfB*YbK7m+MwSlp-;9lFA-NH~AJ*g>O4p89?M zsvP%W==6Meyz*5wLl-kHLMPdQ1F)6*oURC$=ynjwr67-$2l2OfnIVP*aHq_i!Kf#1Lilc%PLU zyDmfT9`|6m;6#mjQeg(fP{jm!JpO8UM&o}XM`C%9l|9zTm-V)Z5wy_)U(JW}3}yGE zJP5E_{*jtH;!f^$*|h;WSNKkC75F(HhPa26^`PBKOyJ11=7`NJ8%nCNos^?=UK@~o zPVJQ%2Y0T2Db;0o_y7Fxj$<de&jISr$hxy>@Esb7P10W#L8vaVgy^Y zIY&|x{<6=wesODtX;=s)4T|7a@ypdx>i^Ci8Db=1$pfNgwO^u|lCzKamUG@G&7v}R zcl$m}3Ecm=(7L=f-)8*NI#%IF)9)nJQs-1@h$6y)dBctTiUJTh$+=y|t^<&VA>T z=Z);!_|!tPw-4J_U!M+d_!e z%=_G#rj^#NA^UQ@tY#)hNexrni5W&;UE0xbKdvpx0CoMhD8D0x2IN50s59BGPG0q8 zNhL5@PJMBBu~5LR;>^r#c#qQkjQlaVLQTnrS(iXpXx8~C`7Hvfr(=e-9-OwK8@^Vn@@PcA%pvN|z-;2mE%ezktxJryf~u;HxKWc=Y9`|c?d(wp zQceH7ME9n`1Z^3({(y!y4U+qluRvY@-+GIq7 zLOCr*MKdJq=i6|XjCjx!4IU3|o5I4wb6H59^{EO?%P_883JhfdC7EH3PJ8RZp%C9Q zEqbEBNygH+-fbt}+9KdFq3`jc1ZKB&(XYxQLifoUMpGISM$^9o=kF|gUNdm){z+h> zP_7`1kWu`do}u^AFrLo&O3g2%a{2+TxY>+FnseOE#-NZW$lx}$7LOeEf7*VtBj}Km zI&5>oG|E-MS(KET4Cf>wf?#4wt$?V?qTfIBE=x#uDgI--LhtxbI~N)7GJV-sZ#Z9> z#61M0xnndM*l?vp&9Y=~v3Q7F(XY0OmC?uyi3lf?)e*0=xj5dcD#|=wjJN1dy=fZJ zWT(fbHBc8e3ayqL;;W_>_R-N}Zfev@?}%n($ImM}?P5A`J`5biElg<0$=D^N6=X>{ zLby3EwF}?LX%JjI6@-i`vQ)@YcsGV7ImO@z>HYHd-diTB%KlN^pV< zXEbXouH24oweGD(9u314YmT=j3#RsghCA4ynl5;car(6o>nBjdE16n?EAT)05RVIn zxbl-I@%6bXp>Sy8<=<|PoC;1xO8T>Q`DSP!QqWR9BwDr}3^!-|&dz;82dy{0pL@x-Z zzRCNd_~H3dGNSX#Y9xUbAs&>75BrydBXabX<(tGhafJe|o3-K!SF(~<$*DBN6*3LK z1RK=Ox~*w<+=V?yTZ!jRw{(TLeg>Nqe*0`$AG(v$?|FHwx#6~Yxi!H3#E1E_wbYMi zy_X+ZF1;D@56tg5$wsR8Uyia>OG*r-*{N`Gjd?V>#_z+8uzPCm4f#s){;?NH3V6oN z=OW>rR>IA-{wKdKx2mMN-F)izeW>F~ezjdht~hqdPZ;vBCS5wY%cLML_;16zU(kHi z_&H%cpSeRag_Ad}NqvcK+~&$g6Hit{mrE4`@U@bSU#iYpN8nMAy~d;cVE1wIlNmlv zy@=DYMTc&+l@6U>9*)0b=d}d=lad_L!qFdSi_k^>>XV$xa{3DjadpeDBzM3CqXE>p$iR z**ZhYY_vb_){CF2m%`?R#<-tmnz2i3lcDzBj^~ z?@)QQu9rNr+(};B!R`jBy9zr9t=!Z3q;5nRHTHOCA#Tnh=|HeO-6e={|3H0{##9T< z_i#O%hln@Cw-c$uMUDTfy`%1PB55~Sl8aO$1U&Yk?paZ!h!6!q^m7okX0G~Jp@UBo z2+ha-I){=75{!l6^qMDA>F%hni6>PK&mu}Oc>x}aV0&g+T^l(~MYRI{9aGov)rhUu zAAv)vu$QW9BdfZ7rqN~@@MnJ_JiU!qX>iwu?RC8${s3n zG5J3+jzZ#oOjcUnS*6i8ZZ~WpeS4nPGdL>hViuf|C_~IRSyBj zayvKEa?pzFyUVgFv6-!VhEvU98&b0$F`a==m%sYct!7YT5cX+eGR@kR`?fe> zq*Pe1oBIi2bolG3?&m>A`v*p6h4cT10(pqSD(-haSdOga1{K7t`$BFj;!)DbFU&Da z0_TB|oaL%#Mt9J7e7kdeM7(}j7$s|Y(e4$-Km%MZICJ;nlSK|b5hM<{xj?I^=KXenvg_kc$cymXJxRaU;apD;!>Yr9A3kYc`*r?I|Xx1^K-s7H0mHLB~*gx8MI? zfka-1NaRdB^HP2V1DnICum$V$9u;_1fMEA2G9`cT?9aYzL_JGS$IB=97Z*-bwBzIH zypOZtp07w_ep%wwk{Bh0)yq9IVWlOGU|y@e`7bx?SRfP6?Hm44VG++<#{guh*?l7I ziEwf-#7CMv>64*^60rbxLXa-!YIH8Fs~YhA`m$+}r7&6}hsI9ueTBySVB6NjWZ$nS ze*&*B1Yi{?7MimhF;`g9Lh>jI6=D!ts>t;7vDJGqj#Aq4SvZ@FqBAxqo7fjY-i^9# z7@k`$^V6Vl^k!;F3|;P*iv&6+@5G&FImuUn7tH(c>~^Us9MBv<6K@7(Ka{xeP&-KV zDO+tW##xYppD+XW4gmM+yx5*Q^*rd#h(^`{{C@4@I?mS7BbhcMjmioTmfkSNI@7a%8aS>q*I{%wE(lqTc$eSc-+C?oKq z$ktSC4D2Drkbfm*xqKXxfc_7;R2!|P>8~giNZKk0?9T$ovI!Sn@9Gu>pKyZ@3H z@8DWuGkSlo1PNaQCMR+hzb|F`l@o`tOJ=7!F@ATF{d{qOrhvS(^4Ur|3l#uAz*Es; z#j2ebRBij!ynf$=nVXUmh28uwN%xOR8kf43Sd7ZP8IXaQpuHQuW>UZT**dl3ijr$T z99MpMMmoevf#K|I4(XO`27`8CcIcek^2`nmcuOOA#jZBY4{%;A&^u08obLUCSMw$%amu94}Xo8{#3=$3jSBBx56>bVnQx&pI8?@K}z06B)QV+i^rFC)JJD;0KIHMpM1Fpgwq*+unf ziPOIeEE!sKB(M6No+hn_hN~F1#|w5hB>yKNC{c{$6l&i%a}+!WX9Ft%k<3Koi-<+g z*L@C6pjvUyXa-jUB4FqF*Av9C=Bg%_FU*w*=?CB$$o=LFguZ<)2~`2iePKNc_)ve3 zD4UCCJ>o|q>$jJ=YU*9y?2|84pIRI2mhj>z@#6!uiKbA;I^_xJW0K(9W;z|vz&!2r znT8v`YaZP}lIKf?no9v_bN*bXNOOB-;;k`RO0JTm{>=2&JzGz{4}DLgVt3_c z3o%3zJl48p_fA2W|4JWtR8qy1G5j&y9|Fc}ig2TpEQh`cA}&0mS0QK6ytG;-SNTsAP@ z_=*ec)>ej!w2(ZKtPp!yNg}l9*yg%_+rEDxPX2c}nPt13WuA}wabjP`O78+~(Wp!A z<^rc{!?%27wZp=<_2H6}?df_r%IJgo#wh9}GOo6^RyB>k$gtkk)RYC&m3+ram<8$& z^ql7%4;w^FrHMeBd2t@~ud6t{auJ!h1~ISSZ+{VxD!Vl?grYK0vN!l>EcF5@;CZ;J zTc#gL|A^uYc3T!Cy|3K7hpa|9nl)g{6IJF|eu~XIaCMs(X*02*Am*siLVk<2_P@Og z6A&a{hS$68M1_S-0JC8rF6k_s^FEm{`>YLmf|fS@vZZ={ENJ$jS#FNwiR(uWFO#KM z1c0{$!pAwvLWHYr1~G{=te5ABEQ$c4rx$hRFuaM@e3soTfz5saL|kfbHHCplTdMdq z&O<*209XU$UE)sK`YNCUOmqNc{v$=BKrQ3P50z8$D>*-6D_r3*)b33>zs2>Xv3IRy zjUbpfo_f}Rxkrkw1RcUP6G8vTl}k|2b!{L|Y3#{wCE6y9<#)bXm+J#rum6f_(zQZo zd#^!xw_fbkcQjz!B`?8=y(dcoQluEG)}O?w-r*;YP|`9e{j1By3M=sC)73q~sHTm8 z!>2Ez86NZT@eN%4rWUXw+d@xNzKV|P*|)_OfHvR3mOmdpyR~dV5$P2A}{8b$P~Q?)BdHuGSwoDHvu z3w@Gl^tl1aB>vQWIDX1GUVvH>>@qRdB`W-!)R?7nJG-mrpad{yzk}X-Z?oTx@73ZL zo0ac&gSLOW~FM`+gZKPg#=$^tE|&1d*x<(;24H{k1dQ2O5T1X@9xRazl-wRdk1CcP`Ng4ID$dnMANvxLGq zrD2~GT|V6xe0l?h*FsuYokj?E6240bJnQvMJ^~MXNMs)j1hl+ebCG#?8~)9rc&v0y zi48~Zxk&#(a?yjUMuVifCT_0@B^{;H#7v8~Z={;_K(}{Gymg8fSw}{xs~zgOAsCEn)B-^Z2NNoGvPsy#m-KTWK7L0snN%LLxshWj*Pue4j+%-y8=pa!y$i!Y6! zF}Uez8vlbWhH3aJuJMB>4jvx%B8mVv+yV?HU^2EwPiWOSFCUaS%h^u7#s_U4S@TT~ zL*YpAv|m*sn{D1YE2b;v<6Ak&W+IT_aR~|Q!22~*e4%Ej3yHCv;R*ols_lJ-W$Z4ef=K?1iB6*JmbsUb|b)Mgm4>{m_-4NVKTg=rY%~wdb-Psh2~@7 z${{A=4fIvLTz}#NTn2$^s}&K~IJmY7D`CbUvKw zg6OKzRL#;Q*f3$cJfkm&_+q%U9?n-w_w>lRx3woZee zz^Dm>HRz{Y8Zg*359Td1R%l>HfDo3~GthrT=0S^2Waqupo1mzrB+$Hr?V<4sXo+?e z3=0oZo2kz-@9B|pS*a2M;&5DxBaBUZHk@spuGHGS7CP}|lmh&rU8wFLSLOE7YScqe zqR{tV%b(aw#t^8FnfqIrfa?{f`qN>*l68+@cy$niaf~NKLAkE(aSl(IL%yi_)rLa6 z{!f$KfPP$q1z7+cJ94x|UbA_AI^0xzc=Mg7XO7@@H4e7rGq;+8s##a2(?j*nJ1i!u zABC@T&>um~aJL@{2~_d*K?7>rWXql3dbVrUP>9VY!7Pqr(BIN*7t)}fPSayYq0l0)Z98#dHs}OKsOthF9`$w(9TK$3))T$<;q8o zjg9Rub)T#cTlZ!@*1t{3W8}5}o!xr0+_27h4Jh^!r?UYa(Tt-;SqUO1*UeOf@&JSzkji|O721t;SkZ|gjx-WJT z=g~h)vimZ3bHChe#^cq&@=tuq`=vT1x*4J_d=DQsiq4Ot?%!@>+Uk*5?>b;!?AkOX zhReC>A;GUgig7tIfQW$mw#pMh{3HQk@4*r?GRmfuarg?6m*@8wea@DWNm!LsGR2cvI)YwN29w3iZE-h*r3-yDR622`?Mz7)+vkki)({37F4e&2!9< zKuPHR`{j1OnkHx!%Pq=c_~6=#){iux8-?+)K!46yKHRxX2I8$pjtO4HXTHO{i$JhgxTBeh+GG$Dj6 zxfyeIp!hDBfZB0w=Y6(xND`+`n3e11n3SqwoQyh|Zi%k#_Ec>`i++QbKiO;rg zmb%DMQDkT1NG(JCEI}FT< z%gk}sefjckA`l14rb+h=m6pu9lZp~LKNXrU|46lp)SPAasYc=5wj8~nr0g}9H8J_q z{(TK_RkFJ9j!7CgyudO4F(GM|Z1d&C$$Mc3e|`7$q5>L0n?im8%K?^8MJ63lMcjiu zuwrU4_m6<%g@LJRpr@8*jgbMs15Q~P%~mh;{ohVTS#iC{Z7HCzNCg0S1Bg!C-xmpb z)$=a<0Hvv^nUJ3_NF{!SajIV8e*QTIVX1EOv}wKtwHkB$!uC+!zN(oU``FQ@XKo}0 z=7&3UNrbWyoCo(e)8L{tr5UbQxc$5@j#=5+QO_#PTZ0KJU-g#5Q8Q&VGsSd0lC}fl zB_-u`#Y%~sJ53kc-y&((yfo^3#WN**W=iB4FnA$VP8+hp1QC(<-aA$0HCuhPee(T- zpP54B^1S=;Za4Wp+^CD)i`vin+98rIhTHx2g24{_E$rBWpI;t--+;nfNEDLGGYD(>WPt&?2^**g z#!T4Omw*rl_hjWF!y-{}-sdzghC!t2@%oLpwS3CJeH3z<&V-;QFxPcn&BwU1OYnBG z>7pIWap|P>K=+FloI4Xk9dXe6g!YBl*!Z}5OYjcS>K3Lgxx7$en~+BR4WRM26yvZV zpRJ3NJDQaPqcc~;hgBM|7p~qsJfyspX)CJ7K(#pCrHJX8WYIv`_ZZmV5*~7icJ}EF z5CsYEFVyj9$pUqh^aT9|+(tj3wEHMX2VVtKI>nCbXcqJ@iYc4$H#s?uMmAk@o)p2R z5NyGEv;2CRYq}vz^53M&x#D0z68Qp2q^HnqigqMy>W>kQk^^utcT;}`TvxA}2sz&` z(JjrFxx!C>xIl0`N= z%R1;k^$H-)j~1Q!B*H+&-)8D=9T>?KUsDP5Ax7v8Kk%8aPM&Y~_hQ)w%zIM{Fs~8# z$6kqdB5Z|yWKorXm*EmT_C?9PBCGRtDw9BZI3@l*6Fi~CAK`%`>;(ploH7TGjRpG2 z0Ap7_$PPR;BG@ES==)|8iX5BuZ^+$d6UD@4mY-YpNK-+QNNz?Wd--qW2|)u^rn}|7GUD zVbpDYD=6!?RJ!wi!!NjGrE7ljII>A4)B9S&i(#h0t%yzZexw6HxC9c|dp%61r;l+= zRWNL&EBZ!qWuR|F`{lD`Jo z*vN`;ayU+OaqA$y8u@?URlx#6V*v;aU1CT)B4cCn@zmTO!VKgMj@hweu3yzjzWc|T zDnwGu)7`ueOo&al(4RQtau7tjtVFlbq65dCNmQ2Rhw_LQtj#k`;@yVyj;vgM{6#;X zkNxavG>EnMit;j{vyyD)1O;CV?+6sX35Mfaf017>Cpq2F_&TL`og`?EX?`G}Z`JxE zo{`J-^FU5njXxq1xjdH6D9q)24#{;vI3;_!Quq`Lr?GW|`;EdOQuSCJ9@J+r^o!ex zP-9$d6JWA{Hhdjt3h|!PkV3L_6vuAH-^XBY+RVBW^Buetu86JZ0~ zizs2-QmUH}BJ4|~%kQtJyRu$YHZ*?zI=HXZEpNO2hS&;I?4!9{(r?#(sssXyXi&-* z1`@qo*IOCkstB~a?Hko1HH(tRsd201{gi9njr%U~DiK`!sTR2TOIR5d0c3TrSshfHMM^bIhM6)51oyh6}mDEkjaskyF zeHy}$kKd2V-=QZaW}{}fS&14A8LZ@En^P`Hd=Y!3&mWKwLB>(P-9K3i-xZ(WkHorA z#~0(YD4Nu38Z4c6&c=& z4t@iMlbKmMj>Q&>l`z*JsbisEH-`X3N5WB#*ecO}AW;z|LgQ=HA_etPcB?oL)D0uN zvuLPUKqkhyDpPxR`co02D7JqTHG~>!Db&dA{Hm#?HB3V$>8lQEKLDngsorW_`9Asd zWunesRK3z~JE%(U+Dok`64W{t8sN^uw)hV_E)_sDWV*xY_E&ve#Xg_ZbQ=!o&; zc`|oMOYt^VlW&3KmwpR}e05X(sVETDi};x)5FQ8LL0>fqhGi3gyhtE3s3;=cp2IGaLIO%6@4mtuUHcF@e!J(zL6G!EbgN;YntOUxY{S zf!J!UloO;gT(tgi%d#!+9u4AS?Cna3N&zoO?Wp8ux==@Zd;c0C4h~L}27v&yncdGA z!@0F~e>nxq^T9h`v4XNkJaQ{OcJr>2B!%^m5Cai6p?Z{=$NLt2mIVmo?vtPRsVS_gUt~bo+{!~XIn|{3wR=Un4W1xVZi;7fy5aIJR ziePQlOslQrlrC>$iD44R3~-_X$HSgqkXt71lTuR`kpYNJ!B2C1uUxly^qr_n*n8fv zSYKZR2peOC80I_+6^I}_xA;j1vat#%JxqI9jANd`B=F{zBWtF?_yh_Q%&zV4TZT)v z4m7-Hy*2#uH~YdXzM(Ae0-pcLm4d zN9PE*0jQ-mHm_ewlbT`~QYrg3YIC|FZdnAUfbT0$=RYYAOa>$!`%(#6wFBb7rjMZrC z=P5U`*M4)g-T;z#eP>arZE+a)47-!uri%xA5k)j2<;w{4tW4J8j}_k!M86#5O7|dV zz7wTJx#&Bu^}q6zG6g9slN>I^hFikG zna^D-Ca(a3j<$X_NTA&HTUV3QQqw`&SG6S@m~O(F$=GJpzVYb|2$+pGzR`Q@+^)6Y z{%%R1Ki4)1Fq?*{C+^der)vv!>vTlD_Iaf8JalSg}ulX zVe6pZNm~V|YXC-U5gQC5N@>k(owv>-JCATA{SRhUX z=u4;X)5on`*+&)n%0JLH{4NKEl%C>&H5WyG4)$^a;EhBi!DO2b@X}cqft`TO2;u8! zUstBngbi-<+}S(Or&NA>Hk&CJ#vH@7Vjx9?BR*2ab%JN%4dV%;?qe{AXUQ~U_Aiv} z9}NgxI-an{J>N@iAe)y8~+eTmG{zslgoPTb<9bN&CO=3V21d%$Bqv@D`vr+I!snfYM#X( zQ}a7_s$AdShQd1$4pc85u*BjvqrNzDZW&*%8z(auS9wA<6W=%2+u>>9_iv1+u=K9$ z!S#&+WEM)-^}y{maC?$aA<|t3Eu5@QS$@7}2h>aXv<)@o)&bgm(se zu^`OW!w$IDO5h#Y*xSx`CPGy7ZF3S{B!w5aw`)0B>=s6a+KNE_&H2?bql~_DF)}JFJ7$UXk9YBiFyv;Eb7f{J_N{83wL>@;j^*63!F~Am#P| z-Fqg+xj85R?Ce?j;pQtnW|uC<6v^&VGsqi>1gUPjE!Fh>0Q87m5MI94gIofch{lEm z+(dnKSamE|Yyx0Itm_9S&3pTpZ)=o?oq*@OZa#}p(gQrFhEYPIBPX@eq#2uI2v~OO z#elxFc1W}`Egl<5n_5%;<@xw!8r3lZm$vJH!?hy-SPKlKNhMK!4s@z}@=)_|OzFWC z*24isI9}nH;_l1GD+E&%|41 z>|r*_$mFj5qNW)YNVt?D90J%g=^_zByx28n_n0>O}P$&b^uGHX!o*zlnxTr`QX$gZ~6lVh~d21u^uB|c50ux~TJ;MO(_eQQTn&Jg$S9O*YtGFImTd#2^( zg(=o4(M1_$#Kb(Uc-hV@A62*o0E=RPBmE;+%;BKAkljoJ!0cvy4%XR$_?cB$(u+{!{48~lW=ukuyz`f$msSFd2) zPqTH)u77NxMY}m&@0KoTE5;D5zfgmP<{LyUdeBedxQA9g&}|LP&sVgaX%Qi~Dh#@z zsqUE{)5g{iQ!^eTBtWTLG88@sN4C;&N@eMR=jZ3okGD!1gBHU^@XVg&cbRlVC30xY zx89K$GMcR+K=b`Xjh%8nTN4ne01>njfs#K2TE@UShkt?1KV3OHzt`63hy!7|>{6sz zA!Ol_+S=Mzzb~t60NCpLPU+cQgDMN+M2RcRLAqU2YB?9Yd zaPHKo1-Ci#x8V9!I=*M=Bwt*&1I0f^B)ipeIdIw*?81Igh0qXj-1i$PdoE-(^!7pi zl!9dm;M+JY+sV(7R{3ffN@f!$zcB~BgUc`y?z%7f)mD`E4R+TdNV6z}qH>+XOB||T zCa~IfZ->sr%silZJo-~*gvu86^n(AcVfYb2e!Q1>PY}j%r{VCVw=Y{-IgwLV;F0TQ z)(D$=ri$4XXr2d+c_OgzE%IPpd9UQP3-dYu@0}fXH<=U@djG=~wo#{7aDJB3Tw(?Y zT{0sOL~=2QDlD>@zl1OSgP!O(_B4>xoM`$mi@JrreVc_IZv34ow3ti6HL}BzcW@Oj z^yKuWWv5C%1fud@JcCc$RP|7Lt+cI8hV$LyH%XlhCo^xfGatE?Cy^|nwi^!AYzY%% z(Reir6~wV3^3T^Xw74|~(z72ikVU2Fc;SVaP@AdF1{aFgW+yP0&Ob_$g<+Mg(hJ-B zMwNGUbqz@D`TP6-vzD3W`OjKrRjpY!#eT{4uF?8J{p#W!V!hoG`wN|~Lc+zf^dR`qQh=6-48XiQ*c zR>YeODP#Sw`(5P{rALJ=o*{qL2*i3kD6fC?<6*DY(J=gjXc!`v*kxztF&K1lyrrzB zW>jhZGnkOpyf;%-K6J)&qpWL=K*?G;i7VTvu>L*r+;VTe9d78N1LV=*z(B*fKO(3X zHC9--S*dyicAo0K<7c=(*wc68$6ZrUyxzWvNIjktEyMa8+z9(NBMwtvj%+yG$RmhN zIoz`t;e9ggmf(ix*F4u6Qt!H@bb(qu4O;%0uAVOV^Ms?{tUIaNcJg}Uc3kRm7NuDF z;`QhcKe3JZv;G?qGPnFV8mdK1*&TjF-rKJSP}DwD1N$4+pCUu-hZWlQEt1wUUmu-A z}dD6*0C^Sj+a($}SsWq5z130Ou5O8l_|BMjEHc+o%f&F&mrA2c0ScQoG)8O(7h7006ZjPb{h9}RQCw3sH>aeiSSh)#88ddg#97o=}5{8&Y$NqEEi zAS&kZ+qL^|UhuWPXjHun8*x~5sXOeM0!pWby#X58Ca*J zP*p2tuY-}jAg6Lfv0R}gaY2$h@CKT11f{1*=w_~SF&Xqw_vA^YY#wS%xhDqv<1Rnw zVP15}m;1MPsm0OPs}aO~qbQDQSwY!*Tt_sbJF!-)^sLug(VMRkS_LxkdZR$sGPwA@ z39~eQ*OzW4@IJbxI~d0E(el9ju|kASHlLb$f#8%gKzdz7$De3(S6Y0{y>G0JG*Wo~rO0c^>zt|N+m zFj!Xf@x}3^-RMXbmoT>HclP&QvwpW4IDcXwXudAg*k;o9lwNwlYvEYB zl3gKkB%2dNCb{kWzCTW2V6yFCvPvnh;U*m4xL`bvzJ7YCG6CY~4&KyyD zD7^~*^~S%{+E8N=Y}=(>fcr^0W7c_m4agAx-*}N)Gq*Md=C@=O_sT2+Hf_YYPe@E*m4PA}2H~!+imPMv3OgK*ZW1Ot5@E5kE4UK5-@Q3zN_PT?AQFQ&dzu zv>YzhQIfoFraHH&`4$V_BQ<*{>G>^U&MHVA@2mLUM(332>J)2d8*T#)ig`L`^uE^q z`r+Cj#cf(7`8BXnTHfBmlC9wJb6yJ;VuI&%kvF1h70=IFWZLtOj*nn`982tce46+8 zZN{EPMlgCG5;ro2%*-jH;&V?H`m%G^;T*ocPkgn*KItTEeQ4Bk?&44sR!pw8pM^7B z==?r@eXN5YA*N~-52_{6Dc{_0TAsJc^uFwx5Pmx&1*HSfVfI<#N(h)E%p3R1hp0g& z_>->TUpv-Dc7qGj;X~Y~I>I?D6JWnSNHglmE^IBn+`Tm`W2yL(zcVq$@rX0s{a0e6 ztbUj`k1ryK;+8|QDsG?h&1gmmwSa;|Db9<_3VW;FlKJytgdJ6z20Ttj-gkXVNZRW7 z=Ax29ZCabrR)$Mo(>B?U&bdGDIBdoJO3Lf3PPV6qc*to^F&v4@(MnsyNF4)oOQ!3E zdHIxPhDZ#IX~~K&eTex$fj8{shV?&~j0>11Ig=2IjK3^i)%T@iD$b^0+%Ix%JXJ`8 z%R-v91&6nJns5is0Yn_4P0&mNs^a?sv{mK0+kN*iobKqCz+_ohGCuVnP^UCy*yc+n z6)*xS8PmKeE4$E+LB2cza4)t$eUl@vUrGwquagBmam1<4E;8}NnzA!d71t*AeRUCo z@t7w><<&iIzMiq^G=D}3OxJ!)7`EP%EW>(NMz38zR>NDXWJvN4i0}B1#jecjKNGKbB0|wggS| zUs3*hC72AutwIW_1KG7C+!)y2V%Utg7Hli-;IOEZ`tK_Z87Oa73}eGD5vtaTU7E9i zV`0^ysN&_g33We%P!2ewwu9k)O<6h*Y>W~(xtVV33D#Lj^YXWE3B8WP)wzZcar1Y~ zR>S4&))%U=l)Cn{bZcr~Rq&8z$(`X2G_tdsI`D;yD1M$kQZA*xoH9)=udTw z(OthL4DB^4Rt~Lu&2q~K4_BwXfYROWP+ny$)>bt7cZ*2deK3&ACrXK&{hwh=1ptUg zcboE`uNI zD;ryV4dW}1fU>rIP5!$L`3E%^m^B1n_0YN8UwoL*DeAJGpl@P}jnqj&EZM2}8jMFZ z>v|Mc`!O`8t3$^R2p5%N9yX)OCMkHd7_ZKDdGHX4 zaWBq(g?m+Wy5XbB5cm1fX(9^Yfo$o@`YGrD45I*nk>%JQ+ZmA_59~;i4vnowkF+R3rLE4{Zd=w2xvJ>a06 ziQ9}C>I()z^wh3*-@2nZ${P|;y7-2{}6tw z2xVtq0rKcDhmIlt4x00n1{U!78Fd54bON}RM6Nu)G8L#n;3A4I5r9eD-xgB^u!2WQ zpAD89KJsK{x{Mj?qsn}OK~Rec-qJZtaBMz! zc;-2p6JQs`o)v$ASP2MNkN#_5d?JdKi>bgH*83!2B0i+b^ zlA#1aIz&QR5Jqwk1u3PwTWNs-NeSr~X^;l#?(S}+oA-?O{rsQjy59He%QYYPaQ0q% z?Y-Apzu!Jn?W*9|`GJ*+?J5UPk_M+?%cU^u&{8%0#^U zZttr*;nU!d$iNn&+Y?RJRyFus6K`7XzslFTp0>8O0^HdV{YItUy}=8E0{~F~5l@v0 zWT!%Ud9>&kZ1m>7$E$|AMPOTpWpmr`>0Pk5A_-*&U;eAXDDm0x7{apd+}I^3;wtE7 zB3(9orrJ?Qm=WWe&CwFOv9^V5NStHN*Y*X+G`5lPm^xlethLelYrJV?^chvF`q3v+ z?~oAu4D%JYSK6$v#Q=}opG)_uKJ6S?dM2YY?mzEa!+6{vm}dMS%->y!;|)d?k38YR zf3!&W^y`a*zB9r6`art<=J21~r-$jP9xcq8wjVS}b@k4^d&Ccut$o&J6)|=Y#k5(m zdB_BBN5oMI4UogZCTkk?9@nK(Le`5f0M2JRR9!x}T&(JWtf=O_1)Ls)_eZAXBGs!W z<$6kw&xqq-cUOP_VsrGu@d`Q)fI5F%Qkf8nRQ!$ZI9`H>{1yNfb8-3Q?|Z)uwJaS0 zYrdz#UDG&e3rrn_4PVtftR0Ci3M}T1Of&Do$=9pqXJZSR4Y$3yI%igjFEtrh3>Uxo z>G0YI9byGxwVCrdbh}vyQ=DERe_hI7`o@(FRedYlYKO{tH#Ku6z_F@dVFo*X~^u2Q@JEjkW9gcMSatJrqG%H>YdM z9Z`K^mlkDaBjpwoX&&!ZooX(K8~-^l%2x@tZzY+{5Dv;R#T zNiV_ae+BpUq-S8@V4QdQ%NsLBR<$^|%iRx_j02pGj7YuT_dScq+wVY=PC0;d(*30U zOrwC6X}9SF1liF?pk`igl7njj*B)pTr>SOHx-R&Mh1Fo=lMLq5QROi1nfnv<|WX$@V$Y27U4O1rGE@IHqVLeO@%gTEJ{YpgoO-LIR=*g2O3H%>FHGT#OoDBc-Hz|YT&5_dDB>g`Y%8rsJ zi`?}GS6)a-<->P@E3yL782BA;KoNOXZ`Aic{!Eve%jOrpT~4fM6s2 zLIv-`CS$!J3bE81zRGLQaF*TM;cg{YUBuk{)*Q8bi`ZxOf;CJ2pKqj4ZNHZk40zSV zWw>D5C#debvqXQg{^W_7+_H=ImWKwP+_22G3NCGwz93N*2+IWJHNKY3sFA0s zbp06wIb)Jgt}_`(^T#8PM1<0aCwFf4O;_2H5EE;cnk<~k*s?vEhga2CtSNDDQ&-#% zwVezdq!62WdE$1rimmY0@6sjVVgv{dK%kTlcNqwXgTV{g^=ky!*fxP6@QekLMt(`0 z@($IWk9(Oa{Dt3_D!$FbeLbk3282Ey-J1#C`!tA73Pri1lR|Mt z&9{9c7Ytll=(jt98Xg`V^XA>RqoW=g`yAzE zwv`377s3&+wa$@fAJPaIiQ?l@PQ`uLCVgp9j^jDuL74Dv%8aJ7v$GkPiswDVsOaD9 z5?Tt7U49!7;j@$yP6lLYj6Qt8wpBPQEJC5*=TzBt;w3*`w7L*4CMVGHe$GHe{7riw zb6NfvOo&&vX=t#m>LV?1wYiyx+8r%OaLBe@?M@}75vq{X`N9KnB68X)sF3R&kq>Vp zp`k&2YBQTG-B{_09kJ};8Xx=nE-Dq^K|i7n-2X`i^ftxpqJ>ilAsx_XTtcDV<@9UI z%h^QxIa$eRtU01ZlGnJHP*t3WnEL%Dyv?@LInVDm<03a_OW1@|XTT`fG;)7jYu0v` z`Y2ro^?cgkWaX&r-siuz+qifMtipQdyg8-<9b7wVp~OPH{^DW_#lXO@Vd~;CX+HDI zofk1h@a_*BBHMo z9|{+GIQ?i;{H2}lg-NZL{W{W$hWV4bY9cTsBbcdk%|1`_O!>*qfvOZ) zYCpshoT;FYrnfOk8^%)s1{}s}o7mC%9Z)Igtc4uGN|MSasZFK$^5v_wGKU@Qvyt!3 z3_iC;OKjD=c}=KFbh42D>HPkxbU?_5fTPFDK{^Qsk0`F>Lkn0xOL+AjRqm8eMcWq2 zgg%h=kjBo*A<5c#1AvCt;$(S0O^29W7Mv_BEY1&BT#q-UWj~+QCdHKGs9y*IL~akW zybEA)yS+$l18z#S^tf?{mEXul%E3j7_l3H;HI)~I_T&H`5Yd`vcD3G0NNo> zio`G0(_=aH)Ye=Vn*4i{gom`=m?`aJPO>0sUF?s~*x812H595#6im&I)(4jD?WhWQ z58#5bIzb$~(^V^C&QQ3fS0lx9gUl-;VEVq`a&w<)>_52=_mOxDUKb3mbH6mJXR1mo zd%lC`=&(1dlu!*gSFyKEVC*wASZpVSgEY4k606#VSu-KNSv1`TA$;$NvKTT-_ z)%>jK%4jvezby?=ztAtNz7i&2?TSw`*{>Lc_9d&51~p4>c=fm($%~xOe&TQ9o?;XCYr#?cb_4fT za@JEgawm%3Ve2_;k3Z3d>Hf0Iu~GQyF;ej9#rM|R>+9t(L#l7ks{vd(L`UYZ?HZC) z4DtIb;!*77M)<0UHw^UnpavIE`=i#+GwRoZ`940tCe`!2|4en!WftP6p*Iho?Eg}{gwyZpaI!b& zx%e9={&6i-T~t0e%=b*Ey5`pU`gqLs$-*3JT!&*nS2Fo3chHSLKJPakR(g~~kVpm3 za_6TwSiKqczq$1nIEjL`0loEVDgjw~Z?^cv?_v`&4qa@Q^m*Kze?Zlpox&138lrTO z6ZH3nD0{4h;pn^*4c*vlEc4fsVgIenFqo@z@95|-AI+cm`L-Egu3IB{j(`s}yZ52L zTxpo_K)DL2bMgN=Y zp2@3VdQ19fxWC5v&~zx%StPkBK3>2&?NOiQ2L^=?8T`b$re|>;0mI9D7S%TW<#_;? z+$>ZmR~Rs8zyNqLE}l;|HpU^&evR0hpP&ys`-j~I6Mg&CRI(i-!hE!F!cJW}Pa}_` z7vbp8RN>Lm8r^3HUt)1(IFmbRSJ~7JYTCJi%DpWy&!Dq4$|8S;-L7R?|NMr{s;0Su z)_@t~({*PFSqDr%5+3^lY*D?~7G@Xro;g87_&SZBHGP``Fl63zmJ5(~1NiLdFMoZe zxgSmRYzTFTHUT3eE9Og;+N0NP>tN}5+firssUj3Qtim} zmA}=l|LHW)18Mt%lm%d4+=D+H&{5@g=RE$2O!^VtpSM!>Q@6yDeKLtutEo{@pO85@x3Q*ON~ue;J!j2j?v_m(gK>7HvelK+{0Z&?{Zm1mOpb#JwifEz0b zY@+oOXIVa^oAdzg&G4uW6TN0jaJv+fnCf2|GTt5<=z>sn0MgySe2+?L`AaX2of7rb zt#>Z|P3B5{fkPHeKh(jU|FYalk?Nxl;7HE?!1}@kRHFlbXic+NME92Be5y!nP(0xe zGwjd7D6g(<$;f};>)3>Pj^|&A2gYY<_<|@}77|c>-9xMO=1hayqUxc+9U+|lElaEC z)jQjAYhFwDR-&B~rKijBJdB*8(UoQoFqE&@ELjNemx(#szxqYF*Eq1jA2XLsUmr~V zY|D<+bw+=7Ww)G7JZvucY#50xiSLfMsn=~pH9_eI7||tX*!7))!&m|D_`I``7Je&L592xXMm$4|C-J zyk;K?QboMV8bA>%{Pvyxi~3t6>`a5=Ib%H7I|Ap;7JNG+65fmWSOu8uo!DWby~ZNl zh$DPjY5mu;E5-0#%yfb{2|f5K!5;aQ%4ATt{CUewUc@`PPhgC6bo0UDU7u><<% zb-CVz^VEpq`H}!!yS?$jv&mqI>O*)gVeB=!#6_k}wiFxW0(r9z-$vSKQSu2u@l(gJ zJq@|mI}+#h&2hRT#J*T(Om#uICnIpCkIX+UH;bA3m>uAHWU!De>O;BXz5G2^&M~lq zwDTRZmR$=*?j98eHrScId^L!bFbP(Xlo=fc0V zvdPvbIgpnSU|I@vl{n7QLSW7MkzNAX(Dg>>RGsSwRIFwN zOV{l821F0kZ*ecT+r5mQTUwJ z#S@H0i8t125`A0NGUa;NU?RoP|1PlJcjB%9@&w@Sl3$5>ya0umFG&;B`t-cuf;tZD z%}hCg8ZKTQ$Ix$d?m^ueCcg~Rt2?Wv^)$iO5MRygn$CULwImUTmzN}1@dVcAC~(-x zQ)QztKvO*6k|wfS=69!72g*`9B^UMlq1i|Hs(lHr^Hyl=3(PY}Jr65S8YQ$}rQv;j z=Px_te3vbm@@@3=7_q2CmJ8&^&xsM;SSh@T$}UcDVQsX=2VE-`UCapB1+Y29S<^6~ z+@HPTJecZ-I-gfV>$W;A-|AG)l$WV|cuX*)zC({g@xcTvl++XEF0@VpSPfQ-*wee9 z1!jXx02{W&rNwX%?nYs6bfnGeAL1w)scToM1dkNb5%2@t{79mvWzt>Jam#N_YqRW3j_*Jw}Wrd~b=;u1EFd-`WCQ zCZhLb&ougr?veXQRN{PFg%4)w(ZMx|#u@Jok!)F`I>5}u)~CAXqJ9yfI@`}vio0fZ z8D^>{7T^nn5hgTXJ1BP#%7(D4sFM@C_e{ymcW6?Hj8Si<=t=m>c5yI^K_VU0@< zs$8pfF3mtqdIWp74|{i)QTr~&BXpsX&xpWDXM7So=O|Ya66p?6+|MuiN7TB5NOku+ zEKzhUCpxo&LB2^ePyx#}-k-Z2I({9K6nIy9hXFo>8?zK~0{zj#SuuT75=K6ER`z&K zei^0s`R~U;&`I@06C5ouROUSJ-&d7aw2^QJbg~wAEAkV$MuR}Gw3oh{?GpS#-)$*R z&BJ^i-iRrtiwRAkt#%B0Y$mh%3`> zoYk&zAcFEC;lKk3g*@#v5Qu+rShTr;#EZzmD!3Dd7g1U^vZpVr$3d-H>>!*=TY4IP zc%fXyc4Ga-pEerHs+AaAQnu*F>X`tY+p9fIPAwt~_4b+oH*Lb%tN6oec@TxVYAjT& zo%IN&BAnV{LQ>fKB|{x(3i%vkemeQwH%_?2N#6P4S}dE!=ER5Z*Spnw{1)Tft|!}$ zbM9l?R-bPq8dO1VkMn#it+$bw!c(!uudb&2=NYK_T2K0BKjP^$&<0DV5Z9g=7d;Ng zz2GFA97QQ736YYJ@V;xsm-2o1;4Vfmx+ttz?P0?ATB4dZ0O9%M1#@JIKXo)OO5^7& z$MnYXe6KGmD#BIw$6UP*d7?{0CGOz{kTZS#Z(y|aZ0e+0`!?21~L zuX*TP!p_Afo5;A^4T((RTrxKIf7YJ0qKVn~5XuGMJLMmz?unhHSTD6QDa7Q%QL`{q z&Eg*?{7`!nd~i22RII*pZg2*-@uz`k*mqOv7@8vK6Paa6WzI9)z*6!q9SdvgF_H-* z>I%bVWUSG|5X$74Yn9DPE*w>7wa`?cQ=yzF@RfZlHISM|m?}*i${j87?J6iV$l-&= zqO8boikAf&a{7OgYU1hKajS~`xk$|;p6zsouJ?KCel?Gqhl}TP*$xz}RSbu3G0@Yq z9%ZNIzl>sLl8-)HO|Tq}UrTlkGYg{@%^gj-11e{LGS^3nw-&*TKMZ=r1b$8=U-aAs zmMAcvH`mtaE?hB<%J^e`=%)C1k&O zpOP|156U=3U$0$m5x4W44GSN{=FNSsv3ql3_#>n{Cc7y+Q=x~2*15&{zzl$`GxlF> zbsF}Aq*DT5hZm>aE`O9FaLGODFJRJS%LcgB-5(-U#LgKfqEQGAM4c{y2CJ9X4#ck+ zHJ-;eL#_Wiotw`3&Gjk!*IRJdPlm)^V}AL1)%@|$U_qAtZ5z$nFPeMCr=%1#_D{GO zA2-rdd6XIVJUGZh)m_Lx+UZY~+7QSe%2LVW`%kyHb&W;v%*_>}9AU!yE1I*JKOYy& zWC>Pa;-lVxg%YdPdY1vWksCet!1aQ(rANQkMWaC5es{WRYYauhYmC2R${xPj0X9LqF1kWyV;l-Klb~hUo)!iQxR1R(R{5) zwtd^M@57?nxbo>3;{#xsounfTC`EuC-~#l3dla>Nue!lakx|tWHxjPPjAC}t#HOjX zW?BOS!TBTExFitpywlO3+ZKmn3tn|8^v#6an6vrM_?V_MMa9I!uh=KrjcVw!Y935+ z5h2Oa8Cibx34YZ8Ni7=GV_lFcN|iFOWA7rq|&U9}?oDeo1nXXCz=gqa@4{_pN=!CCc}&pdA}W{kPAmd=R9R z`&Jl`Z*mQ+0L_9l?oQOfv(^M7^3{UIKZ5B|2k!I<_<^?He@6;eseozfteN`2a)rM<5>Th4t&^g! zjAM#!bKAcK;z=~Hg96S-A8IiV>4mC1w4Tn+t^2}D=_C|I_eE(v$v)!4+lFz#wk{pZ zkuV_61SPCvgMH`>J|jAUg@l#g3G_tneEmMp?aiK(7sE zJudU=!UHIGuos;NvYNb2KW^b2rCR!XMwFAhzGy-<@`Db#d)l{;YIXKiTyG8C`}6z1 zcu1tkfiA9tl=4@5!)0zd!JvoBYyLg=tp%*92KCup7p@llLQi6W@dV*JRo(&CC1nn< zyWhPIypeFuoNE9VSH9HGXy2Qsdew`3e70~rz6k*)IF3SSfE057r9?f#=zCSG-!Q?i zGoR39?m&js2z?`xsb4CX|4fz3wp|cFU;eN6A=LO*1Pth${D;0_JpT6*4>3#Le^z`x z6Jf(}@x*nnYH*jb`36Rkj$ZZ#u-%2mjSD+r{5-tK79hH^FyG+)i`99|fHj!w)hc@x znvj>G2{|W*C_3Sw*9P9!saJ_Zg}gkBYbCOJtva} zkT=fyADFXbmQ^0+q@x0#v7wIevkq9I1L==iD-W%v8!A~2yj%spH#GQE`}-WghLQ+J ztXyN&<6bV#a~&99483qub~bCY=`F)(#tV>WN=^KHLpW)h(Frh`|7s= z@OwS;xBXCaC+7a07Y@tKVKKl;gJgx>@Ib4?kRH?(z2u^~#S(wu#+}{1(>-7rrr56u8tsP)2)9YVm zC1|<5p+H0}m_PAzH?P1UY_=xXi25^5i|xVZTT8hnkH-_X#6yTP?W7iBB*_C?A-|lu zi!B0Z!AuNkL@l2`667ag`8-E?POYrj=0}b7a7cKIHn7)=4)bNo7Ea^;IuXBl`1!-1 z=WI1(ryLLIO2@4ts_1Xj@if7dZPt{5U_U2qRe&R60tOM{3~X5+hM%TGnbe{#zgt^l zxDa)4lszTNC^JR$WnsS-I)X%E$VPt z9W7k|fov0tl}U8HFrW;pEf#X-Q@@Fj`q&WRLpOg#GFdjkHpg=%|8Q0n%Uc!acT3Ch z&J^7BSqEtWX=ibM!RxMcnb5TRnr3FX+EV_DfceJk{!G~?YL6&vjoTSzMgbloA?rH< zbM(@KS%{tZ=F!;P87>k|@PA#tkd{8)n?tysdSTpcPZ?%#X|B$FXR@r8EqDvA zMgGvgkF)=P?);Vc4yZP~U8gUB-$0}hz`M_cuV#Go*;Y;&t~y~&=)=huUgi1!Rxv33 zQ^kPzI91+LQ43({fxjihWAK&(Sd1=q0;-A4JNT!7$;KjWEiTI>l3MX;8=o%GXYoXm z0YnJNjvt=#C+o+!~4+Zrth{_>^jbk_BHw~I~lhRsvx zd+lL>IbuJFbHDoziYEnEg;9Y5BumOMG1b+WOcT`P^ecu zcmos4PIW$9MPFHx{OFfKBWN<1l#=p%Y&PH1V|^({J*)H))*jrse*XQy{<%i?!#7l= zCe~Iw3B%l?W$j*JcxQC|OQ*W;3v1w5U+V|4_@gDg?t&UGH8f?8e{x2AnLcCMU~c4< zBU8Sem@c^bc2`D?(VO~`7JggJoUQbp&a*}GbqiENf)4rvQ52^nee&I^&%NX-(cg#8 zX6*67G*4j&{W~Gfc|VD8(TKdJ72GY~&s($&B;K^wxdzfrx8{4g4e8mvVs#RgEmkb| zAy1|)F{<3(NPrmiabM?VXGj-7%XELM`Lp8Qhx*aJbYms^0m99O$eZinKwi+&(?W?P z!DXLyUw={?79H)FBtV!T$bLM>qHuqwKDnXp}Vn`paARl*k#yfc;* z-Y6-Ih5Z4C*X6Qq?S)`NC@zkKZcaE(+HFu)G%i9yf*zV#$0m4WOTI;On5lQ&b`b8J z`e|3P_{qseh7PQIL%^E^^cZ`c_Yn0XKL7;Tm!zd~XZoRWOv>+7$MZ!ZY`--0 zL35eX2MFG~npgp%dsYljy`%f+3Y(yC3Yukxye2#-6vr5*G+i7G+@#7$iyx5W)Aru8OQbpDsMT(VuK!EDzGIF zaP$f{)zf3-_j&yw1WiM;RS`ISnx~{=PhRR(&RPV=QBak-S#g)0@lyZ3>jqJxb*A~P zVdPSnf~xGuwE}}*fJnFUT4r=nKHU$ft~j{|lSJkCWs)f0gkqwe<2|IFRhq@K)^N6# zCdRvl%k9uM5P0JrYfJ=o^uw8noZ#OK(+92I4wd+8B^~zdr_jtxK|mefpX-_u13FJL zY6^frFH)YnYg~Q27d7nP)uVq>aZ7@QUGRuX5!ONQb@UI}J#gfygsv$m@Vwk6dXD-p z6 zJu~Nu8piA{yq{e1%A&GhBd+NP1GCLH!+!Ws!k}OIGt7Mv_X3yl5!i_ys*OOd7_7k5 znJW+njZv#MLjIfP7+iY@-yS1DfxBd; zDQ<@$h;WLNZeD^LbT^FLRR%Wz2Y<8K`Y`z7DOO^{cvy6 zisz6x$@#Q;RNk-fZ2|{x&yWAeJ-W)tzF_?fTY8$>e5_$@ZH>+);^6^EIF37}`=T9) z?npz6lNk}mb8hB2kK@V|=&Jo4)@_RP9CaYYzoLqn`Cz>)M|6*e3!eBlGi7@Y8H#hu4$JDPPCNgl6tp z8*yW>Y~|I|)I8ub+n%Y8J>WSa{rwUQg=C`qC_~+hM)XkrA$#>gYbw?%r(bKo8e_)1 zg;7n}N9W!xtF6<|>m-OF3pm9T97uV0tl{h|c&w}Rja#xiw;xWF!U=uO?N0h;gnY3R zAM_o}Jeen?J9xqVR>pGfG`HgsvYdqC?A7Dn$j9KbVfgi^(_+B&9*cul+fwRq^XlBu z(NSe(rH>D29Y%)N8O97%L?Cw`m<0T?s_k((1k~x6pFbKbD9?ikq{!2rcjaZu;juV8 zquedQyvQAOQ8|MD@@Q`gz1Upth$=H10j5^#<{0kg=5~8?4S+OVZSBo zcPF`!(n9%^!}F%~A}Bob3v5ukcNjjbg6yhs*d8A!H5~$Ar0UGZP}ZZQw+l51IN6)9 z+xVOH8Xj+nZ7O`icyWPm`ZslEHnJJD;Kdruo=ihNEZ!F6yJI8|<~xt-NE|aNSwKE= zSBXbbk7Dl8GCPY3=B}gk>DO~yHgKyQUF}51;R!BIX(6NP*w)q+xd^vOab*y#yZ^ED zqb#QRpOIdI~i3+s|iIESNz5`rAye@>6Kc~gS2-- z=+w|HZuRg9ABaagi|nV+UGs7ihcsBc)ik`JG_sKmtp)#dFUgoBzF<>R<6ZoG2PE&V zVKdL4WluoK6^PIBQ~4XQB1Ot96x0{JJPBj${}p+s+_L<&u5|wuAfW()0gk@N<*v_J z*lb$C;<^;R!twPrCL}ce>x7W!kNEhB8%}yO(SG8+9d!PSmm&g(a!0q&zZ-IEVo*w_ z_mn$L-XMV6U82$Yp+7iY6a6^<=l{4BjJP_sTjBDSCmyAL2o?l2-g?PPLAoDzX|4h@ zBN=Z?8%C@BU*E?A$K=Yb2?WHwWOX=FnK zmBpOJjT$`ng5V-3VH#If!fOQnTw4S2R_d8K9k1anJ-3VoMam!)1UMnsp|F_+qt{Nt z4khLN6|y7JNFj)o)I65^v5hP*IH2nzXJfa+ka78kv$1hjMs)XmefQs98n5RCIe5nG z?R6`O`7zKMFk)yT&gVAQY&hOKoyA`75Nu?46KQ}PNj=gP|6~^h-{~>6JYaXAC^dZ# zy@ty$%Z!6C?m~WECr8Ep8fIo~BRe?s!grkFBbPd%z69V)aSg)*CR9@MZLvWtvGD4S*%i^~JD~rG1 zIYMG+5iv_NP=NC5*hJmkZ|pJ56Z~Ha`GYy(Oxy)e+c*Wi9gM({;ky2nz+V5aDv@;u zsGWP#@7cZC^V!XsmK`R0s@!kFi`m1abRdkn?4PNFzer{@{Xlh= z8~lMWtRYq|g>z48Q`A_z0aMf|1wYUrUcYvrl+}CI*~mjansVFua~e=4B^GESe==um zu@~A^kVH70r!F^dqXD|7Cz!2xlwFi8G57@rg-;WC)eR;PTZ=*{WF!XREw{U?Hq!4c z&Rp-3yk5azJBEJa>PGGKROlm$-T#&H9)zDkDNdk{nV76J~Tqut1BQs@aK zrQobMVb&z$dwVdFU#wL8;;%9vn=}QkzQ7{itk?4}KWV~+P)JA&!=2O3&N}m|b%`h+ zcrfI%=VpCp8!wx2&$9SRViL{Ne;G*MC#Ps0Sa;F1G;mTfy7WhN^9ikYBN$?(IA4EZ zW|)||+0_1JQQ2hQ74#EbCaWALv;uKWvb87$|W8bA`tnaraq3N49q zXXCG=xHnqE>m56`UALySi4wu=-jn)>z}V~cn#K4#7pUpDb|Su5@C)~{4@&S)WkE|+ zU8gK&TWO6NbdV#iv#_3HRvi`Eb7*XzH*9mj6^~(CLc|6JM9sv=(()Qt*NVp>Mz{Bd%x5_RdSlWLacb` zhU-)<3bdUV7`;lz#%9r<=~;D&E@0`m)esz8GHhIRtuCOxh8K1%K)xYBwMY~`;4b*#}7%YEKi*#R%{qx^IJA;S*cE!K)5j7s*O&1<7aDBEyF3=BdQut3X!Q3c<; z*EsLb=8E3DN)h}bQBfkTLEYp7Cme!{QnzPd=C13^ghVqitF?sn7)~F?N2Tr~ptLe;95FtnCsr zLWygp)7UMVgQ0)-`P<_$y`CV*C@>D- zE|9vp7xNhR>2Ko76+U2CjMm5_yFd2U(hsjB5l=P%VH1Y^H`d8UK_G&c zU~MP7O%d;}l5OjG9#IM+8ZXt0#MkS8J!xKmfVUa$xEn>+O>&B4C@IXdQF@d6QI7X( z4T@-zUF5fE3WO2C-I-%Gb6VM8MfmPU2!81IuZj&!#(s z>ouGF9S9h+U!_l7F<(NOHMH_8Fxhk$W+mJ&OVS$r*}b#m#fskORn}Bdpim>azyjT| z9$7KIrzwEcr)#ClBQpGiE>Q>R0KleB8i4Ua` zR1;z6;<}ya^KfW;@;|vgTM8d`-Cs`+FGjNZ zdKrNY4GpQC<}u0ziQzxa?|R?S7(`(F$jru55L)>Ch2%A_ELQMzX^O z=#KBCVH_M zS?~HenOOE>-i8T{KOqeD5aM0I@K6OlD z5257E)X39JdpxM{qb&MxYONS;T&I`UrMhw3!00b4QJQr`GKmP>rO{RP(k!O$ zI9dp#@woca7EI0+79O5;Jn_4Q@&(Tk{Lb~SL2$v&R7LPsu%RGP>}@Zt+rJlT_t>Xe z98b;N#3c8nWt6m#6ZBZEv+3I0#duJ^J6A8>Q=gXOl_cz#EbduXc^%rfmQJ^wsgy8U zsFy&^Z81^Ilq9hkeA1TM_#&n|fZ(BtP|VpdT~ccuQ;e2+#jFd=RB%P`YS?e@<9fr( zut65R6CuZ4D)FzKA~?KOgR@7&Qfg{yzE}h%J7N#yGuyrg2L-(`e;aSeRa8WkSwV`m zn=edlJZy*H;pKHb*?u`Q`83skl%D#Dp`XQ|!da$8O_$oxg6l(}eYx5h;%q?e*4Ea+ zXB5KXezOELDepUrK|A8l8GMT^^(L=-c(D7Skk@iv)O9IyFXdBUn)&19AE(?;XJiot zuT^WUw#SPYyVu_m5W@$5b8K|W$3+$v=czunl)~opxURd{%yW&4Rg9k`%`BeTzstqW zo)1S+i+;EW_q_5o&W;>Sh4s34GhN)3f~)nrK@y4T>YUj{zVaa(eqU?6@7s{(b>H3 zz4doYqKOJ6KZWep`>P!-H^Ai*-v$0eMCPj@G&=8_kH*+!)DS$XJUdQENHABRMSgsG z)sXX$MO^%Q8P`e2&{^>t%DTs+pY>MuYYVuouxI}4r)$oVKk1eZCX+tdDdgU7ubX+= zDX}FNoX}T%A>U^iM*P@JqQM_b?YjMBGn$oy18MkO(3bgeA&tkGWbP}n*T+bHb@`>y zVUt5mKC3@lvHUGD@4xZUnV3uK%p2;IatK_1%Gayb6d8UepK$0>IJi{64#>3@eMKYU z6!Ga4opx0|#epZ{WBH8WbjaX^j0~2$`#`PnF*13MO7y$BE*^+TDn;eHnBFhDx6P@w z?Hj)3MqOm(YMPoz%1Olzb{&W89-Wbf78D{L_52XSWViF@+h!nu8E66O%K?LcnVOm& zRa+mezxtyZp40iO;MPrawiD#o|7Z-#NAPi|TQQz3F*5Rb2V9%-rSIcwN2L->f>|u| zkWOiC&)Rlps?`GvC>ZikPwx!7l~~b1UNCw)>`V@TjrmIpbSl&ckr%Av6+en|yZeph zi%qR16Do4OhY;0$;xs6jYQg;b>|x@Xa3P)9U{vXc(FDu7iK2IHWE|R1j3@d8neS8bMTX}; z{%kP%B-^@^$=Qj1#We&owCjf#SBFSJ`&GPzyh%t%w1iz+vH89kCX^Rj7|hF81m2^e zCLu}BUrUvdntw`(QcB=In)kIBz#+)AU`%P8GVq3_&!2>uf7T8PbLdUW`m(X0RU{Ms zxFgdD6TLL>3o49?gk%lV>B>JHQ7jvjWG-JyCQJ*I*@XW%!1joM9q9mCWR$U{V;`Wj z_NYEmEzb?ioe`K+%$cl6QA1BZtTqERCuG0Ly5*dmV=@0DC!5B1b)nzqmgg2`F7Hs) z5{NwJe=#rLS5>=&4Amxku(QGKv~N(Pmi0K!sN|l#OBYt59Uth^>H0u=#vfI!#;s$u zb|DP5)Ko0+v<$7VaDtewW&L$Ig+R~#kN5Hlz5%N#Y`mp$xHA#XduC``h>K!WG9`jL zWYJCFoKS1BS!p{(U=f+T-#`1aBwqnDl6S6Z;X@wq+s*UzIoB;AgS5%WIXLCRE&A`9 z6zPWp!@wgvc7oyQu6}$!z5A{$Y#@?n(0rlEKN?~}4FDYT(+vz(h5#w36$gfkeD>Ss zfYLe>W#!)M(>WDhYEyRoACi`3AEz|wrFfsrR-dG4$ZS{(P8KJIjdS2U{}vgtuMS!G zm;uVF2b(U@x%ho2TX5Sc8#6KbxxT)BeR+CFlG7cA7a#s(cJ}7u~ z|G87w>@M!z@b)Q=YNgI|xZQJzS8;NhHWmbm zSmJJSGsuw}bL9F?k~|NaqU{L9{h_m(Z2N$NwBH&HW!4>dPdaJ5{;dAIXQ4Up97lcQ zs`mA)BB=K+UYU` z@z&2iO;@UCDHl0%_&q*(s@zFeV`S;*?=8E$6Enjw2tP9F^(fu&gWfTUU4s61H-tbAEof*21ghE-k8uPEpmXbUoeeNq9E2>spGLRf!PXsVRToLCk}$wB4;e z`Q`M7UdwtzOHXY^+#JNJiSDQ>=5Cy?eJ3}62NfhC$|B*0-quNJ&e7ika7A%sQoDKYaAz=xcd9hb>NVqkd zO-V*`qvjcOmzW-kJyrPa=(su8L{{|DLg&9x!hAH`R;im_-L@* zxXf&Xj8m7j&fin=|Ms94HYk=0!L0YxxSPU;nU_~Rnp48lTpd7A;>(zNJYqXo254QYw*8=%U&aRg^8Eb30}G}@nKO1nN@Cx` zk4gS#qx?*2X#dKvHHPv8NGT9x901F{!Ws2sBIqt+`@9q1FXSjTq)Z~e$ zcq%7xSXbJ=uB>AJ3LCGh0!qey+s4CZE3Va(ukk-^2ui=g;pw%*mkGA(reW2Yf)QP7 zy6Mu0TQ2)%itVX-dy!`rSyE@(`g&%!JUbeG$=akeU#HU@;Z9@~JDeTtzTe+Jw4B?9 zuJ(0OTpK;j&lzeaUt)Q|k<~JrqW1Eg@WW`DZyDY;YVP#Wp26UX4*&3r`J2YVhTubp&nsL?!E+ColR{sG@z`U4*6Pc7+gZUqgs4ZHmxWOW>9t*okYj(;J{ z*f1?W4DH@76T=tj_&3~<;qHig>{shucE?}XLdN@!@{_fdq%!Y(v^;X@tNl*D32d$Z zKs~HG^*sDiE-vO}xcqN&$;sAGvK#xDVtkX<+~CXHDj2vB`7 zT(XA%7zXUEkSUZQiKqypPdZ0HIWq4!m$e6@)3h6$yz7 zIVL9n7C`mEWuzI|8|-+PNFl}mE|)U1l4Ro_S|MX2BcrP)e<_u$aLC$<*1d0Rx@3hl zC|m#wU;!+E1+V}Xzyg>D(p&0`y3K9X^DCpNg2w=85X5{%(tf2ff~PB^KmZ1)o`yYU(hUcX5!Kxr+<9lLL?%OGxd-sn~2Z}g!iId%$QsnMBW{r1a;HcYZyDaO5_ldQ| z#-W!(@?iV?KRdp2(~bYfO(HJvD5>*O08~VNgZLbF?!wYH-%4v8pb^ywrsmQ)^y8WI$rPEW*t8fv@xy1l+kOncQ;kLx7%I2 zw+A(Ef1;yN3Ux0H*h<8tox zUZw8Wm+VH(IuPXf8v!++AH#-Og3$nW6w}gq?+*d z_Ci&d7?=vvU?6(d27WDSrv*$9sa8|~H2@RisFxxch%!hXgKQz?O0+?*s7dJo0aSln ztOBBd8GnYLot+9Y&!;y^pfZJI*7;0Y*nufkxIzw4A$R2U@vjXJQYyr15t$%d0#FgK zJp_mVAkdxe&H~<5qe>zb2;%v8D@du7Ot_MdA$NozJ@O1$#)Y?;27+t|qJcCRgkU5R z1UHb)l|l_rh=1GUwb>*qu{a6{Ky(WD>IX3BbeImr0n;1;B1}>(PW>YXX6+ybeGZ3! zFbvJJ18KDRV-e;&j>cfm<6!#S=fX^oHrEazskw6@?dafKjt$f2jwLzPTszVqbN<>8 z!@19Lgc8MNm~!e2L-16L+&_RCtWc0Q2bn&6KY5%2m{HDtJn|xOSP;r$fTCCkq(gMJ sm<}>fItvrCV`*ZPhM^dj^7|&!7g0r!cVp_{5RgFwDbCLP?LL(M0QU{*c>n+a literal 0 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

    RlReHa)p<#y&7 zHV7&#{ZtD+I=XuSVFZjO=}%%r&PXDNISRGPbcGLE@9EP2>hY374vUCbF}nLpTw^)1 zesjLCF;T1z43nVe_&v$%cpaaZ*b?O4BPlu|_2~-&kTAr|A4wGk{jNc9JCy1=+pcHr z%Qbs6*~iK+y9rjdMVr07My^vRfNitp^{sWIz&1pO83H-@O|+F2l|4f9ifvT=FG z7l01>D-~U^t`2mR6}~iwA~YUXFw?*^i}UTZkH%_qjat=SgXa_^kazkh z+q_X9KzAiLR;p0VwkC?9%}07hMsD+d!bnWNFb+c>U-jNFou#jRq}L38qH;AJMVKXL zd@;hJJh%t=#vJ~Nc%Wb}YuynZBz4v$>`t_qj>Vnb9m ztyVUcqxt!M+(6BN)OOhiDby-d4)IQ>1O&q5zCZ^pAN}gQS&WyTKTR<+vdhjMH}{oS zi=h`0Uq+TnVZzMijxe{%r%F?$zNgbWHuOT6_-Tp8SiK;8OK$k$_`Kfcx6Lf+&2aYe zv{$Nqr);V8iB+DFx-ZI_9boS;{89vDfyP0q%~S zYa@l=XJh$>Bd}*Rql#ccO3e*$+nRXL=;5kmb3|39V5H$T^8^ggsk-J~szGPoLsO!a zZxzIc<=AD2Tc-*;!c`}XCFQnwy*`+x)97Bd!(@iiP8C?ajXMx6@kI4QWSdDr)eZ@K zGW^ldf!fmjVFzWnpZRpesQ-41|25sw@iC{}^fS1W6z!8Ig-3hZ#J7BWeA@1cTo_Yt zvOY-JB~b<{exTU&l(Fv7<4<6hvVD5^VkxSZfPzdGerpc$%xd(*fL~7CQbGEdVE%`u zE55~`Kn)`Yw~kpGOrAY|+0fjt&r=PYM8b`eD1#9wvCNpg?ZH^)GBgBuALE!Bm+p&| z6~1kJ_Txj~?4!+>Uw%GRA&z|4K@o}sHxmz;hyMQoZ0HTWXbOS%XgFlAuP+Y&{QCNN zN5|}Cxox9lM1L}6Aa(_EatWVPuEbaYlM16EYZICBf%_<&J_a}!5c9Lwtw@A~gc_n} zbFL%Ceen$SOCjX+Pw42zex1R+_Su~N5KYEVex49=f^&A=i1eDZi~K$`TXM2mufM~_ zCrvdze*r{wa7hVt)1uOHgbLcr1)$oM$gFNALl=nu^I2llz{)6V2_shZMA$_b0Z#nG zncvK*y8Q3g0ugR)m6qb5h(idtuD@;%dVmDI)3gj>!rSw`C_0(w51OB~;tAqa>!pjt zBYwk#&`9Ul&0)`3KUSOK8FX%K#aMUUYvpc&=M#tAFwt{x;2L~$D?XYIz8fAHNx=`p zmi@99ek7U!LL*S9@cJR^1k(&Bf<2cX-56~E9e*>@cq9OW_Cty0)p+piu1)$Ax<{mu z7?@I4*(z==8SkdXtRP3X^BWhK{+F+KN? zJOPD{0$67m8S3pluzfxzcK)e(lngSUfdJaFy)uvNTROr-{)%ofg+~3$1~~i?Nppb4 zMik$C{U6T9cz>V%sHUT^W~0{dpmOV)wP9f-qc>0ude2Q{df@&F9V7sPy{{6L9*V&G zTujDQ?am3at=i&ob|?=#RVatU(B!l=jN9Ulsyr9sp#1$|PFfp4Pfy<)EsnOdst50DU2^^ zC?7o5+F@#ZkfZq}5j6ZHts~C#8iN-jIzzLyy)j?aKqt0Lxl;wxBO{-h7DlKi-(wn8 zQ@q0jhx2x;9uPhgmPBj)y%VrQV)jyum7ofWfaoAhp=S1mBoG1Eq10=t;&@R6s?Xg% zLv4DJW)D!o+9|6dFnqLxk!R?(?G3<8vORp)u97Mz z)($63eyk!MEu`yC2+I}Z{Ef#J+VRe`C%SFTr7!x0;F6q5XyYUmK&60wP9&_vvrMa4 zH@X*{CaOQ|*{I=WR)x$o(A#HfYHD~?RMCK>Xc-yNkq%N?u2B_|+2S&z&G2y^y?pMI z0VXI;<<>1^LtXz$RF)AYMc1V1Nq+VgW-dbuDJ+MjRd}c$@6q51QR&PVkJN(o>x>A2Qn=L zR4C|1K8L8cCq-hKmxeJ^B9*>Z?s9#yPgL&Q>UH;S8HCWYMB*Vnf1ciw2zLQF~;_WnJJ7$a;3u85TwbuHnJ%|$`3_1R-N)+F43 zE=~N)PqZ2=hZorxabOC)wu*=Bwso`4XFD@CEgmO-R6D!6E(P%WwEWafX*y{^B8G=M zPIw>WnZ!{BVIbj^eeD=W<@-RQ1qEtTKOri;hzR9~=oOp1g2HFvy+hA~e%6ZD)0M{Y z@p0JM6YrcD)<9OF)#x>#lMDQy*@Pz+5fl>XqoeO9tH7U_g^SEal5&sMIT`_mUHQ9R zfsQnoBSsCqDevOydI&09d1cvKSZ?|Uw-Xh>8f}>`9Xd83zrxzban!M7T1}%zFv1Y* zCgI=pHiomRtjC{xaa^FP_Wvk?`5`U~?odFw8@_pbfA4#~v)1|J92RSBS@G0a`@^+AKhk)+6r-dUoD)b0izGK#Gws2S3f_r^y;RPEkKLP=f*zw{{N$ zgb(z;7SPm4a3GvSA=G?{G?xE_jQd3`HXyeT4Qg#7KHIfW$c5p`>*9OdU!fCqx3%}QXmr~1F)tRaa_;oxjNsE1Q;p4T6$yzU~d~^xyXxbEGY%5fkF# z>}U21^7FOtUT6@zi__AvG5Qhy%em}!nJN#Dq8!n5iU~lHX$Lum0_2z@8F;|Ya+LLS zx5apx0A|PNEnFUf7a)fGy)C$t*`v*S=w-{fa0DaA{}mkqMS&7PZdbu?R^LnjJgiG=3!(+BE!dg%E(M@u{LzzpYW|W#L)f4E<*Girn032 z&X1B+U*k)9M$zGN6!X;|5g$ceI1TD-qZ{{IsQO881%%B9s9)xK$wc)5FmP6td%>-R zmJ3oVv*xRFY;5B3R7x*Wqr$@&#DdyzlAkujsAV4=A zy>uFU=zi441|8TNh*1=^ibOp6rKz^EIr?`a96Cyv;rW;29yQ0)!Ds@@U|k>dwSbyD z__xaY9dhC}?r#CDL-wk~-=SloAegvcH4sl3dhv{nj~}i^@j+k%t%jKQFr^F1Bh;rI z9XTSSIoZN;@o`>sj^0y2wIP36^{IJWc1`~}@FmiN#_x16+aTiUz`&u{)!6Xx!XtNg zci|G9ukV%2=|h`Q*Dns&pc?^#@nQaaEf=df_0nmoY>R&PKGkohR2AUGL6Q78{#j-K zNO+6%3Stxy2GEpET~2%6uik-ZLS%jRDfoQR3lC57g5GjCBf7U&3A$7XItB_{hDnTe zD-H1DSgE`4#=VBGN3B*rKWgkAudc2_uqBnBb2TFRrDNs}4eEIe7m@I|h%(XY->8Bp zH2>d0n@z5}vn$(Wt=%8_%|@C$oM8T4rtN7WPVPA9ui`Rqmw)Oz!Sr{35_HYivSaL9^>H9)0W@{4knlRA7ZIti^nN*#tB40`p%!4ssB{3VA>qml=#nH#QU6V zVAEnlZ*OteooR%yPK3|9t=C)c=7w4`>)uSL=m-hGIt{50!BBI7veBk}QErUz$Y%QA z(#F)cS9&epB135+LHbpe$|}mbjI+*i@+o#;?r6qFXrn$b18jnYGH1# zcSN?nv0-+yISSdW!uQq_3TS9i=Yj8yyU^Ror>^iZ{!Y?3z&6ZAvWm?JrS2}wgly-@ zA@-7xnCP(JGPMG_(-TR;#hjlvKoE)9<3f@)9#Cx3S@73d%YKetD(b*lPGQs@YH<;` zA#@!Jup4xh9yz}$ax%}0P_|6(1R{bl5(-!J$$F=a;`cnnvc}t!rQ{Nx&cD}%VQ~ow zfkm1H{ozWwS*KL!ZsS1KD}}jo)tR*d&KHFEG`N6E08>#KG%?W&ns{LOlN_ShKu0h# zGTI-l_x0Z|R$1XiMn;YR;N~1|e4xRw6;?+&g1(;=_J>TuZ;P6nhX^FjqmCg#A8G&l+QA7(1i|=bOV&&$237o?s9!JbW z>0)6RM6B)>sT33x;Smw-V6yE`Cv?qz6>3}nR5h-e0b!=GJNz_LlE%cuMz*=Mh5_1BE^-_)_htfucqxjjg*!l~drqf}{QCAEzaB$= zQR{B!x?57h{hsn+asQ7{*G8HkG`wz*S1_NAXBFQ|R4!JSq5T<3j{<>pp!%})=;Ub- zS3uoDesP*o*Ygk}o%nnB+g<+BWy)kcMn;Sa5oez7FM0J!C!=WV3`*I#xa4eYZGW55 z#-g3iJK^m$pBv_V`En)r#Wo_&Uih0NJIMU1+{uMV*$$f|m&(j?XR2V&zQ}36M9Jgo-YNiLL*BWI zp_<%u>ujS2FP`#|2RWMI!9Z>n+6#EQ5`aeACUHI=)R)L+c6G7^8!}pRSozr#vZ4#n z(EV>wLAm{kEF2F10^l648(<9F8N;7r;@JYf$ z=XMvM+4(a3{IGYW=yW}v5|gT^$eBN#$C~O`a9fJ5vKlr8ImLaqW(o#_^g|GyWH^X7 zoyEO#&>87BZ#W^y0@@6=u^);83keBf=jCm&;ZJ=`zWKUx4`j4Q@{gkYZw zoZB_(N~IS^otuDcy8onZt-L{6i_ZrhVM>0)l(AmwfDZ;plQq-%`+e&Jt* zrJ4VLTUW8YFI#sVZPeiHPfqeE0px)gU?O9LqQtB$dgsj%1eiS#h|pzOfStbieNM(_ z_#Q+(r&JZlmQB}tjnI7_ghWKQpLA=F-g2ku#S~8$&Bp{`^2snQMNJYuXf)W?*4K0gfmlBI5oh)hh1Or^ql`&%z@QpR1GlO)Eh`K?G>iSpKU{ z=}of!qtdrh-w9h1L7?ZsDFTvIz5`wY%c;?E|FVoqytdj%Mo17Z&P`uqTw`$fJH=@A zawAhwULFN>ir?eN{AsE{e*X>Kga$FovZX9pvhj!Lbr-r5Bg8|!Jm(b({-b&ztuuJY z!(<`I;61d+LfWvcN!xwlySCp_h&ys zh8<*aNvb1S4N)DnDvjcokuC0k_Fad4fD$y%4j1@3EmyyJ0`D7}|C(K6kWylFD3s3PLRO ziB#)R1d@eX#O1ipZIk45g+*!iAx*pnPuc`g9%tc;_cGmD=NGAy&Xzlf4POvWkbtk~ zUs_XT9c2lYb4+I~26_WutF$h~u_Spfj}d60$WZ*@Ibkr(@T|IH%(ZyVl`%rL=qO;V zNf*E*%rnZrNo}+*wuC%%tCNE%P^Pk>WvC9stl@9?C0<>a8K-Q;QeYqtvkjKNiyFCN zC;W`2)hiK70WQdD@HTb}Why7lPA!ipI*b0yjB2uU?UC|2&&}H$gYm1p_HlNgj1xF9 zpG;)iJ8hD9XndF?7{^rT#E2< z0T)`AkIxwTXyE=(JS`*A*|d#-YqFUfaj?^eCcdu1U%m8u*hwI1i#%wgP?{p>X-J}N z{5jmc!Sv0Fk~QF~NE$GMvRZ~?O~WAgIu~fcjIo%58mYD~MuzaUb(Fk{7$7R}js=N0 z4QG-8v@Kb$x=5${YO`%g+gNzZkJbmp{8NR@^wS(-sLDiv!xL{ch5-JtxVqUr<)Iup zl4%&Zw|Q_TP8ymEyj`+KkY0-^$3vjM(JH8p4*TWxhq(&fm9F-6n(RHvk2*r=^96A> zk6^DV^M}{`9P1De&==yZyCe5I`O@8;Lv`A36Lizs|bLL&a6Hp(X;Mk9eSg> zuFFmDEN>7-{5`=HvJbjY=k*&kf4Q<=ETFnRYRuzt(=k(7J!DG=Lka#?Xd3sz6*+0y|^;0_@yi+1}jPjLD&gJ z2*f|Utvizk#ZMR#qWwag^+_af`(vaNh}ox1Gdt!qp5MX&w0$wgPFt35VOGmglplTK zx-&oQLm5w7?mxovI$|NaYY(urJoFyN{L&qKb9(|tTt2eJaM^>iFU)FciujI}v+4%D zg7$FFcF>*mJrV{nMCGF>0a7cE6_JUcSHWvRtDha7R`tcmB5?2Kk!)`SOCQ_&Dde#M zTl1LU$a!qg0ffXr{ktHLv#eDN(6ESMqOg-lXVPTrU9%3i5Nr{k$vv;B z{wCp%9|j_&U9F$|!|@K^!COtiTKq5R4;!n-p2*5kwkB zbs@JBFA(K&iRrkGf#ia;i=)2%g+A4hnxj2h{NiER2j11KB;-PIbQ~dE>~=RywvC(y zjaOUl_0?ZNp3Ax-Mm5=3a|Ku#v`m5DpyV;!S;-W)gtCHF69$Z_Zn9_C%f81YhKf>% zfF>7rnL{G}R?VCmSSG}J9LHdO81y&uH^yvTkQSHJTm|kHgh8a?-gsb{HY#hOm<=26G-)4wZ6e%#Bx7!UgY`va$nZDlPo__q zArjlLuR2dKCRH-YQsB$z(uA^p3wRiU^`5rqLd5_3`(l1-9TON ziji(nhZz&Vq~VW)z7EDNhvWrxMrZT}1DPlgM^EtnS^nB#vzUS_F(y5ye~U=A-nr+4 z3)*}>EK=?^pYsLi_z+#J_z}9zBPvHnNBZD6nrP=3lwcSvBV!kKTCzjFu9*6lUR6}- z;TLogFY3q3Z>@-CG}!%v5tLq9bT$lol*TUtse+uyrq-P%;&gC-w{-v2+k3e!fKW4D zqud`Kmen-H2Zl(O#Lo0@tvv{vxonlv`{ht$e*C=<5oREu=5y!sx?7`jcC9$t+J|jO zc)c;Qs6XCHqcdh>Rikp#2QVRLa=bDRqenUv4qF+li~$T1wzXWDWv)toR^`1p*&R(4 zpO*GsK8loqg9AfGM#g%gm;^dr2}u0Rq0)vsYVh*kuEY6&^$!B1J;EoEZ`gk={H^rg z`tP#j0k$GA*RZ3%u$o=wUh`EMj-iu^S?h^i+xNQ{%kbJ0ftKH)T^7rq9|JnW z@XSVY!lD?sFS&PKsST_A(4Dg8DHblZ@c#5I_F0^f} zcE(|*@w19f3u)AV@e4@|@&MXp5Q+48iMn}H~CQX z<3)6sUcIA5QjhMpml#o9fge5)Q&jZQUy#wYiAy9RxI_UbL{mNKQlM2IBbkN^OLEi3 z=6-qwTpG48z;oe|Ow*M%GasiALmi=!tFQuQ3Rf2b)2 zt@vM46frU2gJHis&=wu7-10ec}Z;t@+eYg#_ z)r+B96IWX2Hcpn8fNmFAmr7)CH()lQfAr=cmk#55cj1bH_b|MWGXnwRK?W@iEDG>{ zZ<{L7v_#zJH9*^Z)V9YFpTAAS(}y|V&4$`Zt)Xndo%vgkP{^%@*8ph50`^(U2`wy# zG0MyRN{n)ZZ{$!J-op1zm9_7(dcwD#*$@>+p)`b8TR1lt*v`MSbuTri{2Fq_zhf3keeQ% zV`iL54Dm8f8p}J!KAc87$;bgGIk_$w5Y=yfs_2ugT}LwThdqddFgRRJc64E1s( zwx3+s<1EOxbCojuala_U&!_x!(I=Z}q0J%+Y%C?%f%87P-oNdM`OVNW;zmfTjT&Hy zznIqr$)UcwnL+Oya{!HCf)*6EKNDpEofz?SL2s*~JNasC(uKy7W*z6v#q${X?M9V1 zzQ)1IJUs+*JWrWwQFP z?GT)ZJ62}Hh<(pZi9kyZ&?UXxbXO3vnV+~PKmTYpS#>!gXp+se{AEN6O`x-R!5Ss? zgu5W(QS@CwV1+9w7@7wfR(M3cXTPd+i=#9}l&qnZIMLt3x^E8u?1$8ys zDd_gL z1uVL=3~@2zD88470Xpe*!hzjkx%eg}oOuPm#Gt>o7q~HWh8Ur70T-qOX#ex)gaQHr z2kmHdFc>N>b@=V|d8g^+zTPhA(DHg;qWfVN8NbV4Eo3yWbIo`0)41xj9;XkLTXt2N zcF>viKP11mqM;5|TpX>VC#Mn=?xQE@>l6e-N=gbjIXUS+LV*2mOPw(28ffkQ9+X!N z&d?}*eOsog>Jz8g1{JbMTn~nYvQV(N7C9(6!EC~poPdWi)5Y4n>HF>2{-(z8r zff9sTUk~E*Cp2QNa97ty!T3qBuRn#BlyJkO4GkAr{LV7(Z`f+z&4s=m|KcH$5^jsj zb4CW(YhP07L5yMmx53+hlqZc4h>h^^k#FluhAXiNTNWhKx-TQlI}J;G6!#GLqLzsZ z`zYz-`8yR)yR>Dz)Sw1a^UKrrbhpKb>%GSP$^mz48vI+SsZWcayzjQ3SLaRoJAUbI zMcc{@#hos*ZwZsm$0W)85EI-z98s8Ym5$SWwUzB}e0dPrNtbbd#bXta#tld90>f}y z@_9UzArX}-Xti21WrzfG-e07FcG{p7WXr)%uTBKFr^?b5>r zF(XIJN6}q2F`OJAeZfiHq|6&Bp<^Ob_fH>8N(>&YXdl*r#!7E?+ftg>*BJ!peXDiQ zAO?PPbX4xNp}PL(kLmTrHB*7#Ke>h!=-`26k}<-6zP0|i!=*#JchtD zJn%fRKG|EK8c61mQ;)OLsk5G@H2B4z+4QJyML~^vt!o2eyRlM(eQiBa3-4A9#cbid zI&3}`KmM-V|JT@1x)`+PpO=@{W?RR^#)dX5evJKGQ}b{{4o_RsZa71NT*Q&Nqod>C z2a(z`5Ex}gq7q(bC1~GtlRi>%q1babax2Sqp}i{ zkd&T-I5Dh#@|q9exqhB?um?+8+xs_mZ3KXu39yplGrb)}C{OBFl`$j=;STP{mAPXk zF5#g;VdJh$jBCdhwvqn>MZWn{ZhF)|%BxRjET0!*2PXn#i(D9|dYkqPI%`N_-y8XJ z13EeM-5n=9pq|jgmU@T zfgJuY`hX|UB8*;Y_Jt&(M<7~)meXrA**KAuIcx$7IxuE!Z^bqP1~C*5C8mq`rOL%FtNt{fFnyYM2B0d1>$VK%x*pk3J2K6ZP9E>y0`ecep@ z+YfOtj3+UiMUWZ}jw1ibwZ{Yru33a+Jb`Z!L~OF!+Sn8Va93iWEAWE|vf}|2-~wIf zV-3j+M+@BVSI7=NZWxnNk5kVgn)tqBV_8~7)iN5`Fra`Y&`(Z z>nOY@9Tg6NbiyYNK`4lmmP8il5U0xBaT;{lM7a_GKFD!ScK zBRJg?fAYXrNMxo9;eEI=lCC+coYdns(5{a>tnIUFnpoE`Qs2jx>pd~(930hS+u$zK zc$zTmKreA50uu-O$CC75tTG~3tJGmesRsWOQO-XR=~tY@9R1d1s3;QWcDtL;7o(uP zN~LGNpJ^BVaYy|j4L22&zEyR_J___vu>w{O<7PyUdJ-D6cDd#F+z-9$O52-?l}6-d z<+5zak<{>Li?8Vb5!&(=ar$#1OrDB!?v1*ty1Jn8vpw{g%Eul2bvcM3*&oY3ZK$p$ z(kbzYx^_OwIYmRLX_1p`yuUi7n&pL2@p}G=XHtaTkb`+mzcJ{8rAOP7;P4d^TYm@4u_jb}0$m01+b9%WIv9)0RK_78@ogcEzMVLXW<76sUzr%|e5VHZ74XbWR!C=>0SJ zIqcE9*Dd7N<4t zO)Vl2()dG=eryT$(9{hEm`%*l#>H5q6mZA45-0vC=Cj;*5pFFJssCHmgzqGaZ!*`|srG7L6j-vZFrqp|T?1Aj`r*oly|!$1*EJ%X zH%w7p6v_|p&Oq-@PofeSK_Jh#S>&pByx<(aR}wSnx~lW&;bP3;GC>%vvfPEeh}a?4 zQu~}F48bd-M0!P2U|si@Vozai;EzNagai}FSm80RqfIRovpflv+As{gZ8?oZJO^T= zMTroWm%XWiyIWltb3Pd(jPet7YF;Yn<}ic*G$dyEWSm|V4+N)RdR^G+37@wZ3u7_I z9>2@es8cZ7Q)j|KX#PdFc2HG06c86984x* z6naM(ZpLWNeI;VA9~&_~cRn0m(~g*9mN5+wwa~DmJj~DZ3&9tcHVPidUE_b>t+QT(qo*ymM!+ob=M6lF1``Zo%tmHRMDjm^KfhTf`V zCsMDwh6dAi)4~hCfkCMJ2nD7%Js}e2NTmRNFb1t!#;FRCV78UzcY0iVeLB2=Ar({&<4Hq(U|dh`Vp-I~$qF9Nccs2|%)Cy{Jc_ zT3Q?7Vq~;i(MX_`e~9dkP-WtPDC-36)ljtlkf zi?so%U>pao%0SSkd8<%MByP+vuBQ7T$mtMS01Gz7NF{`K-UDjtM$C~jVBUc7bK<>1H=MJ_Jr9NGiEV6I^_t*+56m`zRW@G$vTTwk~=U9Z4TVbT$BocO2UaV zvdOV2ZZjj}5gI8zBcDhw20G5}hbpl)24@5g?6!Dax%T_}i13>r&@>nEO)yZzg)vYj zq-hFq%=&w#i5|0Yv>O-RYxKQ`

    dHSEB=1i|gsOzvicBp!-=t1A(V@2i-_oQWA{9 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%0AhWo3Nk$i=>eD-y{V@1Di)5#3S$TYp*w{ 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 literal 0 Hc$@ZuNul@^7A#)Jj}0)i746Oso40xbgq0x^IB`TfO393S%c3#F-`pscu{Aik`f zwUMcX;qM>774cKyb9|^nU8b>U)MYgC9Z*Q#xTOJ&!;gy_jcxOA4fW)J<^u6m2g})% zd?gixH&&NBd?>f;Z6#D0cVr|mVZ

  • rO(y^FdZd(l*HOWKROd>~@c%iFQ^-zSrPO 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 literal 0 Hc$@VZh^+Vapy8K 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 literal 0 Hc$@I!A}1bZ^wy=o&GJ zjTqa0`ib{Pzu)Wo=kJg0y7t`HeV+TobI$9$p0g-Tbrm}5o7Csdouhm4{HgZ2a~GV> zog@ExiTu}>oqI!8o;VUcp>VyY!y(?RinsYC z_TI`u<&&gQrWsT-!Z)QkH(BgEJ5=Na$Px78g|QE|3K%BN@Gggv!Ti}?9oH2_%?lo+ z^BA6cy{De9ao*QWPiSe$?PTUTwwA@YqY1F{E*r+!y5DBy8O^d z>#Fcg5b=3X`c!y8&E8GAc@EOI;OrM$Z(it0_;>pGvKh)yv})sgq>Z$AIPr2DjQjU8 z5-dA&v$g7;%r=R4@<-vjM;xx< zS1KgafS$9yHVOtlB{W^@9K$PITM^*UvEK}LzIo1KPN<(ICRfhmzJlxrKkA6yH~XAGb@OH z;(;R#)+cOa<|MxsGY#Y76LB6#cLzDOW^K3>ryw07Yx%T@*b}l{R#<_EM&b@f5sQZR&8wvbW(C0sWB$qbH z9vZ*56npsMTzSMBZD;yb9icHKvHEec#FdX@R2oWk7i}uPGj2{-C+EeQ#;9pic}I3$ z9_o5Y+jainTMg4v*GE~}!CINm=h7NqR8b{gV7eP`GOMMpNU5GTuql?JxP9T$WdOPd zpN|ekmqVcV;Fdtg_HWC>2<)T@7FLW3X_*f}LD#E~LRM;pLoO?vBfIdwFQE(VJo)r` z|Hsw3&X$`I^Td@%kS){yu_QqTkrVfkVVB^}mg-jsRz*Yagea)GwPgwrYMFzq<@ zAUx~Foww)6Fa7W90!1Ukqh6)Ee_p$D`SQkEj&qWI$i6takHMpWZNd69A`pLIc+ki% z-mygqvBFM26f_xVfZViuHWA8O#wyf-0B$qw^ap@rJ1=S{opwMTLvjwgS#0u4nmv9Bb)rm?rqn2rY zy&;Kmz4AAlzJVPjOZx#6470}Y1RZUIWFZKpQ3>lMS2JjS$W*isbPamcISGz-o-QhL zFe#g$(6ek&Pw7+A8K*O2o+LrdPAK$*)uZ779ZzM)DacsNOew7&!5P(pi@W_z{TxG% zRRBaV_C|1rE?$PqcrCUK5eC^bM!juv7pdKyMP+sPEGt z9*woe_z^lqmh@`N4PvC>>6)1qXt>LHpBHSVEkEi^7^WP)VZgH}(yFeY5)R2VJKx+BO5UF@cMD>OM%en`qu_$Zmc*RSK!MtPc+`t4ERalA@K& zcAX1u&$3fl&tlIT=xx1&ShiJv8sGuX9+jpku{>w1mdI>cQRkSPv-z zN=7$?RB0M`>+AA?5+I?FUNLUSq-iso$;3l2jaQR*aHYXP4xt#WK!TL|^K2=>L z=Frdv4hXcKQ>>u#Vv#XkLvN%K#f1Lo@(msa={)&uwj$Yw`RSAJGf1g>n(`}Qa}%HC zJk2RLnk~+Q7DCU3ok2ab@|bCd{6}Tz3qVP|jG|WpWa$d=;AUykSsI2|toC>%=O*>v zVITTlL4q=A`UCvWpncm}LN1Tx2t3&d{u#y|v{OHI5l-UCCZ$8<>zG@T5L(-`Xt$81 z$T>*gD&TAhm|gaS;?E~V;STrblX_nOVHPtz6gdg7FMY5u&GH3um@ki8PlsfNr(jwG z4t^XAb&zI{g8YiV4$GaL&Eu2e+JLx)DL29lAlTkM?Ed3FW%gb5Skf6F?lhga(GmEp+=WPRBO=_yL`fLnNq!}XsIe^`*Kxo=oQ?yw`PYTf%wzjN zZp1|&)?mU%0Afu2cZeOe^7!nKoix(6{K|dxW6eLUaq+@7I3+Nd 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 AJOBUy literal 0 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�mX6K`cq1>2#(sNuwvOw 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 literal 0 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

    W#=G{7RI)2o{4CUNg};*^#YMks2TE?M7H?bC?3T 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

    #}BpRVD=sr+|t*<`@mlpxUzT``ZlPx3U!0RiG-+ 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�^ za<8^SJBOQp3z62VfYIn45brQIfhNrmUZUY_G9uT^l#rgJ0_DZPeftjSoWv*ZoJM!FaIuJ<=bm&1@orUr%FODhKaL^+qQBLk=?nW8TvT23X%>eHS zuY2ItRGO2d(_YNl7?54n16^^i2g`n(`cz+7uNaGquN;hBaVhIH>gFsn?l#yPX>$ z(o;gx+-#wy;z?RoDRN)$@DbFvyos0N?tKR&DJDqhQ#Smr(T3{F*q?*30^6~qnv;DULf^`P;~jYsW5lM>b$?&+?OexO+`EQb^q3u z{~@ry9}1(p`qpsQQN~6a1qSLdxE?voP4DP1>}^V%s85I;dTrNJv9hJpi}N%7e`tXm z0hv$5B_Fi^MBVY@|3uxPMHYc^%omeVf z;pas^$pI2|gjOmjlJJzqHlezB>xRSo*-LGgJm1n$nO0Nw50ZW-*1dS)p=%qy?i;6r z8SyYri6)w8qs`E?*5g{2&kS-Ryc4dLD_L&{lBU1olnHh$-{ftE+cqZKs3VB^DJ2f3 zpDcKO^(psDYk2=~(LlCmDI|JWJueV5_k~$%%CzQZ8iV`3LJC$MLH%1>(^ecj_kZW2 z23WCuU2$&{aa($^n6r1kY}m%wY_Yk0O?LFUwb)b6A@JQ-irmUl8cREO2h@q8dN|7@ zEkSXzW`pGU!T>I51`9ddSoCc05;V$#Ro(m0# zCI>Z_!R;fQZs)&BapXN>$+Ug=uY;4Q$S5s%$`0b5#h?<(24i#>m38WzX>#I7BMgsD zF9twJ^Uxz@h^m;^8)gZa??JJ%9+B}B$F;;9FXwdln<}cKxX@K7blnwEGs%+9Pii+_jX3t+R&Upwp-`p1Eh3db+I82NyH*_R z*Rj=7>xhPNCMO6m<|DOn__z-|5{X7rp)%2}IyPNoomfsc*-kAqR!KRD^<8~v%(Sb% z6MxelsU)drB_nalDxhWo&BvQr%Q;o%3B)L;O#HS>C)K*jU#^QAlRO1#!!3#^OO17c z#%$!&@kVP>sJC;?jr_9u5VV5WekMz#%yu7M z8!4Gwz;Axd5LGQ2G}6X6Nq%xsD(&$32PD`E&NVQF*UH(6nH9IVS;$Z`$)O}>O4y?+ z>FZA!9U;cY!$@q_%Yl)I)OBc{lkxYX)GHDO=1CT=*joP!NowOEIPD}P(YJbqd2-1n zP9_}E_sihd{z-}Khd+fVT{V1@Ir#@4;y6{fVLCckp`M8AoHtd5f+Ln}uCq?V^2)Urno0??_iCf<)kB8c{`FN|5rj1! z*m62Og;pBm09x{h9mSAzolXR^m-Gh4i<`ZEK{5Qc9#E5-eRJ#=Ew-q`gVe?PLoD9T zjCI6D(2V)}!W+1Y56482Od@1RjGa2C-Aba zkv66+me6i13cG%>W|Fib!Mu#k_PiWv=IORXRW2GUD;3J~iN2b3G{0T*=x;U#3qqHW z_VcjI?1D#*R2vEp&qSFf8}IQFv(%`r47OLtTW5;^Evn`j{@RYqbZy-Bhz+MoT2h<^ zof0wRZ@e30R`F7qRSCJn>o59maNdep=@=cb_&(Pb)x0c82T-D>xbTyMoeg|_IFIQy zLD(zh(ZRH(Py@>;2mXQ_W!@Ucn2_yGi!2QFC}=%qB5d3UhVKybwmBW`ZCgg`UF{{P z1ni_EqjEN0Bw0F(r|D;M!OK*l$;t?Yy`rjREZ@tb?vd1SH=b_;M*2=uwbb$!)AJZI z^d^xZ-#yDJ70-_^q}60Har-Afo$n!NJ&*wVKhe#?F)w_Bx8d?5*HdSMO#c@MCxYKVN9iJGOz{(rJAjV`L!x0C0Go;E)4>xMLrN<*O){In-;;+#%i^dfIM zIOWPj>DX^jJ#i~hpZ{YbF%L7O`4b1oiY<7XMn0Q*m{lT8ElPT^4k#h8kP^^_dW+a0KIyaanDXfSpQjCu& zKwBJv!VFiFM@r$=Q;83qeSd)vgV*0u>5-6;dSoQW{?8TLQLu&`XdLC_pj6;AY1zaB z&Bis6QX^sTJEHxm;AJ9&>QCAY#$6F*>DD?irccYf<15dR5>8#u%u19BvKK8B?f0K6 zP*95OYtLp3QO}Z$#;WQGAnRqu5{c=^K4^zW$_>Ol*JQ2xD0R9`EwhEB)0GKVfFjKpiT()k}mzS3Y5-oYRuCflKBvg17?I-pCkdIks(W><`x8kaNhj6K zAx=jOevKU3bZEk(pXp>+@(?lJN!=@lYluWbb@0pn?=^Gl7@DZR}Z4jfrT*4Y?rs zf6X79gg?>v%}NTWE_WrcN9|}RDK@<$@E2VrLNStJ|EZN5dmR#9| z)C%OO(Y@%5*`x7cTb^A)csXOFsEm{za4(sNZ4RlFdJ%58{E`lk6&cas?%@$+?T{;>Mt?;mS%Ryq`v5^-|z|v!&S6T@N+ok4sRI z`1I}lJ(TPn-KEZ)5~pn1n`hKlIgcByOP{|1 zML|c@2Ojpcyhifi46I9gl+xuI4NuYdpNOKr&Cymh4YUb<;XN9D&gn>86>%YSnNB8d zbh7jVN0Xr+RGejH`Ia%)T4x!SSO-LPhF*bS>(WKtaG8}z|s!`Lh zuNJEsJM>tVtQmB<+{0dcX5~2SYONd_-2i+`$f)~@eHkTBwnUn?g@qN$^EO@lL_g3- z!f_=YU+^7;TYuSYLWeb;j)@(pu~tH_{|M#z zr*4i*c$R9wiqFmGhWXNygVpe}4jlB7yYFYpavnWw?f)a{%@b855IVS(grMJbf0dpB zD;s+ryuYo({k81UO0AQx5d(ksIkf%xsy^z&(MGLDw6g&x`Z^STHtB`8y*Z2~@bXVL zZv>P&0Snf>p-2v3bXRe4MEoWb@9TU7n!ddRBG)rS;hukEYuoo~drz#+%u#J5wgZY_k0HAqU;MGdb~nPiZV;TQC$(xav(>;}qxM|+d*(~g^6maAlC zZ*w)HY;u9Zyj2~h{2pYymn>~BUNk>zs_!}l5WC%{P$$9Co~mY^xYQ;h67_DlNNK5j zqPctR0{a>%EH!17Ww>L|#5k=5x-4Op2pOLwD~vzXaIGEMu_%}DREJUJWA1L=+QRP2 z*2l24F-p_Ch4ze0wm-ico{xI#wU2#vdSkZKb9+T&lOzg^TrUu1*GZ|G zHKtVBaG3TdWHb|ymo4h3Q58ve6Yr7^(jkWl#~mNQt0WxS4kSfjc~z*iLK38wB2Dh` z9V&y1$D0eU#A;gHa(_;^{nnGKL5JPZdjMSL>H(m0{R6K*x~{#=-&1^%ar)RlNO~}h z(!vF=Gn<2Oll}-?BcdG^8wV0I(3dP24P_q@!s3=$@_y#fp)BZVPLjBb2{oHeu+pN; z<8uOL2OP=5WT7zy@hGK!29XDfYZam+K1qfC`EKJGNuED_=)FUwh}9CNwV(h)+Ok10>-t!tY(yUWnT`-wbqKg=Ww=lxp57X{MOhW}C8M}Qd?>8r<8lY$yA z@>({B$q&4W{#Fe7y)(cno-K576~2Z0G>SNaiHz9(VxhG(2OKV%KOuPCTkBK;z6?icnc8+ddZwG{aUy9K4wxeQG2;hiLkP6IvJ8$(QY_An?%|Oy00TKpSEPL)L&b#ES0JA9iy+~k0{xxm zX?BOqJ2f1A)@n_*GQXpt|77VIT&Zmj_}W*eN@dH{nr;%amUwgF9RH2~+qh}gh4$qM zl_gf4!&~_%pR2{}c8B1SDPa${;cRm$J7ajx6}&wcmbQxwSa6z}~&zt$I0H zXOPcBul?3C>SN1Qdr<;XgU8QkEC6~=NMWWQPp@LF>t$`0(#7E?r=CX``mzTa+SU0E zE{xMzl{isoHJ_4F((|UDXnd77@LJ^8fb#UIZJ4Sh=ho0%vCb%_^W$#7vs$_=;pRpL zE0y>7odtaVByAJ_vzAQU%br{L8Kmd1wf&)T+eYkHx=Pj8oS%Dp$cT;h${LY&DbIs{ z1uCtZmt0(w+_C+4qhgiNL>O%o$Bi0Dr$iz8_Q8wFt13S`YB|pLM)99Y6XJ&9kB!s{ zHpbQN7i!X=Y#t}sj#5J2)zIJM6=TBq?p}<%RNsfjf&^U_(QA->>TEU9 z+Sc<9Hg(&;BxH70V+OP=w_Jq2SDGp~f9p3~hP*jaizNHzi!#3ZL7|#(+_T+a2i$u! zs^h7?54(ipb(!D0UkkfzmQliJIH&FdM*vM>!ruYgABnMtp-pSBvHZH*6L%-k4A7_B zU8krE)_x~t)9{sr=i;Dh2ZHe;cUYnM?L7YzlA@}Y2!}AmPxt&YtQkJqMSs>7>4cK- z`zw02(02V<5Q-M8uPDg?JdD}u;C8dM-wz|k_SGs%Er+IQ4TGBLTPNilZ21ce{wG{Y z9vr_+@Ji3bpOrXtH{TGln`~)*h(wSO(iSPr!NqAZcteKPPo7|IdmhnOXdMq!9%(mbsvxK>fsINjr;*Ht?RYoSmGgM2E5;iZ zL1zqK-rkUL;Mt)x_BrGcutke~7M1X6SH$W!VwdW)ofqdQju$O&e0EU-&c1IxV}+zr zgNHp#SVVr{D-QTeAld63%o}6C+U>cWx(kkcq}=uoPJ&nKQ3r$#D=VDO-ad{JUrZU1Uo=pE zK+s?xC4WX6mI!A_UMnszg4B})q`ly%(@gOT2f7Yltr_k}zs6#;e&EO9P-(?-2J1yt zJ!!#2vC4Z#Llzh(c$*H7mq2cW=>SX)Mqmdf_3rLERl-eAiWZJi0DgZtpotU6V?{b= z8ejM>9};c#osBL5>@21-$Z!pzY{{XR*m`c6uudM?JI57o#j33I00s6P%gg{ef*GUm znA5s*lMD`xy1zLRqP2}q@mL>v27V`%E$KD!c2ZJOLdul!&=NyZyI4yyOkzBRU`|(x zQW}Zduq4s&-DKwUQmxzLDA5mqGEinN0iy;Xdzo`%p}<2jb?{`NmULtU z%;y@24huMKmBHVI^c&L%~wYg!VXlUlyaZ zqySHRVgwB;ONOn09rjH>@uiF?^hfR-)eA?fF{jXcz-w@^|7&ncVloQ34Eg+M>Fm{6 zR(9|vcim zMc!P~*YBPb^1NhGLdD%S%q|c&64wB)tq+Z$1GDwDK`0>ig#$ab_i?PIyXx&oU~TpZ z))XI+o)l5{zr|b{vY`#8KkP~Zrp4lqR6vtR8x5(A_|4(TZcA6g@!#?c+}(2MV9(<2 zeD`bT!$^9NZijXUG)4&sUJ+!_N~fSULv+Cpf|$k_V+}9-srCMiu~?T z4MS(9Gq!Qsb%yKO5r<|xNFf8!)GD)GdcT;Lg)qh(FEF{%x=t;gd8CL9QR8#W@_vw} zDsV2kgPM-ChP{roFKU2s2l2h57K@3w5!+co`_{!v#5uBFJ{;{&p53LTE;AR+DDvAi zf_^M&>p&BqGMNO6AZ~EW01li}XHD82ksgFBs4JLBJmchh3n*d@jier9jJvS><@=>W zC{0mX^&}}Nib#Y4vCzRC&jw}t81Pbm4Y?Xv0_gW->~WznMJfhk;~sRV0I1zkq+w2^ zz|65W$8rrt>Om=>g<(pGgi^ho`+sSqC$ykhhfeQexe@!vfD>^LdQyiug=(Xh{iFq@ z&3;szNy_yoN~JiyA2dkhnik=Ri)H6sXZ3maeYkaWBHr5?vzX@l(js@d*jraZa$LkK zJ8ybjE>kc~hsi;;!_~#dVT6UWc~g0aW$}!tmR2PN;&g z{FgrPi)UdPM)N#_wD@Rm5Uhk$c#W?>hY_#ma7Sk?Gqr>(=yJp_09t4g16eul$pgg! zA;KXweekiU1&R7eFFK{9`;Kgle=>)vi4uoFQJpUjd&|BWRsJor(vO`!N z8pJ*WO?i%Ae|G>!-gReDCS}r<_k)83r0+A+S{{38z>XQyu*|{iIPf-_A$HW7D=`Z_ z&XUfHCzB=Q|p35XqnF~a5$sRYZC!-_e_P7R+y_`Q)^DXAE8#~JAx22M;djaLedws$r-Ju6!{EL^!&MC#QOvDhYt{a;0iYg`qN#gEd@8Z*C z=UoT;SBRP-zqh(#GUtJfu(28{J3ZyX3GaZOWL z+pfBvfq}jk3sdg`Q9Oi78!vtEB7;}N)BC*QSbs0EQ+E^Z)v?g|+2omMl!+ga@6mL8 zxzBZUvL2_~J8n14!KXQza~ZULpBbrq+dTVBgRu1b`dfyqSXRN1N8Z zzB?T3lB}^J2q3$IF13>9j@iD_A{_ewl6}VS=0n-D=}Wxd4w=*EL3Lru?~OP)q1eoj zw&kOI4EUPf&H5WT{ar{@Kp11AxtqG~VzaFA|3$ ztkPhsYVzOnwmeCI{~XzTy)3kh4#I3m)*bt0_krz#6HE5dr;6Mbs_D|04;YcR%~Qz zZzGk8+MWE4C8B#M(Hl2@*n4<}e-GR%9p5t{joy$>bR+FuD~J*VlGH^mcNWh!zE zOGsR>59P)(kj4E`D+ZI4D!7oK-joz{ob4Rk8@hy%oe+y^1zZ&ycUl`IgV7jgZ=vzL zaPFJ{Lckv4>*r^pneLI3aTP>1+98Sih#kGPZ25VWU<0=vbU*m?gpI#EONgPJ-0$s> zKhC!6XkRiAg2i0sQN{cq?64Bn`)j8)S_r~05@uD~Zrxk;#d5~!;KD;hFC00mh?Qt7 z#@xyWwUd^b(sWNu5>%q@B!{l;bXeycaj{_B;L=47EsRNRi4J_D;blog3{jcux)@sl zSTG!F^J8)1R$d`_oA&P?plFmmzL;!>Veu<(74w8hj+v}N3|@B^GNDAu6^l4YQl`*5 z5cJ8azi_vUEwSe%@t<-C4wx9zwZ@uz^0in)>#M8x2MQB@=(Z$@&6tfilNs-G2Rd}X zhu`vyr7$tv3vlU>^^XUZTza5AbHwOYV8|)S97b-yvD{!_usoT}ojnj#B450(u>q?c z?8fOFtNb}9Ee|~jWzc`viWbQ4b7J;}?AgY9X>o`nhd)ytbi(mHhcJo3fNvNRsN)t9^HJZ=I((171+D1cCrIZp# z;kup)+Z>PF6=Yj<1pwc?4>u)W^AVQOp3e^g34?+5of?v?V77AwU+Qb|^;4jA zq9+@sEN$r^jT#n9z2JzL-2p33jYWTFy#)JFx3SWG;aHsn(_VF)^Tt2}{5eYB*^=)$ zD>^ENjJ5NNYam|i+~sp$AGhZVImn7o5=I|Lb8U=MWGmy{<}_=UC}?Ml`GH6YWGXHr ze$FF1IQ}&V1M)Z^0PQw#T5FMNEwTD!B`&H0tpP{ZI zlF07JuQV;iZNG0+)@WOD_F(Jaxhk{)YWa!vg_Rz5wXXki0X!2t=u;_pJyToFjZGgH z`)dLg_inC5zC*0D*eS}V(Y{DuCVAYUNkZ2md5Kp_%rGLd$CRZ9T#KX8p6iA|;wEyy zM^&1ok95ea<-f#*ueHuGg+EWYh6j8N9@o&nF3o!?>{qhDyLj=^oITd|TFFJ&)Rpkv zr}q)0JiWLdSzG~PNAl5Nqsp8|_x>wdY9dI74e92uE0&)}n!el#^~4sMdeeuilI1G z!5k`Y1In{X-L@Xj0us931@p9obG*`GS21Kl zSF0B`^op`#l3RkmVZEa3izlXUuvV>{pqPw*s#Y~f@1~8>+YBXJwznE( zvUAA@7Zz!;BdU`?>wM^Cckejh&b8UJ_PPP#MeCjW*GubC?kuj??7Ts`)CU7| z*5v!E_asRE+UJ}ae1jj$gX8y6wD>fRCw!+Z{w!PWUuDh2DBAJe!rBwp}cDqZW^PmFs^L=*CPmDA89--}Kb`}_42=L@P zEl=1cZ}_3nwM%~Zap;kR0J17yTdURFNE+&_5L${GF-`7b;yiy|)(TY#^%lKo1(sHP zDE&Sxz{8yYh(VbhNL!{Hme0DQRTGs+YjrwzORl$MlXX0Z2Wqc9u$E61)!G%>LisPj z+^QiEbAd*}2b?9&E$txCI>4OuzSU$GUvs%kvh<(D2%hX_9(Xf$nHp{M?mo9bXSAuYI9>}5;@L&>Ymz+Ls9C*TSpioUeenWwo`kV z4nlZ5Mm5y?ugWPFwq_#uKoZ4B1Ew|slpOa`ytVpTj;RmUz-GH; zM@}LfAjd=jCId*iq>{%BlC3RobF)d@ylj}oc3w<~noC}8)1mV=6A4F7c(1EPtRY%e zpNoZ+bcR}M)_G?wY1n?6bu)@G*K;JD{)tCn46GPIJc+dX6HaGe!IWSA(*QO+74;fb z9xD=ES}SjDs=@ypb|;d#{-RPz*VIrpazav;HB zBAfM;RsKS48khIV1KIbkaWaoL=2|N*Jq{<_P|En)1CsW*X1G@&!CEj(#}FeT(ANJY z#klsYnQ_JU%ANOJbDTu5p9n2JgFidzRr5vP&9)X+Z|J?>NI7j|yWOn?IslXT>bZAY zqVKcSaGZgZqq{L7UNYeDhX?oc&g>(9@Xr|xq0Yd5riy?0RqZTw97Ed6T zj+mZ1$2Asz=FhND^wC_uH^RFr?1pgjt;Zv6saI`%FY=A=^Xo2?^p!&k4jjK*YJGeE zik?V%SFV@u+W~=7o|c5M3>wS#*QqOzOxu>!kPJSGnA4laht8kUf5Zvtp=v7waS0*& z+?1qwkOxosuw0Y7rLg0&{8uq7SllXYb>A713&v|nM01WdGzO=ujM^a|#zo|za-u*rT9Whz8 z{GPBGxIOn`6^y?(M*Lm}5_f8^!Cv+Z$Q|to`23V$d2~;TXeFZr`8MOwt+~ugNDPF` zZO2@>Ab@V`L=nMlh~FHPvM~{z(=WA45IHI{@w6fV4>d_V9V1b@*3|m z;aZz_`|3P3i(Ya{XHf&e_oqZ%E~jpjD^Kf3>_VH`^L9A1^pPN6r-d`Y1Fll`rR>PS z9f+1$2Dy02Fvd0?ly(ZAOA}$dC0{pS>kI1=FIHCp5=fAM(Swv;h$N3cz66{Y%`TzC zhRR567@+KIt&#+owIFxQT~ht^FiKc$*-^PJ*G1n+DXiA{9b(HORlPj{pn$Uokt2@ZaB zt6bCg-GgI5e((E1u10=8S6Bt(r`33)4IRsWWK1t>aVSnuGON`zqNWWv;l7CD%+RTc z7j{_-S_wIlh&_3}lNuglknz903~xj$B?#P?+iZA(4_{N?v4zBn}cc0rb3gD1||dbO@r70x(a{TE8E7nIJD z9uC6Ej!UNbeNS|E9HwYDW>o+1T)h5<{lMRC3(UO!ejHag$_5nPx_1%>#Nzt~5dDZ` zSoR#5-Y?-n?rQu^N<*tJmv;l>7xOzL_;U&~p{@Jm_scZ}%i5f)x5@e?gnM1p$}4Xv zy2+v7$oIp~Zp;hX>x|wwn5U)Al=bgyKUxW{seCb$SKle&*&l$b3E-jW5?4Ot2(Xrg z8`2Asn6Dl5pBNg(*Sn=~JC+Q918bNWASQN;Xo+k|5V$H)cgZK+z6o^XJdYKQTx@s` z&gvhttkUJ_(i-`OEkm#c`c=yNmtZ&BH!Gc{#!6qSbu-!PTREhC1 zmby26xrjHq-4H#sK*xl@Ops|Cy$%%j44()Af!HLOtZ>-)ncFwf2Oq%zKj0KJ*aYw! zPj%9dx$Y)YkKi{PU3=PI`}GI)d4bY5(H{NrJKZ%ZVIajWt${aG1xJl@J^CF-9q^b$n!F0eqT7a)#bs6uKyK>B% z<9xwd2p&Y!#WAzZF*G8d*;a}Zt2s~MaM#uCnwbgCIwN41v3P*7=&9*-EVmaLGh63f zUtxMSHBG{3OS4Sp@F{>$en$Kd{Y@l*m0fWf4lnc)~*Jefl*Ck-`}xI#3{(eg{ZV8DdpYZ^r_uaGbe2 zQqsf&!mkAEC?h5dc-xVfQ|kIjF^>nZ|FdQch=1#lqqx#1u8I57UO>kFK>4Wh>hFub3v1Ne){ANG|kf_#wXEG4* zI}S=DC7`LZK?z#TU2>GMgGw6t^J6KSW;~@CfCx=Y)HLWIO4KXPg z3!IA#C-4Nldi)BPrG=%RwqL`jnH`;GfXYPo>1>oKuj@8b;8p!tWSL8Vd34w77`xWu zS;-{if#FjYijgJ;Z^3`PqX&@?VP4}Z3}HoZtZ>=)HticWDx-^zV8nn0x7jcn3F!8f z|F3TxYCD4I`n2X!Tz7m&B%mq74LoTcDL0aDPDRQuS1j^pI}^e&;}+Tcydm0#qF3aW zMiAN&GGzjBe^_J|@3|jUP7=SSqJlJx_d&zS4H{x!Z#ql-cl?<S6!HhHV?zxEN>fEZv+iR{=a@%#QQL(2^X zt>2dtfC)$HXw$Bt#L7xtj5|PRpTxwPu3MmAp^nW8*bLdMr=_3QM#uKQK|<{cqJ>aO z+W>+BxUfLT+vL@_ozE=6<0lyT-@V zZ1;9Od*3yjo-gIdBlVRK6oB}D$NpXLS0aHZC97zDHm%xk5!$!uRL}=&YY2rj9pE56 zJV>BgL;j%8l575NML@ORhqYLPeN+3wPb#!49sSPVG_vg5o*k~llc1=PwX?BG|M?}T zXiwctsrB=q8DI=<@F&O*Lh1nE+a|C{yXksD5d0{bsl8t`728W4JmuW?0^S4WmGN7r zT2*{pG#31dK)e7vgdfnYzS82w!B(mqHEJ-{$7l;E&ADXldR1yJ0NC5-Qq$W<0>Z|a z|6Tn#qF*4!ZPJmKn(8*t*J@&v2!63KN>k~OskPq2VgR(LpvU&ZTH%7jBcz7U$%9fE zJt6;A5mh1+>`{b#Q;R16r!MmFHnwhlI-Z{@1^wO;V&KjowjY;_G#P3wC(O`+jh^iZQHhO+n%RRiOUo~`#q z*x5A#iHL$8)k?dSti(*D7FnezAW&%K+)yS)w9D(oXu#At39eI;axL zJDb?A-`}WQKB&hs1^?gPsf8X95Kz0}?0=`B5_fw^t59eXf`y=*T5#()0T~o~VN|s& zTJYSh2Jb!pCk0%#LaPmRP>9j=7{}_o&UK?(10j)5%L1NH_*lXUfHY$%9A+ z-0&2QEUStBFIhAGoS5d4cCuLg=ZbG60&H}NfdY`YlJq5rS_%V~G~o1fVXaZjAjZFj*;gDC0rans+{eLSAI1&Rn798d| z9DBlq4JyOpZBK{)DCmbq2BhzUm-4djG0HObX6?2Vo-ovJ9*<+s$cWn$ok3=9R$X^p z{swdj^jWQXu)|!04jsk0=5hA(NLqw-eQ^ea`}`+c0Jtu#BKM)y5qDm~CEdgG&EXS7 z9O&mIcSA%Gn)`}xfS_aqO2p~eWxXTYW$+Kk7aRNH9VuTvdC;1z7>&x9IA}>}%%;%5 zN0=xbQS;663(PfBP>)-l+QMxvBhV;8a&NU8k23}@5Mcss(H+XdSFVc>2JvXe1ms5Q z`(bD2khWJ!x7a^H^Gn|Ch>5lfvS zJ8gXV-p#|Gf0SqLi}YQ3t$gymVm{<=VU@inOmN<2M!1^{Bzw*_a@l3cqT zLthBOYw&D}-Cgm2J0T*=eyV4>>vMI+ijodImcZ%=K<=BtFXpU|hBr#PvYmyX`UA0V zYo?dd1GSP!7OQb_u`Q(2j1?o$;R$)Bts*Yz+r|^tEWo^2SP>05B!YydsrSEtFBrUp zoly~(V{j0}OmLHtd&XC{EX}-Kf^<E7!;$0$;P@xG8?O+k}+TdMh!yt zZ@~(clr$=rvGrnFbA92nmgAGnw!C%2UcTTTJN+bGx*&gG>%W%x+gCQ3YM-IA;}H`L zYmf}YMdE*UqddglG}&+fPi>e58@0}3coB+4f%(LUmBYAa-n6Zb!P;LjCm4~8_b_?3 zd5`-ZMA?gwx!li^fFV9{7|!W^!~OjM zv~$0)8Tuzq*tFGR_s4@rqfIJeSTsHcC{Q1lOswFO2M~`5{GKHF zi)Ej@N=Nfoo>_O;G%*VY@m6dOJCU+>L4BGLc${)}$^vpOd`Nm28m^+;gIT^!pU{{E z^(dOBJhTnwSS(AnTrV#dC$&Gf=JY@tE!2i8IhsQo4O11nbCw^(FS(68lGFJ6yM~`I zCDcSVfm{ZXjgK_FQcSiD!2eiLg>)f5Ud_cM?Y|oK)kfCE<)0Cx3<_J{w{aw_dq_Kn z-JmP!(zS(4+pIm@-ew$0{>{CMMA#tMMXSpC{X1bn9&xoCqqY}psG5^Q~Ff9_c6 zFN%DR|KU4()Uy3S9L;N*L*2xN*F*=?n-h;e{OcQ1<@d=g-6?XgDoT9Z=76Tj{iE9l z3B1J7v)Hrb&M9$<#fGQu9b#7}War)+SYf_HQOx_O-<#tsSA66uOmv1~Uj6;mq2IjJ zl!Uf$$A8(fJAH&(Z?V^WI}I5X#gkwWz-M~USXMkcBCYq5FVg3Geb(8Je|^gSDyib& z_knX>Vu6a+&OL@8vBQ&G=v~pSz{vZ}5G99iIM;v?`IMZzwxi@N=^!YW^XXH*jWTCc zt`C`~J~4g-0CSh?VSyy#PiGib5CQ->KM`3w&8fQy3nwLCuE@d4dK$g~6X62Ic6#5q zBVnNqe~&Tq^;d8tG8`5qC^V%2Ug;xNmO>-4O;uG?O5;JoHCi~)fX6PpqaE&ZL5(~w zL`yfRAvxb!sy`>n6|z|0fMWU}A_pyz83N_`IL9Kkb5fH16M!5%x}$pk(G7kg-<++2 zE-sZ3Jo(w}L_0#&D^_=polF^FF-%;KMg|+x{cDY8xmr2f7tprhY5kt{Cj6BDRy}QN z<+2;;-3m-Zn13%bF|)zE%|bwH=0#PsAYs>#h;4JPzXgeZ8wh3^NXA7eN3B{N@2-pv z?XXECF-5pZBeN`Fd4{-hppk)p&ve;37R(G8HJ@&Hn~1=WZld{XWPW^T3aj<`c>!n( zqzK6(QvlWy4DDtql`tRh!b(-o)melN>E^X0%48#Dkh52YB%3UYw>Szi| zhAITUdCe>wU?p<<~> z%m@purIOA1jbHCrr480uIMXMgUc>?>a})~qwJ<^75>H_UDqsYu6HE3DP!0!gt^<@D z5+JW(%L`F7H4j$XW~(~y7tG2RLuPdxow%gtjBGZ&L3r2A?Fl?({K5c;8kvH|BDgp3 z$A=d4nICNJm#>%;546K|KUFb~JkA~qk9yn=-A5kpxwBiA)WfM0VrRJTBL4Bk5h}IH z9R*t+)6?UN*k65)<~6z#inqV_5Yg&IbZ~R3P^3zEv?+deoiBF$v1B~(oam>GbQ%kx z;2cLb@pBysRHhn6Ki{#9afP7@8U$IlM6%f8gGoVoK|E17F0p+4FzMCV@9)ZIqeqG1 zc|sYA?r=l<9czEq_uqmgkM3Yjin=j_EwR<$*$TYtm4o=F_%{p!6^?4u#IEKL@H4q) zY`cwnez>{SHlzOzh)Ze#jeZPfAyEM1qu)vW?i3k3+t(!Vtr>L+7ZAlFKow#DWL^DD z0Kv1|Zx|;V;tG8yS?!kIlPpfJin3A(h*cfD$w4!^PF7uRQivOwf$NSgqL2>lrrcF( z0guP3CSyg;EKT&hNaZ}nvMxx%*nxic)2+5rOt2tLAg)0$-JOt*^TgX0gTQP7G3B`$ zOdYidCRK)sC29bV8>Y_ zumotLJR}wmZa~ctZnDAvtJ3#@K*x{U760O=913Ai1%m@nfc`9JxRpP=fHE;*HpKSzj**h zE{n1*Uc=*ts-oQAq$f(nh>u52mv;Q)+{n$hWh=_Sv_Nj$+>E`SglfW2*7Rt50fkR` ze3app=R$rQixvQ<>X?<`k04dtfQyDlz}*}V+)RaQ)Zutkbjt-sqt z(I>e(sE3^Mr$>nP5&esifca1x?;nq!v)6{Uboy@;iPQEOWVPmP6)&e){$<4bji|SN zwVU8m7D&7)42J7@4pX!p?_WRR-rOIHF_}Qj%?O7vu>1Ic9#yfp3A%SXg|P3&Y239e zo_~;b>kIxO{gy2(G&{uMA4u>QImQS|f71&UNB0%Pl8ZzCDqkQT3)3c*aRM_lkKk^Quac_P!$w15yE&8i$5Sz%?JugGJ@a~tZ?t@n7ONgk(o2?Bb&w2yXifHJwVn?o`I)srgHYZ9VRv*P zN8q!!rgc$pI|D&DtgCJVZj}yt2&9Fs!JF408%|8%1c#3fKxEoBvcV81iTAVzq<Lu_iEAQ9{e;(Oe6}I!s0kweAa1fu+cA}nT8uZ31ywl*i^TzzOc$v}9=9TYqzy3z*N;@APZw@qTJ4I? zpg4+4N;jrwVmh3?a0CtVxKX+fWKY06MSq`C2)wTmrg;eH2b1_&%IDBbUEZOn03%d5 z^paWZHrc=N5iLDmrG&GXc=m&h*5}+&^n1D2^(T#GfQd2~6WJ%=4rV{^nk0HNGtZlY zNOad};YJ$uf+30StYORd#dxOP?U1OU=vRdf?M=IWC%(Tx%L{pLr2c*hSrBB00($$m z^v4!p_f8o{9wY~%9h=c`Da!-80n$Y0 z!nag%$f%9LSe$IK>nj0!KiS=FYu^MA&+i&-Bmi{$`Dus;&OBvi?2p-~~T= zdhcOnq&7R49mWiUam{`qww${STkVqT$b6@cD6^S~&aM;7sIaJcDo=!yTeh!=y3-1=MI3jzEKiO0iJ?C?(UIwVyyOuqH83P3sX z(Vh6wQEu&v;-gza>QS!f$k%9dX(_>2HpMlV+R7?kjYQq;X1v7IGTp zB`UOmp(Ad;;a0B;fYWpfQ-D=4m|8xh7pgR^ZXs!t=YX@(0U@aY>CwCna`TBd&665* zLMM{^5>t6WO4Ajyl-xW_D~|_N_=K|APeEcz z3ZTd8>By24>yX%eBrm6>dUbKRa29|-)$1fuo-i6L^Yy$h62{S<%rOdYV2FeXay0~MCcpG#oCg4ZVu3*;BMA^4V z-I2_34?`I4=Fc*lK5L};wf)|3P(JlppNjuPt|5y*PrXfwGb%eSlY_M%C@b9?!2E)beejU1cVp9#!VQN zs`y?yoXLpnC&RY&aX*xKet%Ur`_!v$-*?-0BhoOuFBtN}mulbmyYj=og*|P>`MY`V zBz7IT6Onbj0$m)<8gX>zs+9+t$>fKBTHR#OK;`Z8K>F(e@PThyAqKR&Zk7leeIF^@kPGk+AO>d68+@d_6v@!Z&+d zR4?R`m~8Ia55=G{TARwwF_ZsSK{?jrj&8I;K}F9sJG0!LL0j0o(-(L=jf3aiL!Gue z4w-rXXtbGMgFx`JQr|5jcq>>sgYM!`V=f-SguV3IsM}t+Se(oxis`x(Vm1uO%;<^XozqTUf zSx^6A&u*Id%#7ICL2?fIQU)Vg;_G;fuup~PkAEH2HzsxcENb$}sU4-5Zy1!v)%Mo)fm6M;zizf z>=V*=r`4)m$6i4sgSYR-<6y?h83vZF8vjLI@D0>Pj++;X%OpjFiC1;#X@ki&q6C_d z7=Vq;unOoGtWRs>ymJi51WyHPMja9ClQh%_PU(_jGQlEg;?RhMuP^s8^CWO-A|iKd zQ&FO^6fHeyffb*-^7c}UL>?~em4yyTAiJi+G=gbQv{JIaG3-P)3~?!gQ-DOpYec2IbU%*g51 zC_U;2u>IDv4RnqRmOtgwD!Ueeiq{GcAoj_D&)jNWVYk?!a08kuzL&!L^`|rfjf#k@ z@#(ksNKCaLCCgNug*sj^oc|6=fBIx(mpDwU$n&=?ER%Bg9McK`k`BT7fIkKdT1BK6 zAwps?CiKO)vfxBpW1nB3RS5wsT|&CBm)rjjPjb-8F^AF zqk=E(!!-TOEM<%|&A*epoqK-i7-<$kcsIbJFBxuUtRX);-T|fu0AONE0;_q8`>ov* z4x=(uv2>EeX7(WltlazloQBvFL8Z%d6sR0frnH{8HLpa&L6PO;6g z{AsfYMSH~p>z8EXc8*B7-EbSvYreI#kV31G{EWI?GomD2_W0W9ZR6UT)vxFv9I&*H zAWL=q$d15vn;{=3%ldsb?;~67@0|uO7F}<5cGjue(yCjtcz;Aqe$lX?;3mqehvQ3NG55&xauuq--8q@y!qDdYXw^=JR>!^ z8HD(98x*;y-RYx;6?=M8ghi|w5)6#ed^j5(LS}pyUDiDX`9(Wa#-DIJD~gzHVpj4` za4J+`hg^qr&C2c`=I&CXe$&;{scU>EtK=#8e$M9iD|xz3nG?(GlvqHA^$J-;Oovh8ksY< zt_Zr#S7NzEjmhT#+rg#rW%(-S`;pkZoGLZ{$7@~EetaMH2R@hx-B|d#G=Cf-%wOtG z>*R1Xk@^-{REk@z5)9KQLUADYRA)nM=r`4vzYO{arMBh^3&-msg%Y6oKp<5v~_c+3q)->#2qgOnG>0XA9 z9ZE;DDn{w?V*BgzJiJ-AA5T2=RD+(?HU`-7vXNfNO^D-V)tb0Izil=E>Q;xR;xtTBVA zZheh>6l^vi!;GX8o>_TWOAoWjGl>U3-)e}AjF2N#+BZ_)dL9G*O$&XB4HFuI$?>)* zG}7`9+Ck&`v9Os&4Gmu#?ZSsU9OK1> zu@r;j{0V(Im}b)EYdE=A#QRS-DCoocBtm8t4Tct3NH@29`GleJ$~$qL?~sL}!Hs@w zN|);L_P5dS-uX&x#B!{DB9t>el`ekm&{UcdI1`>UxmMhJ=86rXV^uKA$>Ly&Y%{cdLX3`)@ZX7D zCw2%P6aCmL!fuCRNxvE(NB zDUH9Ejps(Qh0(GcY1JhODV}4mLJ<#ef99C?Un(DcfCz{Jkzvf`anh7NJs}BC z$E-#Mm6Hl+t!6USP{$?o2+Twr)B|2oBOb4*kUT5kt5_^wEURiRhs-2YuwL9uz!X%GQTDlj(YmDZdo*^jBRsmWTtm?&E}f%-t9>Jb4lG=0&;ldhe? zPo+m+ETU>U@jIsrF4RA$%xHa|9D-4z{@kP=b+~VgFbqgNg%NRc4P4sATODt)+(Nl7 zq=*+Nn<7iRX;23HF$elFT$m9BoS>Y_h$IQbLW0q%kFKxo)mo>?^C6x(!;h9twTYSu zE+o(+B(Y93G+teBj3SlE4Q`JNV9pSO2bamZ5zIExSpXTaEvJudr8hZh#Q7dQ-oHf} zuc$?^WZ7uh$QN%J4PMNfCRLQ$~BKP6o>BuY?>16fFeK)67`WWb(_| zXr}B*Fh_K2Uz~(?U`)W!wU|Vk28Et&IJP_j zkWd}J@i|7~s(?O3&bzH0?cLcSE6CU3N@1+X5E0oi|6nn-bsd0B?5!(iXy__dWTiiCaE%ZG+b`Cw zS)C8w!hONLGoI`8V7YoD;k?l48^ct&YzAjyVBQ5S1FkHTmONK~GfG?r)TA|CKkB8l zhXM@jZ6oV3iPG*2jOPX;!o$juypbY}>Nap{6xJ9z%(#_ew~PsnKGZ8rGhsesy;;x< zL4IuaM<&pWK{CZ0oSt~@lqleH5&;Lu^zq)|N6Z0AvA}^G7IxhV$RIT)3ZN`XIVO!* znyfFB;d@`eg~zA7Y-c_5Q#de00Jy$w%a-pJJ3_i%PX^%}7L8)Qu)JV29naO~>T(ir z&hB25t>DzoA|?DybA=!#-l6(L$qz@vdR>#yy`I!Ul>ri(ksX*?UUe8#%9RnL!R&QW zNQk{3aA`UM#$j5@Em%KL>p=qS?JYC|f@X?$2FLyC&Hv$-5)1DH%m#vsHE7DIDs|}N zK)z>VXZqB`F!6#n1-HqrxrSO|Xe|juiRED?J!0gl0fafGR)!E}tKh&>@p{6D9&e0W zmhcw6;$!jxhZP;-B;^cd8w*TOix*jITYf}`Y#vQ;t&jv%1VcAcMv5Lf zKXG#By=U_?D8*g(r0WJcwnoYrR=DcD>#Xd*1Tr)~&|7Rbz$jS7k55`Tkon`tg+5&I zf2nxGz_0k^GX1lgw{rw>)tyIPlW7b8Ypd@U5k~V*47FZ49S%852HuBNOJ`%J?SQew ztrzJg`5Mue;rL5+1z%GZF(9Epo3ocnZfQa1!ER1T5R<0^HCGgKx&C)B!T%T)QTsdD_etl^G-v%|ZHf1Y>0ij_CzP75PcjqcDl*Fv)i7(D2NbhTv!`NXC&K=|dC+lne%C{8mQy!5# zlP9mopO)bnV5usV<3dzBJFPydiD#EJCHx+khw;?|hue0znb;w2>~r?4rONg*%E99t zX1W1<N*N6mC` zZgmiBZEUnw&#?Vz(q+qK5xTD|N@abZj&52}vm;Zbcn(Z)gp9k+?s4>YGEXBp?E(Wl zB14!H&pDfT9V0uf8MIi^d!Ibm$}E!%sIif?IVc4z zxU4jioadG}0@K9k7H1-Q%T1YQd&LE-ITRvc`F^XHEH{bZcjU*AnPx6InQlX{Z{}Lo z8ZPpsyy`SLO2Dxv$lycorc?R=iQ zfScBb=^=o-NEjaaeVuY=b$Ed1bpiLbs-&a?8B2ygrSg`$jZRu2@m839=IV4Y1gkYZC9ehQ8A4&KHFOjqxIh84J)I}|#$mZED zv(XON=lP!c0m8^yMUJH*G%+PB4)`;_U~~ zkNZGa+!x>V3?2qiHwlP=azq8r(%`V?*K{T~N6}HseAVNr2Of99F9Qa|r>Takp&RKi z7iDY&^!F-npKgc$x3uTo9W8CtS93 ztx~Gwn1E_E+xt(5Sx;63@o_jS1i;;eegp?bwM5f1B85Okq5Qs`i%%3l+E31VWqy|+ z!z$O=DpK$4I?d}AMvl4vZ#jwW#Xcl>4e92lxx*E{S$|(ZnxereE>ysN^gtHDOTGS_ z!Uu_Aw1R-K!e8YPS54wN!{qkOt92?KV`WLnv{6Wkx}b=*IItOdibg?nzBPT!Lv*RS z7q4=n=1?YXl2pbiBc4j!;6^(Bq~?;*lfQ9a(*4YNJD(&@YgdO* zevD-uQQ-7!PZn(3a)u4s>Y0+@GqvD5h;aZirokJZSy0y)B7RKhk|7x3a$86LVfOo8 zyA;}k8WBz^v6>ac9>|`N=>xm_y|XRe;wb}f*NEuxhs>vMmUa9)Js&~|1>IZlT6jMx zgo`_&RO#}DvW)6~kmyXVb~Fp5&@Q&LSTsR-`KAAp3t;G*RH472J?8t|gjHiA2-;)^ zz!h&-C+19!zDpR*|IIMhaWh4e2mZ3(8o^n2g!D44Hs6K|Icw&QO!g;oMT}tAZ%>I- z4C^-V%&^YplP}$R{IGihKDBg};Z9kb{t{TWdbCb7RH7`aZl5OSIg+%6T{ZvMZ0?4~ zKTc{ry19AO#az#k)quF`Lsbcwy5YLfQN=JiAmfYY+|6#VDbk`OaXtK z)>+24^$~zsMHNH`E%(>Ow9vUNP3(D!?8jFi>_FoX0o?jU;s(3)SbmSc!cvVsp1w*3 zeIHL>Itthi48;Y*a7in(KY8eb2gLrM2UV?hC_b8PKG)Jx=dYZw%U(+`5%-%tcUC^1 zNvns>)>8)=xi=f+(~~vPioY~QMix{hLSpXd@AyK?#5?DNlin|xYI`{HRchRAOP#v6 zM9ZPS@?$d7ntbVtseKvZ9!`ZJb$C2S9R%GUZqL0?F1m<{is$1dB1e_@n84O0?GU%> z2?WZVqo(Af804j`# zI58M=)U=l4t@B|un09^sPZFL;dzp6!VCs#5rSiaeKz6j>ct!Qq)*XuqXs`uWkEj67 zyBgGeH=u<47)I$)CfFpPUrR2@rdL9TVkl;;sR8oqrY>YM0T%L;D0Zkq=BfVR(DiT% z?)zCF?NHaKUe5d1#Dxy4c`-CYRALY}W{hckp&&o+#Q1lBc1=RJ}#|-=cTsS5b*=OWfNMNfs`Sa#+#lVm4 zYYZsC1p^mbdxElWr9ar0>Wp?bx|tpXl@8`%0{nk9D-IiGB(WJN6&VDc3;I-$7jXM) za{t8f*O5s;Lje|Q8gpmObl3#Lr2+HT*L2}YW6#M7g0=i;+Kd+~VrvR6V2pH_V$5rT zG)bi*mJOs^Z-9KsgEiY=7r&@762{puC+%&)GFgXRsq5(?!iA5~pi{CYfR$9j?z}sz z>(<9qu%PU}+fjzKh}@fPp#SU{>hV@BKt`+4v4URcb=AhhPpr!F{@^Km-GR=26%x?7 zfVaviKW~2JCE{&@4(Yio4M@m3P`81NT-vdpGKH}yO+j{RzryTSH4Cb&;aIpYMSXSO zQ3#K4V?Bv;ql9ZebG}4I6A~BoFW_i`2>tRidc%r~JuTsMoyx?xLtyw4>J%9%1izCU z5TXS>5uA`TJ8^PhD-5%TTz`YG!a!O!ggWjuV4$YAW}~kFx8jAuOnDZAQrk4B7WcSGIx^cB zNfJZ3rYF+WCpjijb4L;rsvp#D3zmVmBbwmnxJ5Y>e})!S0pxgNNkyhLI#!3-tuwH)X9dgI^}9Xn?Xw>UfW45`)6cS&rGQW zMF)CzT{k8)*f*TUbR?Shl}Z!NbROpg*91gewc}gN*J~|+)b7((z%TA;MX1{u0p?(}#lKT()0R+2f#d+k zQQ%_{!V`u{R|<7JEC5SD>RA@+xl}@7bjNSYB6Ab5JA&}iHBAW`aU_mb=CtPaL(G{N zuQn3KURTDQE+WaDU0nBu$@{nhOhsY{aMChI)2`QAmEb%x)mjA@^s+!7n1bnbfKBJo z)(Gw-%Slx2W4Bbx=7N^|>W7Xwk&+>N9Ah&iH9IWETYCxtwBQy`ZoJkxe-v!7^J{6tykr!Gpl%6-lqL|EQ z9j*^^EE22cyIeSRW<}4S2DlUx?PCttmX3fP6j-@@@m~)u?{&)8D?h8`ni^>wd1vyaKGEA?XE`)xT55tynws6Q z1HCW|L^>vmS9n@^gYjH?ldokVVReLfMa93ZkfvJZ@Q_`%6>y$`BS?G9xozV2`ib0_ zImhdozqoiAIwf89v~VipqdC7Ag98CBKh9K{PB5EPxHTY?nioG}H_8& zHDu{v!(*lBH1*WCKC0wI$@50V9yE{X=^e&E@IL4uqb`Yx0G9l50z3Q@juaGUz1k3` zGm6Vrm-Z~shBhH7+VY>5fs%(%HZg{9)5WB)WfTI^$-Nqy+39 zV+Sl4afK6%bI(e{tUNy;2kAL;L~QU3bzr$|nTqos`$#fsHa&{#!J~33WcPpw;-Xy* zLkqt5GQn*B?`AKqpl>Y^-pyz~I92S{3I}&ir!Jr*A5%>)Zj0Z8fl_3p{-3r1S}Th1 z41%{8S6DJDB~0qeJ5&46t=?4zr3@D{vg+^pfd~-g>i_{k1XHE}*MnsZcQEknB!9fl z_I4EdOcVw?^w8SMMu;y7Y~yod=%pKnC+2)=!|F@u=)>Cz=ah@K;0CE(R_B-O-66KMP@ zI0b3@`uZ%m!i9|NfKC{*lV{<9!TTWIqrN?fg;wXfLs#l4iI93zl;?efRD%JRvw3!p z?-3JpFR&ywO!8o6QCuqGw(G4rA^!gwsk|0L09mlg+b6+xZXo7^s~Snic@04SUME8q zZzvAsI8(tL$W>Qt-s9HF0T}57(o!iVI9_0vr!YPY&-7rn&WEVi(xiW3zxF^s#4XbC zy-q}*8tK&HjJ18`d|D$P82Z_0eL1IZ4EMfcXLZq6_?2PI{P~&3Vcxy7@_ihCQEtvA zq@IYOsA2g?@ra^@L2cGfc0GQB_x)W#*7F(T8i$CKx|4&-kkNtIji#aM(HuZg8kN%* ziY9nsImuio*nTGYU^Nk}wc4YkSJv4%80JbYJzC(r&kb>x8+eQ zUm}{hGRWpzFYb>5H>+G9B$_a{GozbU&I{wA2|X*hmRQoY>tc3_EA3@a2O54PjUaMD z)qr*P$?72NKFi|nbh$$C?dCw^yPNi*sZ1c~co}A1-L?JVDqA}J+mykGxZ&2{jszZB zkBN3gc$ctlU}-Nkdwkqjlpuio`^B;#5{ykzNhHz~k37sw{X~D2$F3-yh`AmX>widmBZ^P%}|eIA1IfLT_pFj{8SRpKDw3<*bNC zYoe^1+M2-T+<EW(a&~o4$4qo zzlU3@FtJ{aH&T@_8*}JNKu-Z>$$U&FM8L^Pn-NP^oO+l_qCD;@UA@g&WG9i{Inz}<3OPN?F*)gcX zu@sYdWfv8+G~vkv>8$V)#=DFNy41Lc*}wm_K$RpCQD-H!`AfI!(*pv#2lrcN&N2dA z&hk}2#Zzv(zvtDkgX^2$+2RdCKcXzBt{U;R)ByD4ZDfk&SM(j7(i zBR0zz1?n1}mhyHm1^owFq#D15#!jh)?HI^t;MDCOxTWpw=i-eXjiAr~;Ir_zGqo!~ zSLXZRZV#%4BZ&tHg?a7sdQaShS`yoLU$dVV#pR{R?kA)X#pd>5OrrNwRo?TX{Y5yk z-!i`xT}24eY6hG@2Ib6bXM_PL!s~KSQB#)2q!vv zbm8}`SqqE0P3Z1r`hhk4nEtS=_pC-qlka;!4MXFd%X6^-2|uohdGRzX2(|_?8u0u+ z>oO@P1;bCHyJ>~ceId}M)@<$Ay;tk$xN@14f9-~J0dMBUw@Sw!Wc#H8Rjvk+yVNl| zLBYXsYV8=j7tV-er^~zen6k}WUE>&3N1WVue zOYEU^afdon_X;rwVP6A%nw4xzC}Ijn;C-=gfxF4C2Y>_RL5QD-?R>Bv_~@cK`(9M! z6j3GP_Grl_hIe456^ShTmWA&~i7817dO#>CidnpKiz#8*rhgGqc`;AbkQzK42yj5W zmYNWx1?XF+m3#Z6MnK!mp-50?P4sT*@E1}N_ftZj)^d_fNdgxTkwC>6>>+1MjZlFd zl9EkHU4sMpcxiTToiA$qYGuZwg%H1(K)23+`jybVy+gEJc zb~3Runb@|SE6K!mGO=yj=ETOH`+lEye{1g_-+K4YUT1e#cXb`rb#&F)wr$wlpVUss zl({nlD+BjKF`84;<}m}i#^swCHM%(t3dDd{u+uhSYiRB2zB?tFsZQ}*8p!?whDYZ=e3Lw!uB(KALYO+A&^sFIgu%&+yWrB)7zNV;R zoMYr0+}P4x7e6n@TZ~LxiEbMX7foau07@FdMlr=xy;HU9r`!k_$H$x+EU1@DdGrs# z;9LZrIsU}i9~Dn8@2LRV2Mz+6MKjL*3;FU)K?Q&3!%$Si9sREXOZ4-r9V1F z*y^$&&c#J&%!DD`h8yxai9XWe0S`YQI$`ZANR~HM8Q|`Xk;rX|p6rQ#fpwsJ4o3D( zM{06<>CRocx2dYJ^1^Y%Ep5Q-NDsCN{soTyz`I~KL#)mQDW5@QXMgMNjfw0#vd+9G z)fuQJv?SLV!8IHOgkX=`edsb*)Q{zfmaN-Akt%_=RzcDF+o5*2N$;~}UV*+spOrdK z-Ifp3bZoD?pfU0N2wq=bRC4G{h|LKq>Buqv;pnBgZOhT-!c)gQ7H`D@yINepyp zuOW9SIFmZhBT9B0PwG3d1T&Wkar&5>?6KZ`A&l3I3x5O(Jgt9pIA6{=P(z+>We+H( z?1U_KLW`S0cX+;?Q7uAt!MIF26ft#h^71Dzg||3k@%={d3K)UO zYRU(vae2h(8%1{{q2^Vhi=Xl{n^2;YNE z(*2<>dYHQ8VlKK?H5LW6lO^qL=q!BB$BMmiML4I+_a?UAzFXoyN-?I!w_^aa=N@&5 zm?AN_WHT-ul+Vot?~2;ZiX3lxqnx87CZqL4S8!As~N^upp}c*E-&oTen-EA-srJoCy+BTwk9=Hb&xuq%lR zlAQA&3d;gbtM1Q49a>*y8xbRY7(nX<+;Y~q4FB63%}4E#tt0hjM1u|N!+;wA&h}T| zX(>!RIc1k?@6jV(E`og-lg3Sy%{p+54$Jq2<~pQ}%SDlN)8}@%zNF~Z472g8;ENVj zvOAitQX#)!TtAM#DszsiNaNjMo$m(b zV`rwArFVq8K|T^zsd|ZX$u%MQYAqnVH+IL0h6t<1yTA}K-bMfuS}alj~+$A#y9BXkwKv{IhPLI!->s@m5%k0b+fL)0k2?{3Zjxr&7maYlk}j2cOwE-#4TNCUiWU| z&XYb8Q3yQHBG$U=ILcGzcx7nPqbYmf`F+@Gx5cjcCJY4-1Yls=9M4(iXwdOh*~p}R zFXh9RmM11g2?0=D367k?gmtZHw3|1n*=XYOBQLO3SB(@`6^NWk4^Or9tCn;&O4Ze+ z4L_IUKrGs;7XCUQQSmNb0@T26hTl{0SZU5BM?sQ>`YA`P` zW7zD9w>ETyWYg?OT2cZhjEn_w&2TpX5n41kUC?)8O(ehZ7e~yw_witEu`qmZvwhrE z`%#8X$Bj)Ut%aUPjX>1wh~+Y4xQEoCp%~#*(QoOp{?K`DI=rXqcy^{2$6OC<&!RnB z_}xVJlpD6pmQ^;LEd;Ukn#lRCY7kO7%}n~~t)0B!t2?DFMO|8hL{}UBlo9z%YOEL* zQr97OnhAhchW8C$A`R&`Vr??9pD$dlhGJ}VZh;o`L&kJ- z@+<4y@h!YL32@xvN*83M8RwOqc^Sj~zQOWnd~RWoPB(A5yQPEbab1J^gSfoe`9Bt5 z9w_`;`jHY;P+Wi1Lf?&2*8>rPBzEZ8qp3p~LBzPc2%*-9u(a=BLp$-FJJ&Zgbgjef)?y)iDAf6 zPdtaf)BXeJp&GZ=d#dicR+HAHI!XK|xGF12$dLDTQ~7hdPKT;gq;y=6=Q+-plQ58 z(!gL#3%#zl!)WKFPwoj@$ICIW&RX$UxT1nSG8|uf2k;JQf~LVPtx-D0m>u|#&xn2a zlpyVy!6(1Hp)IRZMy&uIWjb6h{?{7v>AQVOuX)wUVuzpf+F6tibcd4cbin$XT*D7c2m7 zY~i6R|6FOD6e`QRWv9dZ*0hxOxNyOii;h2Gi9HGmi;N{H`hk}m5?bl{JY!iy=32*8 z`@6ECqu%by(u(nUkl0Jsa`;9gr)f!ab{ilO00ngaDUN>3d&forwJKe)#p8om`$^=6R_2^~no z+54YI1ItgBU`v^@?la)st~P~=NrUOoi}=hD>YZe4w9OpIBaA(hVkJ0(q&3h1#P!gm z7!vDYsZkk(Ss%YKfEzdNyZNiLs8DCiIz<7-JY<@Dh>}VM=3Io`v{Y_GIiu^08p0A^ z*6{CWnctsT;GJLm*S)c0ogaeO;Nd8-(D-(y(w1_vBwswot1W(6Q5AfteaH&fhE(uN zLFFhaWI702@f;8~#HAGn%Et8x^SZ3@JWnxgcQnX?kLj)nK|Hzq4r?vjVt<`HE7P{A z9s}dB4Xg3hUdY7|I$8^=YD8*{xP>wd+WLj|Sa@t81gp6JB*n`-pxQ_>eT+9rVS%6hP;LC_k zMfQ&s%S)DS-H&{C=jRUQSjpX#Nret8bzQJj=cO)y(|)QmxosrTG8E&W14@n9YTE0! zp(rXoI)2w>d&T!&z)Nv_G|a<~+YERV(lBlR5!<&fE=HGZ#AD6D1qfohc(gSqqFk`Q z3yxK)2r|Wez}J~ttH_>Td^AF{7aUvdh2f|{WVsdDq3CYULrabLj1~IYukn)Y6rkz2 zzWdXfEW7RcrhonM)O9Bh7!S-+D0vED@@s;sCadZ8bvF50KNlt}Y+xiE93&d0854X< z6Z~a{aQOCBrbGWZuh^wh`RW{0FS{)Ri@e5|8hSyGeu3lBXBJZiuF+639;AR^5-0Lm zQf8;>7?LF)#pCO=i;#R2EL6sk#|~mL09}x+n533^bq-uJ=FJM7u~SQ>kOp|oe(oiofm@d*5 zec(E|!+<4QW&5C|K8+*_sPuEYn@-L~Bis9qh_KnBT7-z8M8k)AER~y$3dr}Ar;kU$ zktu%|&$78=Z?8e{tr=H?>t%1h7lDy$8jB?Cv~shaKV45-1A|k4dn}0I-u~)6FF>Vq zr9X=rt?B1Q5?7kqn7+aSY9O@Y0`VURAbl)|=WiG@6Wb|5Jh~C(2gC8^rEmMSS0gZh zb&Sc~ZokUAKR4*CH4 zU1Pk$>rTOpB*NxSY2t93AWO{c*NGa@@0HtaL%H`P!-V+qA?K6SW^CzZ5vSLH((WG! zi$lgnY4fZm@iC)=rGbNfEqzJtcuYqTkJfmo(SEi*TwQ$oSX!Jye^xtLTlos6aWj`} z8&R2|R!z;HS;p;@nB~uL8UjZL7qZ7f4!>2wPwKPB@$56SpL8xYc3tOQ$Mj3i1IX`A z$i6CmQGdO~9l^V5dY#jzue~2dEfZH(95+#%cmUJeLRi1Hx~Zwhx?4#_GhTIbp;hxr z{LbS@bSf>Ncpru?!q{HTtN97>`&<^z5^QJ$#nwW#g@$rl3aK_<`%b9agZ-rtBlLeA z^0m!QZ)_oI;FvAoahfdV#yo{UgC^z~aqL!F9!UmUW!o)&)8o-5TQOY8L4kp0LW0PY z^f*Ic$^tFBjs}ctLLkcY`yH`IKMliNb(U81zOFASK0Vt8HJ24gFa)Sq2|sJIcs&2b zUpPu7B45Lv_O(QA3(cl#!*6Bq?_X0lh0vpmi5$9fk<;!Mh)vM-VA{xQ@_!unZb*L- zOkGA}WgS7s`N9-iO@?bi53`-E-o>kXrMR@MZH-5p{G3&0E@1h2vz3`U>!pr$jfExPxxxVxWXrJ%y7)!CK-Yx-g3OOiy(;-dE7sy^v55z z=2*sQ$cQCh2pTX)2lduBner$=hhmK!7_9mNYq+KseG~L%npt7>O zA7Qu2B5apz3Y%4S!x26PvH$pN+|cfXjj456&fyP?3xE7trtwJf*+l})1wapw&?7w$ zZ^zlbGTF7^aSegbFFt>wV=nq99%ey%KZJ;0t})zfhKd{68)@?rcjF#txGnIT1cY?G!Jh^(dxzAG zA!ax&29M?1ZdN*-UU>d~bmBwm1e-t#+X^}^`mkrDK6G~xZY4Nf^Je+cBu7>-AbLc| z)gAj~zZHi1-tzc6cfiMK<0GnUogQpU4kWoaE6Lk*_ne=6l^iA#m7Fuy3}@CTn#Bc{ zrm)aOMWm2KCOT=naJ%}a+(-{sq&Cl8U%oG&8$MzL6;eu=V|z=(_v*4*EFxzT&Fla)2d` zOvO*c%&o_-+cGM9U-LYvbJ~?Bc^v-veC<|C?n}ugiNDKO;ZyTURXNDRApa9yQtQ*k z9|yRgQ@lbm+-b(g1@oF*1X})KU0mw=2pcrH*`M|q)g%@Ge0JYjDkKU#_K4F=4bhLp z@QiW=y`GJp=Tc)u<-Y$!Xxg1L^|btG@w?o#iCq)?&ACRpfrS@>g%yi(OdhoyFb?qWYKIRt~X3GkJPj8*J^f3|(y{q<(2BQC<+9~~0xLloY1NEGx| zYprj%PlK)J&T@`sFuyrj;ayt9l!m*;ie))0-uh6*A`}O>Y2hg!$VrAVRS;O*5Txd- z)YYrjOED@a9tyiOwh}^$u0F@_!uz{!53X84TXq~#06Nu_)Z~RD?wnL~&WSO);q?Wb zL7aq^i=j)jDN!kjIyHEfG4x7d>yx^+FSE`Im@Ludv>RB;BoX7iFxeqJ%@%`_berxM z0#S>c_iukYgkJMJny?7sdm)*=l8WwJ4B#iIhrsv27(pG>ziv>KHr|H` zzPSsh0)O+;-7KJze-kU!(f>G_pH3TJoIbMy_<#l?afS3%Gh=7sV88y`LI|>#ZId23uPq+fO+JlXC1D!pV*rdV#J>`aAUMN8 zfEbb>jD+oEAShS?C;)4SzDmScTx_`w1>_zBCEfr6&B%XyP7#4sq20G`S1T{f(@Q4| zpuS25JX<4x=PxCOA->r}_ZvSE+4D;n8KPXDU?$eaZ*jhRB|K)HG+~{gKa?@eru}c21I+DDfp@&!&XCHM?x&*+p*^xvHj+N1e^mw=ErkBp(f}|493QP+ z%!tQrZ!9<10Wl=V5-mOLn`mhHXHLL(hi@)=%>Pk&urTq&2uAVNzB^Xj=J0G>I$U(m z5poZYo6E{x;<6+Nk#JQUjxYFsl*RmC=Lhot)9mRYt)*(s8}S*0{?7*gN5eoQc9c;5 zsyl4W<@e_Qp!a__k5E>U^psZMt-EZlx&9xKwymI@jx0y-9w1Ww0L9_$R*Ee z)8?u;5ausPgIiP(^Pe2?z)Ah%^1#5t4Ez6`;}{E6AfTErF`+Py-%&Yoo~6RHaiOKg zS4F)dw<}b^=q#A&|B^{20Wu90_uGZV>K|JG68m#p0$s%xCc}&Bkpv!-lVv+4-5KW?Thl@xrebJ zy?)@tj`)~F(9)2pS0L}6jy07R=3%(w>~aX+S4M%2VvZnTwjnwy@T%H0Q^F|2`Btw6U4+EA18dD zI!npgL1!wo@dnjEKNv=|0kX&WrVhOrLo_ z>YxG|5@0bk?RcU;_}qH!nKlwMFZRIhpNLCQhbfkadZzdDQgaIYr6;d^De;IU!DmmPqT-%%Lx8pJ)s&F3X^NYnb{-3HbI^E)X zd#wU}3Nb8vylzDpIU-bv$YPJ_nPJo&+sv|=>r@xGI-iCrv57pGcd|^sR;r8&I)33> zx9IX}DlhMT*=v$9^+oJ1bQ)F`;foP?ch^Y#dxK-E-OHu`*p!7Iel}#T`Dfk0ok!t;+xQv3j9syD1xKNWNW0q7%NlGf=##TY&fSjO3 zXXw9D91QFz;mx-rGQ3i>wXV~Tf}qNtXxg=jQ7lGi){OTEnLZYNg8li*fxq@n=`9@q zQYt;sLK!S%j<|v+#FXMV`eL4oUdU~ai3R`YZbndw_A!y@C~xx}3mY11A7RHa-kD|C zNm+TfE)N1t)*nxT#mus~>SuvDeS=4?-}t-`yv(%~10e!wpa#P~h!q`(xFKH{yZS*dBI;0flqG( zl*irGi0XOsBpJSD1mY|Mnd#co2M;$tu|~+Tr-F8Dq=6OK;1$UgA|=dU~Dm zK)>-jI@>wOddw&E00$N@UCEu_N6xxo4%H%jc-cV*j$J1lvo=hY9*20ESEVDTZ;YEs zF&ISOyYxgZr%7GU!eaNSdy$HccQbrWChz=Rh;i$R5g^t-I}dIs65UtAeuBH-q7QUn zTuC29QvqLDOlNXqC_>F$zC3S*bX_4@^N80~m)Uhn7Fi|TWSSxQSNx+%cTvN`wr@gE zV1oS3QS{M1x$JD#gE|ot;pjIT-l3YQ-kIM}h}cHLy@S5ntWH_XJ)Vq3X}2m#jiFMV z`KYkbM%nIO3V9=di(F^Sqr!0i@{F<0q8O*xvgVakVrbH{(Zu67eH}#+_Zn4(rpY1` z8}>u`*JrU5uDACBerq{^gasu>aZg40!>u2u6tYS)Uu(ea+|#y8DtU$p)3BeYMCs{; z0ZDIcE0yBSRv(jpY~Qla%$ewDg67gvyGYP+LfA~Dz|bz03e{YXqd1yK2zbvx?$g~kKcO*}pFNJzOwmHv z5T2B8Yp_+Eok-**=9&ldV{X#}DjqWKFQO7c@){6`)+THBM}8%WuwEg8x5P!x2O@#~v! z!%9%*_Z&?dG^t~QCRW;j;+HW!FZ|$g(+9z?i3P5=5#=zNEY;e+u>L|5q8DS#RXgt? zz;9~FxPIfYLHg;Cl0fn@EghMoLy^QZ6ZX~R9>o+RY(NOu43?h#XNfx_71qUl}^RzfR}`XsTOC}(SyOa`s5xS$5mz){X3^*X8HNH+*nD) zD^Z8)sLxh+UnKU+)*cUVv9+${2t9iEB=aKTLG-R^EuEG*cKU^w66wg`eQMNI2wnDMH4O#AUf%;U-^4tC_vPmAf?!>Lt3>xm zp!C$A_MPK}Y28h6U3T`F)1l3CebN}?;SkB!5TJFLC9GYrc`3oOIDSjB<`sx_`We-rQg=UNN z)8lZK!MsTg(MCtR&(+2@6r8^{5xitu>)EPWLJA@UGaUlE9J!|#okGeMf0XrCuacv) z@MV zo6)_t;9TIPZl#?sZ%lrq2th6}SCFhN8}`dCjiDBgHbYW?A+YINvgaoL2o8Gw?gDUMh?#2sOX9gl1}`Hx&&lfm}|%GLC_?zzUoH?wpKBajn}xW zVvX;v+%k=8^2{)eTQC{xoX%F0|F(M84M8!wfcg=FZ7)o<0cyYm^whhB|nf)4I&%CQ)UGQ5&D-gUp7vF9=#FFHb4u?dIPd5ZO~wDyi+{C)MzqeeIj&i*b=F)3&-xW*1WD zsmbre$U~m7c~C+%pBOA40qx2{!F{v2wjDYd%-kwoqb%Xn7m30*1Z`$2-k5QXoAF`2 zlALijtV^GnSwstF`SX1Kz>m?(xJ{rv{#Nxt*rDE~Fzb8xzVITEiR3fqkwX@dMU zyR8P^cg01WLFZPjrSzn$o(1}ORwFJ6hq6v6xfW%+)zPodk>YgoLjFsO`xB=_8|{f^uLpznj-0C zD|*_xE+#GKYN-F1>Y78-WgFfy_OVb>|1xtfztFPuRO!^#QS~~fO}@)wGN_ms1P~5& zr4@!T3YRChYCq+Tt92p%nHp-3E9u2$Ki3NSUX{5ymHJru3PUn6&ngtWrn%@K@X%4d zBp5YQw!XhB3p34~zqVtBJ0cS&;Gvzk61XbC@WVL~X;`Kq?f3=XL;npy{)cDP+O>@X zhc5AF$2N@nZyU}B(r>hMMb1(ye=Rd2ENBxkdXJ9V@iitS_sIHQffSY}%l8Qlw%`4> zOWEj`F^R0%#>bCpIfuzE$Lp@zMX$Kxl)aW4%479-NQ*s2G(J-^98R^Z7ff(k zWeSBH8frpl1|xf>qRn`S1%2vCpgQOAF_kLA9bdQYg+V)lD#i|yGU<*Zho;h1Cyla^ zLPU2*dGl-c$F@D!>Ojp##K9^3$i63cXqR^wn1QNxW~`MR!O=v~Uc;ASyV{)@gl$gV z^oJV*1AmOKG|fme6FR~8_VYY=vD9o0v1=&a!f!_J1|Nvj8dr@egFg0!0{uI);bU4k z_9O2<8=USkcO1FdUIKxwdO{4x8ImCeb)l#6yX|yk{#A=wJmShwa*t4|DITRT0KEI; zS|os+a?2nR;Z(E`qX@^wsA-{e?#mR$p<37ZRo3qN&8_G+_omO;@zO)t93n|rLS+5Z zynUB;73g17nlewf`W#pRJ}2@e6m(@;)dhSXCkd>E`;eIv&UZBYcSm2#^Ai;ZipaKo zt9ZeQlDyye&*zuD`W77B8TS>k(Hk}Wd1iPCA1KKdgU5t2NTAX*d5ooUgvg137AD8>Abfm_vgo?lHx zEnjhopY9rd_@lRRfsYlJ9D-7e%K}RpJzB2?PJUNM-^ZOZ9ADMd&|pc%!&~E~=H-S) z1-Nr+P)pTUQjh&2qmI->2VAP%;tslh#P68t5|{l$jLoWz)S(L#Qs6=*f?7=VQjJh9 zFBN2?tn?u}u;Es*8-HHFAAU1kHI7zztD=xpYFrOx#|J2Q=kHvwBoxghk{?q98Xtm@ zSi`9k2Ycuk?TI|l)E6HZe{rhX^aumwE_+BhY+;3ab33rghyCQ7{<{_cK}s^5T$(Wj zt3f2d8jVF{dPuHHKH1|GtGJ-9wV$uW~w;*jA}I%@-p;#@_FD**qv&h>$<;%&VP#S zHgHP(?!QLKP~d_|Gh2J$Bh!qa6`L_4p5{o{6p;{oPijBY%RZFqaG@OtY&O6#qAK+y zn@z9Tz5wO~whyD1dJhk7S))|H5$A#X}EP#r2rSZvPgVQZAql_gH zegwn7#j1Gp*5r|y1Aj+WNVjq82Z%suxgb8~D$s+dMhh!9EL8HzBJ659RCv~Kh$54= zA$>m{Gg`{eC>AX;nn?P|8rN(LIO&C3D|T$97^l6OncrUDRsSKjdB&N|ye}J&YuI|p z*-h9)Y*K#-IdCz6NS|Nt5Cw)c3H0Y$#wcHtBuiS*gDmES!_VI)IYD-pOz$UW%K60G zY*?-v?wbU*oF@z<&tseGl|jk!NEV?9QPhqg7>KKW>yt8yg&dQ=mxhF<15aO+Kgcf@;x5z-8q} zrq5ycKc7KXiBK|1YCL2Z^{W9J$-6M+LUe_bu(ez;kU#G!tq$(YrpK`0-S6i?~w(JGO+f(3|>=So6E=Rbd6*mx_sIRq-KYiDFf-NxZy_2_?bIJY#-_ zJsn1@ZeEzszR$+onqCRt1onbUOa|(bx|!Lc*pMCkNy9;ZzdVn)bt7diS^2^6pF)qq zN<0#bJiA-O5yOHKEKPlNm?4lfKJ=gQ&m96*c(Z#COxh-Ld@|Q{Ha%rSsp9`Xt^>In zRgxv_96K%C{m{L6|DfVAg*1A{oHMrd9GBZcSVz=BGwRapRe3BK{BNCjr1S0c7ZrrC z_)wQpo!VpXFfb85B)I8Cq;73q>gj6xNV3baYcT^+gA~B^}fL3nwP~jwd{8i&lk0{uX9~L)zdL7#t0`$ zx^0i#3|i$H15ry(8?@-%e~BSf7q%&C=hahZEE2_&i?{N!hIS+cR}O$Nu;gt%!7bT^)s($FDd4Vv%XBh}VGo zCR(9Wa6c4Pvzh_6D>diUN*48>pS(!=<3~tiv_fMZwk6}mgAH;fiJgIOeefIAS5&E+ z&cYqoXPgy5vH~CsQI?e<=4W47I1jPiFlQD8ZMx#VJ0S)X4687em^VHMfIk zDP8ocRQP5S1jTqF+P>wz?qF(103yOZQG>Z+Xiq+ek>Hb$%53D*VU>5i zEX4t@$f#4Y5NzA+KJ3b9E1A5~{YG_iTu!!mPD9F{52=ft3lDzJK}q#W}L)99Ck5_Sl_G z`qswW)EDX~T%0I#Yfo#QEgIyyh#+u$6ta~VLq#o+wW++^>yBLBB>#w-7SUcMZ_Z_B zdq%XR6NgM6EP;MBFP)q$kkOGpa>gbFQ>>x|2jeWVga(eYcfZkS^d*|(Bxq6qVm;W! z*U<{3#b1mvH&4N%zchQoEE0DesU96`d_2({CyCR2pl1>pd_F1p+?qQda;U^fOfzaG0C85mJ_p>4^lE(wO{B50LZg*{ud-Q;2TLVT9{((MsI?H>_BYGMF~Hh zHrQnUXn|4N=wH1UB_IOgtk2U3ujsb!0U6Djf&=WvUBMB0Ny+@M!3Qy(EgGje8a533 z5YbP3671(FAhQ`+96==u^#S;A#rahM4NeX0vY-(3B(y$pH3kp>h>~6nyZ`-&i`O!v zcR_*6Jp&F<<*wSfOLjnWmH6MUbZ*1~aSB1PaM2B41`+NZj&uTut9*#G?+t+;W)O)@ zx(E$~t>d|f6j;crZ?4uC#af(cM-K+kJT@1hr{j(noV@vK%z|mU3KyQ;_+fgLvLO{2 z`=gcL@~hxkSf>%8{EFOU4`r(KFIDX`G;RV9H3)mPfGN<8dCa4d(li)|Z(fS#KZZv@k03=k?>ZjJ?htFQi1 zHAv3tq~gGPgF%`(emNb@BS=tibI z73RG4juI$_rJ}IE(NgJ0)y`>SvvWheADft#)%9I422x9wSZQ8d#5@}JEM%EeNHZf~ z^S=>Dp6TO@eBhZ}xSi)5hB@jXw2uitMn)Ly;Dn1pnt56;X329dwI_$ruuZqG4y06% zMuMR=9IrddaLn)^2ls_lZ>HFKa+9P3qyJv=m0T^2cik%W#fm~;7mkloXpd4M#1_=} z(Whh-$Cq;q+kY0|$Lb2u|MR*sOIl!^GRBn)oJtCYs2!6#Dhw0+1f^lxNPWWt%X&G% zz|0O8@ImOsygMMpR_#b3nW^U0ezgCh#rc@j_41qdNWzZ>N&Os{h39Sr$2%x=(`9R&;u1 zFJB~b^Lqv1cN3e7j1@?F<0+lh`O+qbpr-tMB!e$EWdU)bwhu}PGt+sP{?uTX$+ZNRBg!n9H9qYRoMiij?_+(BlpU=eoMGNN$kP*xziLRE)rupuM7nm8!B})#| zdHH1MEio)Qu`b=6H0++A_$7(KM%SuBBjC1YoKb8qfVXkGNqH4yS>xg%6Wk2LF`kC* z&zd#p$t!lJXywJ9gH(RqdX?|To()qa#!Gy70&ZaF>C^|Xthvae!A44(ss%ne%@eHA z*4NSJF9+Xk<*!*7rTa5_0QR85nRVnS=Od0mM|K31KX%$L$%u=8=XzgA5zgQJOiWB}=i(xpQeccM{T|fb zXUs4&HQwJ0y*NYHmelQ7)?*&A{-*$qVn9d;?T_!cicoBRu#p$>ox#4W@Y)8F>KuWm z=41qF9k_4$FVgT>xCS2T6i-9%1Q*e_*6bcYYbI^Vr-Ojxc_Z@%Z1OETF11;cVVU>& z>9H4h+e4J1lgi^(whu%~TiOXGipOLIK=>#0y8eYknN^xg(@&yMnsB%jakR2He@)To zP5VpLcjU|p@L0ft=B%i9?O&*gcdvdN!5e<&S8n+9D`FTCY5h)VcbtC{yg@{p;-05T zoC(>o8s^;=0DsxESv$l(TqVfdzKJ#3oP6@6&Ts^QTgh5qeKBOvj;Q6$w%xnmj59m@ zi#%lN`^IM|-n6y`HhbsUF%uoZ_CIU6%gH)yBLhcdFIMFd@JU8F3&`_+G7GTVq@) zR)-!lOj7Buf5JDi~Xm-dF6`vH5 zpA65a6n7%;FB1R7^)phcXSOLHN21$J=PH@FLr+;elxAUYZtNizalrlf{;&{? z4}RrN)O9ng3TDJ{zm7*3Ku8bcRhMdL0>vItx2rXk(JMoGd--8NOdpV z-XYayH+q&)?W#(=u|fU`Sfwnp=M$x51rxm~gxz300VaYN6&8awz zp;iW%Tl%P9rk+1IH4Yg80GyPy5iD*!Xs<`on#UYy<=}!+BVJ`qk%z41(Q1qM>?pfC z^dA@D6QIw%tV%7$%`4WeQf@2F9PA22t1XH6R>;Nqi{C5r2~;kGyp1@wL1g!1?fMZD z_GYcjFR+>FYY$leO*s&8^17TTm;&ht>IuGs^_GI?$3p$rQb7=>|Zw}(iYFUC#GRGPWvh1+izHv%h-7)<~fj%ZBs zKrm9$^zl_GvHU~6@HI6Ey*d%qzzL|Fu>d$JD5%q#UpRNtP%mDYDffi+AkXORJSVt=Bw1U^Qm~Wy^qyTYi{>0#nZpn#M z4YzL;5bNODaHE)h;UbbAULIVXI0}*hBCxGrGXdBjMggF2_7c zd_i}6ztv(pxPpZl+|d(#F8OopB__cjWDx{Gd(K(Gjx`d&Og1qYlRru%+|QiFVrgtU z209$YVaU*_0}8LDZw#ymiP+zdFkvL-){zq`ma-#84)aa0HI35Rc=CYcH0Yid(^Dfy z2B2Q^24}%Zlgi+>G-2>=CdVf`4~RjQ`$JxI82E&u zH@)`Tpo;0T!P~D*E45*-VYhA`4UoZ0=>D=-<;HIEg7-Tk4k54V-V65|?K#x&9Zw{VW@W<^5OU#$3PF zN+tnTHbf2PS{dva!U{Y-t#z^?~g-Q@t8~)ka;3UrDh_i>rf{ee^Aij zH2Pd!`hDS+LJiP59|Kc0yPOVV%d^{=xa_2N*A#Cm-3~Px^7L|x;N_Ozua>Qg_ADL* z%o4F3Y{Tr{XFG@#gSHKp9@bsJdKvO;X?3Ud8n6E25DW~tV)J=|6kaYXrc`g;RjsiJe(`+RNRsvEKpGD?)7%4D)#X^T5uQBpT+&w17Jc z=iv*)EW43e%klaq=*wfAz~I3A(^1&|IL{?N^mOQQQoc-ji!hYp%{Eh-%~$L9Tp7>b zeZaUfguRzzqmvfg{l$3$SCk21+Ei&S-tU#;xbIcb=lC*}~6^7}TwGs%$I zN_$u5M=fCizQot^=sHun9@iLj*@BP;1OtGh-k;lGpN_1uF#Jt7uO&i)?^|hS)?{K6 zAWYge+hIM%mu=VZ7G!xGO%5H`SmH&WUA&C4r+pz_z7o`_Cz^)Bc5&%d^hZ0gFwM~Q zs7aUa*dei*`V@BET>YoiUO$8o?zEv+{iO1*;*|Lb5bG~azdeIjtXZ9AP5X3dU0BBu ziM|B#L;Zk>YMJoeKX^FqUBN%nwb(uz%H0hs+ff>f^Oyc-nw7xwWo-y)~ z3!+7>y8ISeEjm#hm($ zjNp&xxfj7#ZQHhO zSC@IpHGS`WZ_Uh_xxe-P+qq8evokWjFETPSB4BG=Nu&ke#JQ?}N*_4=;o!!l{1YlD z2}g>&(F`C3Alv8z8h2AP=44$_(&izRKw|RaoRozBBA4^Qtp;h>Uzmx;012`HHTU=Z zp1sL7tN;?Ux;bV!vh^t$cAvI8+?StD9V=4#z}9#Jyu(5&FPN>lrIz8ZW}s@|4!f3N zY(qB2GgS}T4mfgAuc9z9^OZ17(f~ltLPc(otPnIwfO*M`y zDvVL1`VHe2F7G+Wb;pdhXz97awd1vgm%1leK0WB32^Qlnm}n}&tEPaw-y+z{Z8KAf z6JoEW&JmY92a_WE@(cDh^6tsD=-jLvK)SmCO>~6s>-9 z3%)wPuNo2F|AfcW`;WiETEq9TxK;|QCnOOX#J7WI^EGx__ZuG%?$>$^R+4pH_&?UM zD(ma}h5dHJQ^I~R*8{$d%X}pi&Qg_$ z{6tZ6gKqQ<6pbEU3bqVg8rVF%%;njzoR744?J2q%%L57 zR;qeW(M54$)0{P9ov7;nPEYtn4#E$Vd2B?}+LiZRLPb+yNj5Zo2cnRzpv2rM22&e) zFt_ITWa|h25cV|Cd-pF(J@AA>ZXy5cSd{Btz5cb|(`+2j*!{7j-urE6#@uVliycj% zsTtbsPVz*hxnwbX`}uc{hS*Dm3OCt3>tiE_Wb1n%)Fl%&JKGFJMGpIX!r ztby9G1NXD;&m8<1F=Xtytqg1r{!+`qsi8G9#p5awxsO$-R+))_74N4t9`L<}$K8mc zJSq~^nRc-o9?HSJ?5|%32vSqKE_CycaanOCg;48DT?OJ;BPp)}*+{J-UGJ08z=SKO zj$pDF>1H?-Ex4}wn9jqUy-BP-zhtSfFvl)|NS5vlwm`MIHOKV7jAT}G@Y?JDJP$q> zoiZUT!d@v&t>~Ega!?};T@Qu2$UlMeh4GmS7N1T3p560XM|8M_^G=_^N#x7>(#XEP zFx9CKk`GcQhjZi8%Q*MBRgXyO&ar*E7n+Z&y^`7tkAzarh1oq4kBfNk;;y(}c|?~G zJgLKKZH3U}_lzf2N|l#Y#y77jBH#Pb@%RBO890)=HLQQak5+v5re50%Z#l9(2$b6B zW;@ptWt;62e8GN8_It67Wp>uwg z`U|d65hiK-JfVtRz<+Sa;C}f~t6VhM<0H*yX2l=3yYUGx@UY{B=1drs$rvBPUbE8F zDt4KxWcPT*u;b^nQtXC__-!`1L)~bVqw4=OwCZu>#^43xZ;>`mnX4UA<&5$sz^|J| zgME;@-rV*M;s)hW58KOSQ*OiFT4i%DBW8lyFq3cyDp)hNvdb4I8k-^ns%|!dRjAq7 zwpGbNx6bZMQW6>+2)7hnlKQ)J)90sK#|k~Or-G+jG_1Z55q3?;uK;?I)>MLaYAuO+ zBcyFl;au*G{wrlQc8?<@sk>UNmfkaH+fQ7-qkN-qr`vxKhQ&WnxiU^%tP~p}W zjrOH|k#w4pa=7fbS#+2^RmcE1wiOFg929{=QMW-sqgw@GsTz*IQogkL1Nk01dW=(2zvgq2#K;UFJU zaKzlRAqYC5p2u(|ncS_pl_!JP{U2%*g~1>qto~#WT?40oQ=|@hn%)PiHrheEYjO*| zJfB1xA6^oQtu!6%9bE*JSYCyCWpLi$taZ6zhZRHxyTS7^+`-kp>bkM}%O?eZBVV8g zOfg3&audbTc?YE!5S-h$Zp;+mDi7aRhT~0A1fV$g7PJ|sF`*2=i4gVc2NO9!y^Ti% zAqG6NeuXa?>MNaKH=URE^Cnb~7lLe*aCr=wgjq^g95eeq3ftKaqp2vIk-IV#UC-C{ zFzq;Wb=3I{0-Swy$ta>uutde$oBL%=8U zEts&AtNLJ6qx0*6cDbqx0`87JWp6VwCwlcYi0q z6;B1+2>fkM3>kV_aaVc;+OP|5@aqsHVG&xqa!)okN_Fq$acaAwmk2Hafs{u46dTzHGjVFRAATIv#q?hxj}Me z^r}-GK9Og+$JkxzR>*!y8%W&y`LFcly_KA{VzJi-t6#|TivXuKQTWHYvTF|(FBngl ziM1#P)<>#l(|B>hDT&85+4zX=nME%MsY@!(qGD9^u~6 zeiGn|XYAUb;_2Lza&{@I@bqDW8&hQpBzAMOsT{DzI*jD@bp<`2*T8mioxR%tU%_Q4cCg@t2N|ZUz1l*Z5+#+ zH}sCSn%?TvR}VWap`xNMPmxP@!ZB-|yWLAh%gn?|=B?SP88RBX-VA%kx3>@L@Q4TP zu#)d!bOn9K2s91CU&luAiB#%`cCluqAurMIQ_y>kDM{WlCb75y_EhS>r9p5;5;OfbT z&>0%wt_Cy+eHRsRB%2(}IDz{H$j}8MPTaWte1d)(7fOr`hD`J8Cu=qh6&~y|M2`^d z_glEe{Kl`BWU)ZW#pEI!2)~F;G;fZG>kEDEpA0vGG~;k`#~@RSfOZ{>KC!bY6}yAO z#`6v835`su&nPJu*rT?jKa6*(%zt16me^kg%VKf|zc|ZyZ$J|}4J}5fgz}~MDY0mu z%8+2~7ay`64aD+Xql?4QxXv-Iz#DRB^qQJ&$fBZR1!6^&sXq`#ep*(F8`hfh65>hYZe9K&&(9NQm;RxY1YG~s6*q*na~4|URb$*_hf zbPZN$q%pc+ofSpz!#a~1BjB*f#8LA-qFcX=GJkDVTc@(xwz>7LyFn0ng*P5w&O8^A zcgIBN!83#;e9yWFh9aoF^RX8Wc0md^&#U9FXwGb^tsGEz1W>w#ud$(Dj;3-M*n7L~ z;#|MKbw)5#SX;;dC!@=JZvM*W0lWR?F04B6?XuPBax2678Ry`3z(gHB6R?c?SM~-B zO0uCQQzTP++eQ+3g%4Yu{IefNa7ZWu4Tz4C5V3+@=}2PeqS8x8IE!o)Ign7WeR~P4h^n(>wg*XWOF8+KXl$Zt=EHsZ!b;8LcdM=njQc*9Pl*Gt{1CHY0;J-Tl zD}QycBUS2sMBpfVu=~}QPhV*Kma2lz=QPH!L1zWyY)Y*>NkU?7e$*6I<2JOhuAcOiI|E%oiLC5a(G~00ftm|eN`s-c z{_C!!t@aXbyMKBnExfv|(SdFZEYn0u=+rSptrs-+t;)K|HT&Z~N{JMI3Wr zp%2JgokG4LaYR@4_OQHoH_rYL1=Js1+^%{CkhP$X7Zb7Y3-(iw=9u5F)6lgnqH*Fl z#N_N;|B^PT#7%E9^7Ccwhgv(y{4xUt=e3_CrTf&+z)t#UmI1$vX|mdiq3dm1(9r8j?pxHJ znA#s2wOQ;Zr^zrOtE_|x^)QaHMPEafNt{O%e??}Djl=j;aGHG8629W#PF^J~bt9QB zcC-&c?&)e%Ws7=7^Qob$omU7Xr^7QSr?lYladwGEU=VfO#GXCR(To{Y1d0U)GQKmF z-TwT%?02jEqE}&v=~s7JoflW(y~Ol^5-G<}G<+!yFKq9ngc^{IE@{%N(=lp4M@^7)ohU4cgq~8`ajX!8e$fI8fc3*t zDXd=lE!GaGtBj^_y7!l4+QBtl2NiBod)f|cjgaBaPJ)m$>V>ns;HoTU(r<=Z#Ok72 zn-b2U)7C9ty}(r?jI*ccz1Ec+>v5p?)5blaL^R!K-rA$){OUqT>zJMzqXn>F811&5|B8vRWj9R6C`U9 zd{%@XRt&&0RELwjI=Ek68@)+xy5`3duu>4W{+k-t<$*TKQo+Cg2Uo1sgAi9NECGt z^u$0ldHumtzxXesO>_t@rcpfAR8Lg5k+0rQs?t7~#F)5r)i)PrekS>~W@#9@aQ0hx zj#W@ zv{$+FCTy6uh#cLLc2i&_{(V6V3BJeE&uQCb@lU=d9HHklIXMJe{$-A?&nuuBJ;GE1 zK9b7kBWl%FErscoRpd=NA4wo9setlwN-Ae2!`h?U$Ly}Kb^FOC@PAlI7&9lmi`~D! zXx;1j(6i#yTAtHipJLNE+T%nwIMGfpXVjFeE%GM-7m%jZIwqrs5tz}T%RWQFS5Yp^%|?ykFec;iJ| z@8FT$-H}-#-e0rcYymx@O3FPshUw>e4ZE^NNjmsx_$_IOJ4dnt{>eDSk~*D zCwtcArIRi_l-TXxj>qsnX9*Fz0Md@B(NMdENhwlz)Du|Tw|{3wLlf^ob(n}avj+A)O0tsC~W*6$BW zi9i5`haNj}%{Gvxp`*Olp+!#XFaB}VW0!8iof{I28vuv4-i?Rk(!gpk_d@i0XHl#Wi za0v=3D|$w&GMBqC+u4|HeOMn4KN??70VU$`Dav{A0pFKo>$h!m>c5I+U2bMaVG(q_ z2jmqP7=cqNXBNSJ_OmblcJWYH%dx7m>Azl1@`#rudjy!-3?Qs(|4EW`fLPCmW{(J!*6DB3 z2fO3pS-LL82XJm06O;MDyV%>!34cevDs^Hs&LXtsSk4KmuNRBj*7gDOk=-db)D9N3 zp{N0piCY&9)^0^GkoVTgeU~3RyKvAlE8HPgLS5)UM97#8AR^s z|0z^F40)lNVjiDLLK=_M1f@+wIF5NB)I{V*Z6Xeg5++07ToCG$YF5^z0 zc8L4GN=nNz*@#BsQl^A(wl>t#5p-6%_wc5NjmPRo$^SN(->jq`ImGND?P%-!-{q9S zXVgjc3vuYNAP{%c3A%!6-ux!6$$w^C3}|T8s-GTk%LJ1Y%cBc?MG_$jWlJ-E zjNgfkA00^4S@)v@w|N7%5CP}5+zTrtrWt})mjv(S2Jp3Sv0lf=j~#P?)NxPpXdKN{mgdkrV-d?y@X=g$zzzn8U^ejOLomeARw@BkfC;P^A6k;H)lS+g<*ma#3B@ zZu{riS$)&H7QK#l%1-U}2c=N|YqlymroG>k!Z!(er`am$_o4on1baUpi^vjgC&VGY=wy?WCs$ zu_308gPdXW>B8J14nYT&L9jw8xdQ+idNwjj_)CEZ?NQO4C5-iFya{Tl8V7s<&UrIe zQ;;~IW0HNDO&}sOq0Us9qZu8y6iVEMhg?EofqYoX*$OFY=Fb+NXYX`hsmsO_@8O=} z@Z93**|GiTlwn^%nj-FTbftj3L+2PdC&vH!9fIwfu>~s&7(_rX5<^@9r^a&h#+0b- z>-GLsQSyUwv<Se@*;tYSJ6jfMMuD4{PFS|%A=Y1kt%dd=Q#zjB zn0tFX1S-?>R4(fm;vim5K!Ho?OaJYwtQdbvr~2NAH@y67 z(o_`IeNr4*ktQ^NktNF%IS{`^n9KwX(a>~{Yz=0kZJmHzM}Pr?UQ@?vGs~qH;nnC+ zdouoUfe^7F9Bq13PLm@|mu(GKqt)qrNt?h+r*LU8+vW0SvTbtj9LI!rD?5jyWl*bJlgo^gNfxbJkfT~uCDdbe(xL1oS^ zU1(}{S(05&&s&hCxNjel6{=rm>-h39if7yuw|cHkGQD!YL@ot0vY#P2m<2Dtv@&|X z+lbO<`64G;mtX2_K-`=QPPJ5>J>Ks!xe>Az%f}yGLT*BQ;bKTa!M4QdB(Si_n=K@v zh+ctU)($!rsXO4By#u!d?k5qRv^6z!uU+afoM*XsSK=>Kqh*%Yi-`(QU>si>4+9Tm z*ezH31q=5&im1@du37_M`wcM|VmFOX421w#{;*w_2an&qPOHQ6x^A|N%456stK?yR zJpcsguzA8Tn>?XVJ9NQ;p*yZdHF(Eq7ZR4={*2Gqupfw22z%-x!CnJx>6Emq46?-l z2`ph#J-sX5;pdX0>S=tf>0G0PJ+7gbHbjmO_~GIb88g<*>n2)5-7y|uYP;z=PS=rQ zQU?LNX!eeiFuBLf{3hyVzQxda^9D#$W7dK?rHd)Bp2}G(%WfFl_pRsfgu;HB{DepE zCWKToKm=I}ro#4I!;Lh(JWD3pCca!B@Ogc;B^WV!NwnX|y@Tqhq=BF4Szs&78A@J5 z*^O=4nH8zAneQ6EW~W(FpY1mZg6ethOM8O8)aa}jDz+| zoGxPQ!V5ZcWsY8_r}k2E1xL0=LH@j%!lOceXPom-w_ayXaNM$y*c|xm=HQn)mtxI>9o=#IfPMAk8ma3WucKw<7v+if9{89OsXToT>o<h?SqQ0-w?J~z3;vBgT!5`Jn?JrTjCI&+&cz1D?95aQ6R!#9lG$BiiYTc_h0Yb zsy9O~7JaB7+Q4cDZ}>Ds_hf}9Z$Q=kh)Yi|;NXn4Q8fJ{d+=uu2+VR^@AWhp7rB$n zd>uHNxU0qI*u`B1{a;3rv)W&MQ1oN)}wQKzCsZI*Q>yZ zGa8*vsKDDlWS!C)0rCQf!kYRY1ID7J=_Y2#u;uaK$RA^jE?vBc* zS@n`8WJx1Utz9n;9;^_?K2$<@>3vOR{wGOD%tnf8xfBIBO3?-u;+=`2>V+0YJ(RiN zJ#pV%7*WVV(kKQeuKh<<2EK=Fgie^;S;}$fR?@f3j9Z7toiCy->hOubslQnwG5 zfWwIsUf?e6FPl=Qd9>+7tb7PJ#BDd%1$8n9eWFh(YMSob9(Qu$p-qhofGosO&C-kJ z>rHAk7F^4pHRij6i9WcEX8Te?%^(bIAZzfqB!oz}pgSVLWc9gD8Lh zWPi9w^!-`=MjKL@tQ-AiFb}szLXOT-_WK>;MCsafmP>sZ@3?aELiq-4{q+sdIL@9VQ#I;xX+_Q);f~RhOzBQA&%2 z0g-r9v$JJ!_oOuK@mMr$AeuG~9B|lL@Y?pS?8x32O9K|neh6L|-9+lXnmKJwye-qR zFAR2fIVdLcIq$xFmuLK3O<+7ncpmD0veIEdw;mVo*wdvw8hYl6CZMouUuj89&)RX% zt|{K|p601@*xM5hP$ThuWBD;%o}2PjfEd%vERj|^TXhqrJk(UZr>LKM6n8RYMa_doPUySmU0Du? z)ct`V7ZCh3tTJ4`^?1QJ9(K&*x)e58KjkIAPJ@8}Aa-7x z$4!G2R`{B4uc%Bb^=@cgV3?+jHUSBssK~qI4WFhP;Lr?G0ORq%XVs6myA^YXwdw7Y z{M`#W<(cFVR4YwRg~Dsf4To)+G{pIm2-UM^gY9ltA+*^LWe8t1*V|>iY8HX0wa5ox zjvp{0=zB3^#WKT73eVda<5g2 zkS zwLyFnK?5Pa!q=fW^Z-haRAf%hgMMo)Qh;o9;&>5Xt1p!*Qj0HiVrVR<)k!cEo)4@x z(1O8SyCRuQEl;dav$EN+@>~=dYVqjAM#uH)M|n-reO{68tXZtBoS`mwZ|!O8OqT(} zH=KpFLgL1pB`R4m1L!f&*S(VwHjOd2w}P5%5oqgqZ>4%SHyVG4YiaVyIx;tkRCqym zuTtBTmH3%jDf0OS077a4c(d1ad%EwLUpph^aQP)APN(;!D;6{}4io=>@e_n;Y*G^X0lWgcF5k-*)Vcd|pheCP3sp zt0N-=CI3EEplFj0s&NB%=Ph?|AV>JH?*-z~Vpbd6ZI#<)6>s$y3^jxX*5ANhgzVGrd6A=nK zjK*eM(`P+;H?#g7n^%vVZOQ>nH!yr80!?codlyR=EB_9qs`_~+j~?zd?RJ({b=Ljx z=8P=op`@YxsA2$@l{nP?johIt{jkZd&+q`m>SLl>cwqn2NfLzRNL$~38AILoRbEgP zwz}M_Dx@<(5^fP3W1?K;%H(=MLC7M!+!8+<|HKHq4{w^1OS= zw=%YbFkUwBO^%h#ZM(MQ9j}>D>Nq3?Rr6Uem0pR?=eo&hq3qXomsX`pmWTT(IZmH~ zFHuqoq07-@pAm{|QDNsp;dHNYpRGP+;{!munr5c#%722YN;@+2r>mb?@>E-L7k;QJ z4$SR48KqTFVPrP{w-y2L<{kPPM!*!rqo2<-z;WFB+^`AsxE!)Gx!+ckXSP3R%uw^Y z6=VNwSBwjD0_|}H!rJUaYklWFD)SB{HUqqh7n+W!=jD9d8$S^$$0_DOB|cd^?rci|Z)+xV8OgNz4C@^ifduqxhhQ#^k`F?-D*M^P(XSpUS(n1Q+Mgsre?h2uHP4XYmStMcOSaf zR53`t!{Lzs9ShKN5QCw7fDgBo3Y=M=)_2e33EpgR@st9$HRuQio-=wlOQ_>Xcz=Gl zm-EDiahNHc4?BjxQI*8jKepcLvpKyx%6I22XA(I7lJ|gZ_5n()9TlLj-zOOIx~luU zwk=@Wh2hC?BiVh{ETz0PPhM!L(V_juooY1w)iTkbTQaye|9J?s;qxUPc#@9|e|$vR z@vUhkgtMH~Q0m^+*T4Y8&j)@+4}F1|MdDu=M$y&+%h|6rCHzsrKVbWtjj6V%BYJ7r z0>Lwig)UNm`)JbD4sKuQg?<*5g80C20ekLQhb5294Mfjz-amI)P!6&`rX1Zsx(@Y? z7I}uuE&jkg1VrDpO8uIO%_1$(Y}v)EdZ#Jl`#P+TR%gq-{GyN3_llQUD>B3?R=qa zddG=$0D9x;m&Qo-)U~rWxl21kALslEf9?Efpjz6vZrGN$3ZKfVh+LX@ISMjfX|k>Y z<@C=&)>cnm8cnTVD9%57=GC!2#tGKqwE38WkHF@CodVz*Ubq0tSGwtu^V&8Gm>o+^ zCUqFTwr#l=ujLVD5U3e+QJvW?*Igd2oab0$I5B)h#iG8>f@;lh^2CUy%MUI*6z2#Q z6DGEC4n`9}(4FrL_QjyD5_drwD{pdh%8&G~;4#5BHWDAmT0WQ#ZX%7RG5xpvmTAY6 zXHK$h<=Y%pUgNv<%{pss-dQ~K=WkiIgxI1*{_|KKuW_SnG95n{=z~4K9GIMyS8_mX zliy|?AQDtpwXX^oFpKc#)Db*OB9(lOc0F3OKUl!LwoCDB0fzXI<9y> z99df?p4Duf2RnW4ET<%UG;TMwR+t74_U^;^jI;2IN?ioO()1uF7N567fSW;+u^jBj z9477^B6TQ7)wfD4mLzVweXz-vW2cMuq}8@fLUB;W<#}vdOR~us`oLbeeieAjhpiB1 z(XlVxq`={&)B%Nw<3-a@w}35v%M-9BZV zg5Nd9^67~y(nzqBBYQ(b!LR|N!Tu{Wos@C{S6*2iUAcWN5h8uQk;}M8Cs+g;!QhLh zo}dx8z?h`aL0*Mui6Oj)M`tOALFx8<&qrjUzab|BO$1CHRZt8)I(Hg{B?Tj;F|UQg zrkhouTqi)#DLntWIrw}Bt2pD!BU5ozKy4_Ovuc~Qe~q)PG{f6n4MU%KIA+`z)Kugi z0y80_i|sf_p^`JxteJT5+TLVwWF6~+KO*UN)jy0gEF7(pL>KbOf?FLgE^uyPo2M?Z zZfRUAWfe4qO>gGE@YmxZVq$~aB3tYxqQ%A^%#mtpeoTNJ9|mDJ6K_qkRt2(Iu-ot| zFEt2Yvl`N~Z%d0q85~WSj*s3RdOA3$ti&*ZfKuh&R(eQ!{zih*3T9OR$MCx$&i*~I zf1>mddI(Iny4A^ojng^uPDx1AYxVu*f&OOQz;Byq9@X@`q`$~|A?m2W{neMp1k=Lj z7}@}--chygM@UWRYU0W-E&^@fr3%~9;N=3u5jac+Kj=#TQ`Nj+xzB#57gscb zTBv(Q4RZG9g_Fj${?@$r!5^VdDIyDbI>9q2XQZND_b@;L^c>8fjO>>Bv`H=1|JEq) z%M%y8?EY2vC!~3LC_bE}H`)+!$%EImIw3fAw8*>_Ns77(IXQ(@AD5vDG2YEg344@F zX@#nkM<=3dIe%oZ9W=ZInWK(aa#Fz8{T`wd?ND8OO!L5-VkOOG+?nOjSj03=R?vKR zJQVXO{QkrO6B5wfQaF-vs++-RX0!A09uU&96L8BKi{l>L|>09%Y5-nMbmFx z>Cbqg;jI8QIOC_QOJ5&sTgkx5@&}Lg)Bj6+a){|!wa=Uup0lV-M7XnYHy5AH;5A;J zhT4MSh@KOe#03u6_)Fa0swP>lO9EA52kdqhM8Rj9dAA)%a4JKm{HkuBhhGk*w@_jT zL)p=i&ftM)y4b!RuWQE-bsy&du|`>dEc$nAg_L1ui(<|8$NPPz#HT}fUNsdR+4KW= zDu#9554FEG@CI+x132X4N&{E1JwN|om2wfSCX}m`&DH67Xvy2D0!a$yY>!Cji3jyN z=sMaTuzI?FAD^=CRW!pjB; zj#N~PSAu(%@Y!cCE(~?8rV*z_KqYpF{``(70_`jQ_lQBrkYPdo%&4IVVvj$u0FbL% zee?h4y+YN@Up#g{M%gY|M&WYUuzsETeE*;TeDR#;G<_vHkT>B(#Dq209HKA_ZG(j$ z|D;l}gX@ih90kkZ2_Hb)=SucxCN%3nCS;A@c)|{elV|+%u5`gcoonL<`ln={oJ^1A1aH z=y0BEC;*1y4bWk*_TV;B#p+9-SA8U>cQ8S*K)32RwW=d}2s=Igf$Z$A7D+WSU;p};*`aiz(zZsWxtfjaK>h32 z{7FgugcU;lw4~ytV@yn+zaeF$pm}Xv&NY2JHcp>;JglA-%<=@G&yxMu6f=X~I9>M2 z3lNXrdb@wsZAqQa+G?>^U4AImqiB4@5e^&_{TG>feo@wt*5+VtY2lj=x-AIN8Gkby z&TClus__vl)Ju;i9Aq6qZ-!%tjuj~j4g5ed3+atG#UV^JaOYN0+zB-Y%@2TOWOO(rrA zDLx03aq3UAyf8ySwEh+G{0^P79}HKzb9)t&7xs;C@-elgmdU#SVwm&~}GKwG1`UkPA( z^{HRm@E3~RA;8!Wg2p3@^CC)0BK%m48FAP{91UO&J2k_xx|)ox@_iV^y0^Sj6>6K` zwz~pHOSLe%1s-a9eOe@@0Qe?NTcx!!8e93QTy{^H71%rj(h1G*{|$jWVqc5}qiDg+ zE-4)xxVdGVZh4vO@ywAyBOo=qXzBj4_nBc}ILT3DnNKA4APwlr+ZH5DDTGsFBXvp& zEzz@)>tb;C3X~|Cb}_Iv#Mm4?1i>LmW3-y&s*##=6UYmG7Yo|SaR)1i&;Pyg6j1z-V3#!TE@h5^nKn}G1%oj4 zxB+cO=A~KCl=Y0of|-yk`IBWv#(R@Y96W^HrDT z(%3>!@RN&o1X(>6;WOJ$#@iVPX0dW}+BAv3;p`o{u6 z5L?jA0W>xx8Ue80>kMRW)a2$y4cn&LV0)A#f2aY6f%XRX(|gWVjfX^=LDFRyKfv(|eL?hr(cM6} zzZKOqc~AFbzB=P-)zsTqq&sgub&4oCOeEQ+f$}YM_uowu2$>xjc(f|SVRFs4hN^c7 zb^6D^!Hx{%tEW|Im`XB!-^{0I=s(mLDbdB3$=vU{X4^v3>yo2h@sd{RdRM%YvFrz>^S6VDLKqgn4VbFoOJyKKl*vatH zlGma5=i}jHd$g7(jisq+VF#>{Be$d^Iccn32%ZIE%A&i^m-)b}em20{P^ z+$}^EO_7cG)@rpRMbGzzHcgE=ZJ5!C8|HfHV3tB=_^VUe@hsju{~z-cUrA`&8(!i-Cp`X~?1`MtbD_ zco9gUF)(IK&?0O$=>1vjBIt2ba6`~wa-dcBLiomq~o;S)X0+ZAAhf5S?5f08<}`+5;{axJm;TB`IwI} zn-;^;s3u>fwW|O#=^T&?xI3MR>_pc{Cf2@p8h5JcaKoWH2?okig3`Z$VjKwxgL{|` zmkk|~@RdZJ8~ovZJOJsqk+CpntV5V~iljzNHAjt_5#%gW_u?4rwPdc6D}>+lwwa6> zOz(YX-|9ck3Bc50EjiW0TSt@#stp2P4_J9V9(hY+yg0$Lb>dlw?n`o#@%;4vU2#PI z2_i&4VWk}M#!U`GeKGRJf4Z)2 z`<^|YCH>-*xA8(0{+M%REcG1Q1a^RJ7V5rT|1#GmGHAfZSi>H>n{eqej_Nr*<{FHN- z!1m5Ls}{J4tXkG5Gl?M+xmBQ`6!19Aql*wN7hZc=U*@kqTQsjufP{CSw9VSTJD1Jm zcO#k5 zx$&5#ic0E2|5?EC*~>@M68yu5Wb}Mc+($O~FqGjtG(olJb4hUNFWE<4GB_(&(Q#*Z zk2}nK)nQNP_tF%}$#n<5C9lP!z8m7FgAv@rLH5JMfXDkoF3PMW{E`$$B^X0(ZP>p& zKGtEylYo$NRBeDETd3+$)AQEfiqyh=3>s_hiQwnJ(j42z`13DCIKTq_+g)TYhk4$e zoB!Z~_Hg(sBg4VogD56T7u-szg`r>kjxFSBl|bmyP(cf?D0386u+J|Js4Gg103(mV zggx>G(v{Db-5Ylpu5=7a*9uWEpHsG|#Z)LDPWmC*7!GZO5O&}x4W^R24?|agtd};^ zk&f2Zh%Jgtu%?4fR6|8c|Fuh>fX-7gF$x*O_*Ifu_PY-monvPuP0LTDu8|^lfDHUV zI?jnWIRdj45zTx;XM=?sk4IxchfADdcZ9^sA!}r@nLA*zs1DvDGu(lqiHQt`hDtKT zUu3Y9^~wx&=&$IKmQgq9b3AbJy?q^#*d!8^gu6n!kQ{FOWYq{R@dQDrLR)^+2T>Gt{N9EF9k2 z(jGb-w??KimbjmIIRlN*{S46WjU*X-hCRs%{~9IY;24tA^&Js)np#RRTv<)Ru>lH^ zi+{}5Iv_)A$}U+H4?ElI*qt9RB zJD$EAWda|J`Aq_*JE{VAcoR;%AX`I@y5_*%fBO9_hpLy>0j0ae~@t4hP86Olzt z94Q$0KX^`@tKgU+c%bN^^2avs9O^a{d-9vf@g!4q4&R1?TArezR2y%uJ~@^%QRmh|2eG6~f*mY|8eWN8f<@ECKciBkI?mK>>dwdGNXB7S zsip<(Vt=r68jTyVI|KJ)HX&JRDYK|gqZkuXfHH@S+b_(G#To6$($HLoxe_R5grrV4mWVSeH|k>*0o{R$*O! z=bZ-j@fOmk%wA{rE#owx(AVuYcrGva&q6rTr&3*nZhXA2IjZ~9w#i1vS3>ma`F=QD zOVaBFLkSojV-~{~E&l;fj%)M<&g(NKaYqNxEL23ZaY)_dx?;@D{$V=VkeE*6cFw!+ z#$%sCKopyDRXJ(%@z~!ZDu$ z&O?03dQ+t#igQGsAS4{- zmbzBdrZ&0Q{h}hMs~~W3=0x-%UT!uV)Cm&x>rGZk?sPw9t@312tL6i`0Vrd!gePVq;_HNGN$&g=X{MUk=-Zqr=CU@fuxHlBQPF__ zqxGhY2<80YhVNixi|b&u)`*oqCmp=#NQYkRlY@6;mHYBb*mdZB#T4D5#N}rgVlRqy z8$YJPelr*|wQPr@oT6iDq-pzKjJ$JvW$V^8SQRHbwr$(CZ9A!072CG0if!Ar?TRWk zy6T*JyYJiG_k8c4Ywz`2n)8_$bBrgZ9$wF+#-fQg6+VHwR!Jd_;Gw1*Ykx&q$pj8u zlTD<_I$f$pbh*?d*q#t~&{4!y=;9=2FL(et%EFPJl-{txmSE}{zmwdbQ|jyDvRzLj z|2@m*QtX}ZG@^(`v?gq&$~GIZ=EF^Jamb@|B^dh?#wT z>c`#&ey?49K$jBwaNY3&a-i;*e~VmPL~YjqdgqvM_0j08IE4v7q6D;lpRz>TMtSd! zkwm}~(H%GRGjZe?#g4Ky^(a2GStbfYR#P>59}z#n-yqS`pXt?@AYF4ucS`9L#`#!^ zfYi-4BV7~Ia*Ey5lt%u^YpahGTiu2(*%Ya@BcGCj!64!1UfF)4+a9aP6EhMGEH9lo zzko92O{sBH?7;)EBvsUEi|_o1Ga*`7z&igYePx8b!|)v$_3ygC3qImPLGtt^e*22* zpQ`zM$iPB>hwn5%LN+muHAN~F50#Q4-EDZIL}>x)V(_CwVX!2j2=`Oa65gS&ZK7?} zh(N#!mq!yIc;#+wL5^tQaoCQ>@1AwSAn>=;Vc!};dH%!>?=Jg-kA+q`p66oh;qa@j zlrT<(x|^H+oFHJFImW}iY8q4de zt6l67eR?Cik&AAwC`ZhCW!7R^RHs%>(-$cAa4K#Z6z_$}Bt^!?^oIw?9+dgZN0w?w zpD#NII}zeih};MK5;w^g4Fm>i3+XAL?LZ|QT)8l0`1JF6{#vR0l5kwz%w0m}rcV5` zkv=CxLk@7Mu&x#QgXr^ao)6Z&(S`ImkmaBU?{xf|as749=eCSwYSOOHo2&iPFgs-x zdhu4-fU+C@!xpyk9iLW_S^z^BxX*5wRc3pDEaR+AY>IdB5a@#h+oeK<`djfY+LCX) zq!W#$3J%%HDxq6(pJECOueT|}@EB8KN`SnS3}m~4Zl)jbMsL~~>1&vAiHC1q&#Qj< z7yv8>^I0=9)a4A)=J)JXzZ1i#7ej4++x19fz$b*T<$T);Zzuf}^f+1n2w?4>05x#L zKhcHTBrpyDwxpadtydfnts8uw;3PADH(RHVlVC2h9_<{G=GP;x4MI9Ote*%Zo4$rV z2@=D6ryGcc-V6nV7UtK|e^O6PEp~vRm72}ijJDC2DUgFlU<=O{dam-2A`>1~xZ0er zGmoj>Ro9h+XdVo~LXNZWFt(|t}invnnqyL-eQ{DvbR^2=P`A+dP>$iB4 z7H{Ae45JLuP>f|=sZELkA-iYmSiaa$vReMrOWjg#e#~8EUc6vA41Tia!rc~~0W=5+ zT{TfLav|s|(Klo@Y9;ak5mmzpgW?U_n90o@^jj(XjG%WZg=Tks4vJz&1fMB^GXp;d+ z!|EH}FS1+=66r)>9?WxpGp)pj?*&R!%@XVLxk$iJ-ZsSIZ4&G75KvGIikTn}67rxT zbIHfl!TS=;71Z73yxJQM-ByM+G+Dp{lg<_qH?xkj9>em(Ee~DM55L* zd8a3neh27EC2Ri&m?BL6EFqVbHpx9}hL=*1qC*zl7Z>rQ!3yQm|IkXlkY{s!nwIqnkAR zWymAK+AWK6#2jPW8yK%;;@@0=OIw!4j|3N;M5aN}@gw;G| z$P2c5ZRKSLzow^4B8K`I|L>{lQ~peS*P~iv{6jtmIgaQ6!n(1C?ZX{oiSC&DMfs+C z1m}3VfTB`m239NO(C|JKQZsSb=NC3ra}ki=J!JBB*MpM=)u(r}M%HMaQQq97*L93z z&u%?jrdmGk;&QBSl}(4t?Uhf`c;jdDl2z12#Tc+bN@MU2ZeweZ$UA&&Ce>g`qO8Up z>Fvb))Y~wz2r01ozNd1FE6IKQ&?ddpA#X(UA;yrtXyG z?6fyGA@@_4i$AU`LYldr?begO?%c<8_455ui42Yv5qwDe6wl{tQu1?9^>uE%6F5>9 zGFf=}xff`bPX4r`BPdY~NO1nQ>7QRVyv>!Dsc%iz{0K(Z&iB#^KJug%@shV~>#iN$ z>MBHVwRO3H{t1frQ$htm>9W9DbE2Q*F3yajDBjX$nJLYcDaC<*N6s@GtAgRV-RrE< z*I3Hc_$lSN-hjk9JnR@37-ygFJHp26UGK}11n52kbF`p2FU|xVdQHUA8y;ZxbX8WVJg)NctVMZnSk6I&7~C- zp_P=wcE)n`;SECu?(WmRdfUnn`I@PLtyBc;cjF!_O2MFd2 zUUL0URe1`6W7mZ3K?8z?Oc!n~7a0kn8aW71t(Tq9S(+I+WDlSi?Uz3?ImqIogxSs5 zpH_n8m{$AX{&_$pi(jE}#A?8~lsn&H!^UFhmZIu#a}bt1z(~ljyLjuQ`zBq4qcZG@ z%ALTK!i85m5W?|QHQ!@aBXVjQ^$$u6dxnlbZ~gNM&N;bows~L~vxD-&t1p_QRhgS= z2%K-UrU!h1FDvi+g;<+KEgtoep`fhW^81qd=0g@yvDPu3ha0=3VKs)WbJ6l=sj~mk=J8}P^I*Qo1TN?I@`eVmT}Tl3~tN# z-arG>0Bg5(c8D7wVY-*cLI?o4_4MQ>rLu^w#_Zw56rZ4t_o-LiHZprLTC4rym7jUO)h2ztPn$84(^^Qf_-(S-B37aB)T1`j5F)L z4v*Atpu0Dy&WnWOpAMdErFnDYIxA}jh+vJm#3Fl%XL0&yo%_)mhyhL<W_iJ8jnyqAUUo`!U^#}Ls z8pVaJ*v&OsM|z*vRxsuDzYFvg3}UD9s>N5UmCi^Pjp^wg>;*cOCik(`e*_bf52y@n zYW#+4%E)|6&?07S?q_gLEEmrW=Z+0A)Cf767KwKY-n=;fpxa1Y<1B$xDNOtobic(( zV22$-bpfY|qpS>@tb{Vmsl?;fd^Ha` z)NZE(6bIIrDpB_`7h2jU#2F#TXNHy?RS^$yhOX&6PYsuY$Ms%!1Y4D?;S%knCZ2vI zWeoDC;H-w4eDb1+kUSEWw{mrY(KX#aPCdCK^bMe1U=Qk;=&Ro+ACVhxyY^@t6g%F{ z=tvslpWePRBDHwyhz4k>n|Yb}+zxD5P|R{|+1t`q@lbmf2(MJfhPdWEA^jpR0cF+r zTo-Kxxo&F+-&SYj(%K^$SN}6H*&TtxG8ME*tm7PRjSEyne0Eh$#_M!YNG1}Yni?+j zGKpVZ#A2&DZTnalFJx^$(n@_6&>9x)3A0NcGZ28xiwFpwA4-_Bxc zVD8reFve$1OpK}VB%Y#YaFOU2LHC0~Ju$Wu3p2)h0(fa`YIbmiDToH5h!>*Z zt@#abIHD9so2;d%pAL>huc7~OxZOll6G2J7M@bgx|8gPa zqLMiAj`py~jnj>g=dZN_du)(%7=~D=$TU~V#&xe|4KNgF9mnIGb>UQY>%5D@B-+9m zA2z%@im|sEZc4qC7E~~L_pE++^tqRmpz0@d3&~oQITsqp3b($ltU~!Ep{&@7-FoZI zt3lXzZ@+3+1=|bk zBo6vRp}dH5%tk4<7tJ(Z2>QnmM!${W4<7Mac3l&?iV0`Ge=0%_(;ONP&Y(Ame6 z(+efTwl#Ljb{;D2^!gGL#2*oLvd^E!yLAd)8Ol)e-{qh-?t2-nuvtqXy6J{fwHC5W z*!@mS1CboY^^$G5#0A_)1gI?ON4cwwj~vn!!B1?VVo`Ow+dw_$kLRkk{1;yhkOUQD z-Rm+Nt++HS{q80UFzp-Zs1v667uTaiv7GGlP~6J~48zFn^Ak;1!VqUAQuYzkGUHVa zq?N_P;Zv+snV41CYgf_x*OE0XHIW-+JQCyEct?$#_#40)e_YP6qO;A@2D#%0PYU4_~kcv*JMCnKt88jaevWUVU=XtTlNI#bOGPgG|>L7RJT}i)LE; zrH@~Oq%aMeIMf>I`UOd~q6c=R!^AcLu+fvl3VTC@{Y+fZk?;rQc$F=X=h({$_Y7f2tB^&@f$30j_bmGB(5Pf!+^>Qj$?+2@OH zcM+`_CJdVW_38J@B3AF4st8HeysZ~L5t-M53F|YaYyX(8-JF_*IKaLL*X+vid_v&Om4n^UGAO=3v()4O?(sPnUq3fVXd z<$uvnFIYh)!w;A`FJRZ_3b@0b>t_D5;6WNaxro!U`5^0lIngZ@&i2gX{%!6Cx>cvW zV?G$hx~2K<*UyDBf*^$(@>)(q;*Tj0$65vL+Bigelo8 zti{C4_X)m;AMTT%zsBZOHkD!u_B%*W3JduL<9u@NW~K(!+!mOJx!Tx3(u~^0*531Q zqC^(8d~SSXEPwdi@ad9&-*`B2m>L=D_SoT>A+#BT@HYu?9l1+1J!W4)T`tAKJ=`O& zk28MQBkC?fUWDr+N(xg{549ClMcW|bk?z~P|CO*?6tVPARS_i+I=})Y?fMZOxkSgZ zIZ!yq6h;-<5o7dxxD}>>JwwZGFSN#rP(i*H)$VY*o`4#-6V=L z0wl;Yn?0}QVE!NoKyk5|);U6&@%x$d*wEmelAWb&n)uZk#aS3xxaXgfJQln>dZ^l+ z$QQyEm45Ik6QN&by$r(z(ZQA&fc0(`1Q`VvtXQj#xDwZD+BVrccX)p)7-9rQ0E}{s z(Pt|GWSh{g1?jZNgbJaj4dk-M>?PEHm?dfF_-kq^ezyzz>;*H5h022P8o|J5=;=!{ z8}*}GRt3IB&36)PWgFQ@Dzx)x1_aEIP{}6M;vghRJ5#%ESh<&knV+xXzEvVh#kCHsyDN=XX+~obfFx z`vczK;&;j8A%MHYD^md|Ag>oux4g-ZqvIM!W|au9{gzb#aj$lr=(yt#X zeXD%?bofU;NyO^LW=f8)Ut7eh5h5<^Mh%^6*CO>dx%XZE@>sCY+;~eQh>!gt+{V~K zIzTjO#nOMmEl*{dc4!0j;2isHRqOLZ*c)WWcM@~jIxT=N4%(DN^b3scJTn`@1yUl? z*tGz|CWw3Sj(c{n{cjY`e7etmRhD#$NUEk8mZn-S#{Zw)9Z-2Jio*4co!R~veX=1U zRXNi#W*@cJxFDDZTbYuYo(9=8IClirF14~3f?^|MVs2dS&FEBv)1d(#kJ=v-E%&Wn z#Fx(h)%ePJw=`uHvC<-M_c0-xdWZHOig9&`FGd2D!b~5%MTIKG@g*)WRd}63ln@N* za`rX;8;T5bh>pE9)C_G`C>=nuN9?hI}1eRfuQ z{cXUNUmRjb*dzG&)%t0XV`@XgtN z7b#MvTqVYpGa;cxq##EyCG<X*~YeN!i0DBv=Ih(wi3kGOn51Dcz6)mA`l&M1#2p|w^{Uh{8^Jdm^r^46Lrzudwr&=UWp{nZ&{%*>OoNXf%< zj(b)Nf17{wvO!pEUCzDExBVZm3jJkBY8=MZIO%px`2XNb5K)oQ@)Dbq+zittJY-55 zx+SwU?r?OOYV%za;xRkNt@cHS%~StD5EP*gd6)zyLz+Z|~o3mo;_k(-8h@URfV$KHH{M!bRsQMM~F8JKeWKO*Uy*8;P?tv>fWAMZ?oE5s|Mt znA9z7Yx-8X0NnfERdkBSQau|FDKWmYCN+_boDUAizDBPM)04TW^x&Zy8#R+Z4I;O_2jeizVvTd}rp5z|A5g0oGi1^GBk zAqp_22Xq^6QWgqsZ`qkZHxsMY=a616$hYWdlX_iZ($m=RD`aLBpdau(WWdEyVE>f2 zzk5w*+7LZh9R$9KR?}*Jfa>>Y zpvD>P@U8lx*Q&K1LT8i)q9&C&;c*=QbP!BToJM&lIPi=M%tqNCt2s1%U2bY7Bz1@J z=mm>lkpzulhsNWJwhD^NVH^!o>81*0f27Rja<=T)xw;UUT$_R;{R=B~oiaQYv%$6i z8yb1Wl13>Z3xQ6hbnzbe#AZzDfQxT9ApvJOucdD6R%ZzXo;Ne7>cuqX$s~SwGQqjI zO&iW%8)urOtRzm#5;3cmv|eW-b>a6aC=dVlPG zZTrTlm|@fW*=Xxo54M+p#fAxU{OOI?m7iCx&a&j1Rmq)s-#mt zT}TurhA;k3_jwdk&QbQUBUhIG5Ru~}QtmE>yOaq>(Nf%KJ2*0{IQkA2M>wjn=eY z2m$tVDZtFlTdNMh>T|RlQTCEUOQApSc}CS%r1O5kN3)ozui9k+K1T9GU!mG6G9*-0 zzGk(K=3u!0D;SL`bRh@Fy{M;w)i|Rsjdc$>a%!ekO^p_JLV%_(LdxL%Os;5hHfYh~ z8LcZWshh@Ad#M-=`pN*A--|bu5dn9a64L9sD+23Uc_aQDI`tU4=*jMv@6A3>r&&R3 z*m#^_&xuIv(Lo3SVKih}_U$zzxNze|92Y@BaRaSu+&vMQ#;Qw4>~Pt9;Obf`P|N;w zq}dKp>+LC@dG`+4gjHKQ3TU`^zRuPp1_ZpQh79EqXxWs38-VD>SIs8o|lVw zA3(-!;IGJ?4-^1D)O&mH-+?{jrC&qzFZzUtLgm~i0-MfS(~-r0JfL-h7$vLu;#JYBn%Qqym<6ma#*{)-K|i`L+5@JTW@A`G2cF;r~*9vV-&oV0DB9I^1JkDX#z0 z&#{H2KV7g@#jmzZo(@nOjdn1Zr&-{xklv6vEb(+GS3Sz;A7hO6Nj;rg!qo`SOIb!e zFeakY%$N{%*;^;;{2!Xj0L|on_&uY_GP>OzliT~BUCml|6+v&=N+G7!t$?RvT2bk^;F`3A`1_&T6NpMYo zBE>Y!w8#Dhz8avh>*FRPh=(5JNy~cQjfrR^hbPlebDi}3(wCV+iQ9$cMuB5T0@?ey z5s>2zM2fwUQan35xH12^o}o=R4MImv{NF;}6rx|=x3~Al?HwzofhV~hTfU+NZ|ahJ zdAT9@#GDg>vvG`#KKIw*2de%!^K_YmoA9#2fj}8{pjpcjnGn&&Zw*o=m5Os`-_dKi zny9m<0{`O77AKsK(xC{c3&vxojE)UewWPwKMGw>cQ-(%BQg^ldKJe4XVHYVka5n09)3azc_G1hWodw;;!l_RVjE3-k zp(WxH|6QM7=>Nc0O16q_iRC!s7JMPI^MSe=q6YCxVe}?_KTlUq?L`MroBi!sCH=o;K?AwzLX`tU;5cQH}UD-Wux}1!fSStWhP*MG%R60 zF>cz%EoYZIZlHAy3B9?gS4CjZ4Twc)CK$ev>A_#)6T{Q`H3{fe{k35azCX@!!+_`{g*b@YhVbP-}(p5`hh2<=&}+-s%6!CjqW1}@4b0+Q;_GNEFjyqN706Wdi}T*y3Q9NcCL9Q#a2Bo$3E=;6|4l44_|MX z+w2H_@O0YmmsOp~bxK=Y#KsFV{WBFBCk(Xl>S)5{>lOdMe;(%d-4$n^Bz15TQBgGP zBh6$(TKc!DX)2R{%tO7#D|jDKgF{C?i7<*zTRcREu#jVo=zL;Fy@vSOig+iaCsK3< z1wzwl1ym-xad}%sCYx;fj~otMIkIu7=Z1~|+NKw7eUlH2otQCqY9fp}me>@$AvGgz zg1VK7Baog7@}IBDe*$94?EnAL{Cx!|UG5x>ItpMzY`3c}-fz2qxc^vv%vjw) zeJg5MA13)Lvl>6&@U#N0vuw42X--UvUphZW)4Ke{cQ2s2FenP-bxU=M*=`!1J{8ncnPuwo39U2{3JmxVV2u>NtK3vSz&L_N zxrMVS>Y}F=+C>@O&D3cSF5;&h3cb%&GXv;KU5|-;znN~qu?`u*9t2u&!wpQZ+em- zF}b?`uUJBc-tU#aI|gow^@N}&-w9zKc*)shtm{G``<$0vfUE<%8$B~vX-%whj2x@ zcX6?6(`#$RK@PexY-+4QL9RV)K4e`0>P}vLn9$gLfdiqvo&ZlrpMK zHn$pDkJTN5acI{Yonz)>YdzH!;}G@Yx&*M;tqH04r1At+SFp&0rDe0u6&omyJ9cm& zR;`_AQfj{12eg|;hkC~RM{Y--Fu4Ee%j@>`-IoI*qvz+<>AXUC%^9n2XAoId)OLR) zqF&AZ@#?l#M*Xh>?E>EHdw(s69$2}*g|>b;Bz8AO0&b?xg}0IlDkvJN$H4Xd|B_D_ zW6*9_)&gcBhL~6oTU~3(slv<3B6bh0iEQqaQ?Dyg8KKwHl%hcDzc3LEmxa2@(xR$} z#HoD$36CtMl4PRJp=9K?W*&m0^wm|?bjI--T49mMFX3$kHt-ihc$)wE z^a$j48;H{x3u0FaQKsH~j#R!9)Ra2~8-=Xlc-asml@nWzuP<7&^V`93;A&);xz!KK z;^ysDP-XvWV!XxoWuzPcZ7a?)InQ}q#5e)O0@ohmV^nM`<{3yy0hn4`VIaC?H_Zc9WNL2aDGijR_^9i!jII`M$5M>pP6KkrPV z12Z2mRoN6T(gnAh0Fb(pqiN_DE~`sD%af{h0IsFF-074#x8~`Ty+j_Z+C!jiKY9?; z6k-nAgro>JmNHy+kVe;gxJ!jznhECk{ZG3hubZdqUly=U9ed}J_Oc;rInj#P^>RJm z$#y(a(}1jQ&*ARTrw{yj&OjUKl_1{!orLhMfGqX&E9EAb>V+gklAJVmP9h6?II@VE zs7;w%^ghnHGJc#lPN$&eBSe~6kPn7zsfddOStTBy2caM1yL-cnbC} zV@>X$nnrZ47H3_DOxAVYoy1DC)!Q2lihZtnL|D9$h9#i#cz$E`_rfUA2~^JLmE6h1 zDM8LY7ozaG>{wZ{l*eeDud` zuWeIpdR^R_$oXJ#!}Zc&!&pn3Nc%>l26-fC+}NqJWGmd|aG zoul^?dv5aZui-9+B2o>_&4?VC9_%E%MZHn3enk05H3YrZjtZ>5T|ie_^3AD4yz4W- zDaBp>PZ6nqwWrbeO%O$o0GH$MIA8E*fg$`}U?Fn)<5W|-X3$FfKv)PXerBIL)9$@A zRs+y*e`i%cB;g(80e^pv;9R`$VyHnU$xsX&!x>4-FHE+Z7O7!+JCv&*P zBFp8iXkTvPb`0?)7RFVq(`XB?VWrrc8#%*mE4l(J2B%;O}krN?ISNv@dnhhSP z+=ez34^?ZL0h;O?CmpD!?zPp9u!cboRfjiXcQKE$?#viaOT3RZ?znZ%Y@b-a?PyZ0 z+C=`rlLra#ZQB$U==)YrfX`0s92_{t6P~oW|3IcGSdME`QPHh?6$svrKY(Q0glD;` zx$>Dj`p>l0skH_2rJ$_EBs7uNqBZBkUb5UDc*_9WUT6U zq;iqB#;s&TSZggqSX_qye13R*2zU_PZV3IF++fzvzVe2x*WFDv4?n86lR4eZx2I#> zil}pyAM{6(0`0#HLR<4U$SFH=^*n|>1|5RDJ~$R4AcW(x3bZNT@3107MZcs7e%MWI zdl9w;lLoSGhO2+v=e!m;fCh0B(*;d!#t`LAi#Kn4${9=_s_KkDC9}IW>M?acJk6VS zzH@{4hEoM63LN8pX?Dfyc$HR|GTyo=7i@h4_*@x-{^c4!!Y^#@=7?;#&}C!?6=uGJ86Bvc+(cXCpfzN;&amNo)DMB&OW0vGd+;rBbsC; zhNaTESY~}i&oODTh3%M5fD$xg-zEZn+G1Vmi6-w%7k&J->S64#HiHYIQh`>;Jjy{X$m^Axj^4lUkTIJ@ zKb00`M;x6oQi{o;Ep`f7CG6gHyUucv*EN|Uh3flC)heGqrZ21wd;?gqaCSlvA(>kc z^gi?qZyumJ+<(wLJ#U@^!GvS^Yr9do+2WW7?P5NTDEtyW{Ek%5_}30EpwoU@a}h2P z2V*rA@>7S}k=rfY)V=f`jHFAvU=#Nz1R@$X`2RuBWUcpiC;i_ z&D3vm+OhQo)1Wnzedh43nMM5w4x)D`?hx2w?P{fo<0?om<@S8_{F|E_Ya$x-;*+zb znF9=n7x=~d(3+-=id8PA%F0d2Efh~p76w|i&4%k#lr_q1zA($1r5xgf88 zo%FD(a{Pw`kr@{T_zZc=5)iPYW^QgJGd&b}VB&$Ac}FIpH6ujedzvtqI3=vWEK*{Z z%R3*uUE>%8vqbiEY>%pMFJFIM79*&xkk6qbai$gKVP$*fvU>E6;lkqV>_I zl1>CY#M2GcGfL6aTQ6wzVPsLp1q@fjJ@p7>6_MSTZkq;A!1#{=1+d7h_ctDEJ|PmS zu-a-Q3z{hh!&rx+e?gXiR29_=&`kGlN}h6*H%hC40prQ=yVTtM#%bXN4*_LGijU0U z8Su$#XheS-ch%(z)$#k%HtzeCKDs9sC@~<^ z_%Ke`ZZ`nc5iXB2)HjOTN}x z*fV3dCAXLqOlQy(5cCp8)n%sWsV;4J{e_*wthZ$E&KM&7RcR|SH6F^Okv<0C$Hb!+RuYV zcfOkfuMoH;tDI-wu94I$dhcyNQo<~(kr6m)od9wyj2i(b%Te(kBi3$FD&fm-72`=5 z4;+4daWly)sQHUpk{SgA_7&fj%d0*2>XRHUrqi=b51vdilVIIfeysN!EuHbxB|2>@m9uHr2YQ{BpDEk0^{wE8%q(J#opgZ>MgxaKifx-mGInw=lZS7?s?;^m1*~O zzfo4=&ofM_;c6M@;Ewj&YC!bT5~-f)#?~5*fLc{=Cmc!g+3U>CV61*r-4|fmVV9fk zTe6p-)|JD0 zSmZL#xWyoKNhbVKgg3qP@+{{=QH}K@Y0r~b*Iwrv1DBLY9IjGXyqOqmH|A|yFF04~ z+um_u)qm-^qkcELNAG67N7L&|Y-yz%*F^n)zel&{NsxtQ+{gqeM%J`y5c%$mq zRra{K+w(qedyFud(@m4&uhaHZNF#jGZ2L`o>=w5ZAhS+fxyh-H;`#<}U^&O>nStA} z##GRhbMQ`f+o4k_+mQr^X>fM7CYSoCqVLCw4Vm>XEuD}CtjO<9~sdc57+rlIYsGf`@!yvz8 z_wrPeiNx&vx!)p>%7+Ki(x--gQ0t7q6Ou}RkPq=sR{QGTa~R!&0uO+)$^wIDGO2E| zGs1siIr*hg1>FlG=s12r)-)GCR~H+{k?c${T{n4}Yq6wd7oFsZNPy-~7*wmVqel)} z@frk825ll~|Ba!(5=1wbcr1@x!WbhPw8$55ZBU_UsSaRM7NvbM6e0AG?jBy|5>mC- z)`KvQo)q8s0zJArVoho}KURGUbKruRjh?EqC79*jy1^*aZZy1KowH@7P6@5or#O4| z37_;-yBOjpZ4J+0_^rx0*x1S^T`J@)Bfrm0kls9FQFgc#rYdB?i{3!%6-jpER#TLz zPv3wdHJXVvNIEpMsfQK2+hkB2EZ{=xaW4`_h?aaz?H2h88~KC2X}CY4J!ix;HohK63LyQZ6XOP_ou_6U z^+qI}V=6lt$$5d5L2Cd0o-?w=BYT>)9d*4?2Tx!U(pTndaVjz>H93?7zrQ5f9k%d0 z$E6n%WmWKHSohUhx2g)T)>1N~eE zLuW6J#V!o`>=zvJ0&}L!WpgH44G;0hhc6LR76ndq>gmT5<_!s+gG9aOVzSP%)12r2qqS^aFs|giNIVmorl%J zX1TFYGlWiAYx4RgoEl!LvB8Q{Sd}6s{_z408RH$P)Dcam#xj#yWNC>A2VF`A9?87S zj=ka8rz6uaekVl5D`RwbDJuNR56u{WFQnYp<WXPj6K8l7=(yJc z`jhR2QOgqUNI*p&L?)Pm^7De#8AMF3duNGt@CCf-R#;Q3cct!38^NaX3X{3JLIJxj zokE>8J&9zxZJr$nXk~^?;*Ii704)7#%wEmj1Q}Y=`orUuC30|}Oq}sautFg4n|1+= zl^b{&$O5w>nL$<)MLgj2;Nd&@^HGzLDmAcalD=$yQAED929(i2!5ld!qjE#dEEy5@ zI2fBTPSbp0&t42HF#VVuF3Vf^LGcvnsb@-HnfN z3H4X-uhe%qO#P#J{_mzZZ2I#7<4_l8n);V_p*KL^c_8q4QyTj>p%H zr&T0el{;N4KtT%c(^9>t=$Jh-lsR3)QPH-*yV8nATVA5fFm@3Pi5-+uD9xi0R7WPPBvk#S;mgZ|Tdse_Mdza+=-I?>F4;d#^#DHMqS@zv*?d~R^IBM(Z}Ikm+75B@SFAScW%y~#Y@Cz0 z2SM(u7b|YYyvspwzfadLks7k4hQYkWyk=M-N+W961?k}}#ns^Nk~G&G!ATk8I=zBa zyeAk93|V%XKT^XJ=5Vf8q8>kN!}>?Q$^Hc?KmeuS0Hyd)m$2Z2+r;%TP!9KiqVKfH zqJ4-Yx9BG%8kQuE)Dwb}N;Lh_cwfqe^P}RY-dK4gtuUtOC-*mL=nK$cJ*V~KU|YEt z%uHYgMOX%P^MI62$GlNshJiCCq}M+T)Yh!AvRIqqTmnZ23-9sI_@JkX%fd)a?;XUX z$9@twRy)f(f-2Yp{vB`G@%^&iJX7!eWR#FsEahyoZ1PxmO zzIWTgQZ8y1sNvD`Lv9FGHtO}-YMc?ab}%oiI{yr4A06~9DukvpWoK^%yYXMY-5?l5 z3$GI{HqD?#9?K6!mSWL94%s}&$8%+_AvT#h=bDHOamCL}TwC2+n*>?l8#{(pgvV zBf&5t(NYMxzx2c@4%VOnE64zpy%gvp7`6j`*|2AZ_!R8t3`0cn612YF&$ifmou&GV zHR;55c~~Tn=Y~FYO3-Z4JvnYUc>pNR!i*#a>EM&|rc=>(^xID-Aj~Z&9yvoJ;nh6ykUs@Etvp$+*r{4E+)|Go`f<^9hul^Dk+W zez&HjC-4mJ0^^tu0i9LTzrNEHV>upTI3s$|FuS8u;ZDM%bEw45f%=(o4>ei$bR0vM zV45f_mZ1q_4WIaFD@41wq$0YYf0(R2aoWDiHdW;P*4b%-<1Lx{I$rVc)4k-6b9*HbO*=T(Q4 zs87L~;K!#>H4H2P--*v_=wtj^-9%O3Eb?=2ncNDJDi-_Efw3RLQz2wZcbqDlCzo?3 zBdjOrP$ElOIl+EO`QKcC92D{3N~{7|QbY}>Fcwo#5B?CicWGt6Ih9I&b(4p5C4c-8 zkxuw0+30WW$ftd48j~n@(h5o>L%W><(-loZid5{&p`;P&oc|Xi?-*U#)~yT2w(V4G z+eRf7qhj0EitSWv+qSKWRaq6==2v^ad!Kvnx$SHB{90|!Ip!Q=jq&u}dVe~Gb`N}( zmx&z@wzn=6*_~{;ZX_Az(QzA3NY8Sxj>=JXs8i@l(gfyjt*TL_e6lLv^VVkIZm)!c9Doxb{O)203fBV7e$ z{N}RFQWoisY02#)4TW=(l!!jcKUYgjzXzRA*n{x5dDtDYCJ?R0sl5xdsh>&a8U>D6JLzG$ z;jsP6M^KQftl;g}->S{@+MYHoI%!kFJixUH>;rZocT4DHyOW;1=qS&(J~yn!Jziap zt4!ddEmx*A7^lQb!SNHk`>|KR|9F3xA&EZV`m|l=xxLl8Rp-q8- zy17Pb+oseDc8ougCTV(agt&>ou#Pq<6^XU6nL|% zrry$gVu_f2pWq>q2FvwCZH6M1Wi{3}=Zgl4NM}Ij*lB>qi;#{y#`sJtRWD&=2WPcY`I{PD--*w@;O+_GgUUu!U!Eh>b zt(uRy+GGW9)8}~umg7E8y2i+ParDdV*Qd3{GtE}an1hrbQG!eP>G1rBisYm$Fiifh z@4evF$OVPtdKk)@sdA&4?&tq`nGDw4VB2YDAZ8k%M+cG(_gi3 zTz=YKy>$AX66LmtFplsBlb^Z3e=i>7E$-?^pfDrd>yjdR9k;~KL%}@^DEsc%zPm!L z3p<%0f9I6pREU#NzWFqHs0xhsyBKyG@CPqXa@1m!LTgnVxk>%-hc;FxAZK`;+AR)L zmJKtuR`;H)h1>1v_gd*iK7t%3jk9`4IGXK48mdlfb` z)_lB5ILd0`wzfc43VqG0qu`uNE*MItRT%v3dNiOSI}H$>8`zsUGeo=O{t@;yGiGL& zAJHySr0>Trq@QoVdzSNKoViig)J!x-69338wF2Fy7PmopESG;;kw=rpll7-VbeV1Y za+I;#jvTl4l(f|!llm`G%ApBH7qmW49SlmRz(4@eTZpwZ0zi=`aBa8U)uWv|@+AN$?AoFmOCVKui>3lesSg3BTHjZ%IDM)C zQF-|b)uudELZH7MS&O#=(6z!W@+5pB#+UThk>up~2*VmKwC(D-p6o_SnZI16c0 zRmu(25-sE6YVqDjMZ7j@TO9b~X2R`KCtF0)+~MDiY4cAEs-KfpL_c*CBtzced*_UD zPSfgY$hv`KX0GuMvPj@cSkJP|^+-Hs<+Gg)4lLTV47^BQk8UrB>ia6i;2i>~KkU>p zA_SHS=NzZm4G4<*_Ehn}=wh)3#ImoUIRz28KVV1cL-u{}+Xwxsm#V`Y;{(i?dHn-R z^GAz-?8}9rCFX#B_NNGz$^Dxnn2N(ZCq9^>p3G)$IK>7WpuAGUq=Tx+SIj73cq~#{ zty*3Pu~WD%d`XtzG_meP~=&%ks^^24+Kd|cS|-$bwrS9FmPWoZO~3Mbu$hXWVm!bp-CDL zP7>F3LV1%!%3dQ(iy*Z3K~_X>z;32{WM$H|`q}qNyV=CiH%$ThG#@w!MqyavfoG%& zgG2Pn%u#x=7r$j!YfSHbkeg3^6HfV75PY< z*=V8IU?Axawpc=oI0NC3O2wQ@JXsVXd{?u=Rsn{|ZS;z#Zd6jz#)MTO3t4tslP)mj zA<#aY*?_*=s=Fc>zR zW+i4G`-G?nZG#ht<4XJ=kOk3M;lhqxw%n3iII?_}H`?>nlM|}%-B|by;CLF>jK_Lu zAEk9FsNC@_b+hUUS5`?Ja2R9k=pnhT2^F#N-Cdt{S2SkO~*z%MVL6u-?_tAx&J^tW_q`|W(Kazbn# z5i>=*5teUv7mSBn-F``Do4~lG$?NlLv=4kg=qvWWLXUH_dYIV>ObR`x7;544nPO6g zg!srfWS|^m2x8|pUo|&kSOtr?$&9NWMh9#rd5Rle9cp-&@L(ewpOo*uIsMTWH!lvl z%wOK?N|m}lSH5PX+z@Nou4>3;S=p6s)dlH!RE#s{zFB!3$P!{~kkj~fx+Nsn0lfx) z63B4h=myv;h^2_DK|u>w73lhYOgm&L0OZJo(u^l1P-&djbcj5kUfd|%kI^w3iLNQg zwpy*QZ%1A)=O!l7fNj4+P@5CbzcGPM;&iAq(g@78SGo zBIoia*(}TP8M)AJAlO~o1TOhCqm2|)a)uf<4Vy6YdQ@V^)tN25O~JbfYz7k|{{;3Q z!~2K0>~`AIx^i3`Fhe>By(T$OfXoknqXZZmxYT?Ae-2xdF>3zg6)@0+Baw;a#8xilS%)a{8ZJPk_UVdxX?sa6Q z@9fw|C~MKkx4obDIxb>D&L8p#v)1-)bYrS%hX5t;SEuGab3R{v+^ZZ9@KT!A?Lknf z6S-M3ZmadryY(13Y$3SO?yQY~_sSsxDE$FHGL2Ik z+J**UDkUWX>nJ?1G`!1A)tPjCu;eq&B=Nm+%VsFN+tKcvI{J!KGB$4wG%zF>5*EjA z3|GcYB($8mqgd!T*=1s@Uk2UvtM^%3{fRv#Z~)SG=Ck>f%Y5(ElON#wCCgF_4%W zRA$Zm!4Y$jw1*M>dG_j$eQ&Hgw!-Q1I-h6W!iul#$bz1W5VzwGo85-nCx773f*D|2 z&h(OxdRFt7JfbvgRAC&G4v7JI0E{OS$v(_ShsE zWw}=G37YyGs(RLlF#RSo7TjWfW_g?Y>>fFT?%`*c8DfV9cH?BoIU3YktXK;dfsjzi zht?gDd{H3Up@xyj#v(mH6fdq%_l{f>7@d@Hil8&?R0^j$rPnPGoSk`$%zq4(W14QDWv^~smcY-4$iHam#Sec*y?1?_ z_j~#E`&UO?nbo*~BP8#oa96I4$>E1I`GrkmOHUvaR>5Mt6y+F@7~x6BDo6Iv^eN^6 zymS{~-3-%fQr-Cn1Kv1jsI;-{bsZg=XE@dP9Anm`m`-WlJ6N2S1klUoguFbwFr@An zWIX8i5C)Yf2_nwu@5P$k7pF>V*oq^VzJ)}S?_qU;l;Do2h;Z6k7*r!0))^}1K7|lh zl=7Bd`#x(8t+^l|Y>buP@gr?kU>5@nQ@ZljaWg_scow|B{z~r|NT{=&gd0fcU|M`d z;(}0D8QV^5NMsMbig&ofZ;>r~5|vD7U`?aF?&CS-sJ5#-_qJBaEMaSZ(ZLa+wbN3w z&~gyv7nDWbNG1*k-@pgKg+u5TW@275_?_m;D|_lz%|~D}k{f}4wfOX4K90UvOuP`{hQc?DghI1uja;p)_qLI2+KM<}s~;;J#2cl)WNy&al| zZmg7d!DOY#9QG|b&6uux>i7tMnB7YIanq37cZGqlIS{L(@T)AA2%w8=mhpi4wD$4a z{-k5@h3u}#Ubu`0cp|-DcNtDGB*jIw^cz|nPZ1u!W$yArZi8?vLV)48@HxSQRIiJQh9?i-$5(q=o0npXse|UqD1)NP3;zFq(QM)gDDL*Kegz%fZuS z$rvSb)Za~?&4$p({$A7fzK3;h#{o6KHv-igCpJa!wqW7ND<~d}#W9{kkvI(?uzk|3 zkFajKN0t2?@^vVIDs9{UZL)4@m>l{!a^r0+y5`*jXLQ~W%CuAYV}@(X36rPsQj!M2 z_9H~Yviotc)BXWhu(4Oapy8q0++&d0q+}1FOFXNBgNxB9wh+~sz?5f?#p5F+7dbA) zOz0X)WXkw~z%7>;MVs2ckYz_YLLcW`0Ffge2jS+|ufVl&7zgX7Dw8+S=;iFOi345=y`0`n11K~W3B0LN z;(eT}hE|79F5FW^U^ZrgwqyoyrR-mp03uN#f%ayT)ptkQXe`T)OdlpNDpqhLgf0=Kxo9Rq5 zESOiy+p|GZk1|Q<6ZmZwA|XO2)Q}$_nPl*z(ZiM#JxI}DJlanE;^flf9)N8jn3s|T z#>d7ueMbLd&no-tW-Z_MSNqmw@WZ9oTQVP5DkQ@Klz-TxGZ+9J3zn*dB3r}PVAy)n zLNH@wx&tDJh_dD>i%&X9Wd4n?A1h-aV|{`UBzb)SLP{$2OD#NRf`2S8$(&P6P#S4P zQ1}3F6@O+Z1RpL3!pOir6dW{RSRtGy{ymNk%y?M8tbQxB3xzegP^7zw?|c^bDhQ*J zd$4IAh`+aoz+i3McoDyvOjU%b^X$MQ&bu3RHWhktGnloubDzARC|(@45|=uAgl`+9 zqYi(e36wg+xCBfKY#NfVH<2lTp$O~NN2@A%A{nKH{HQ=9FUzPQN76*t&D(*sE>T$8 zQl8Wf-ditjFQ2)cz2wDKq5(T%-s~R;=P-P!z*L)V!aJxVEOC4b&W17|5%>_XVEZ7302Ec?cS z50YAWo*ka^pt*6NO1M$MUP_Ub#$-6{;+2El;h*j{#KkKJK%EX|X=)fC!_v|hNf=@zjyoLAk%LWyI z<=Gc&!Yqym=i#s1e6PfYF#a*p^~Kq}ggchL^n}?eD!qNw%+3QNjU1edbv+^0>CpA@ zM6J%JJlRtpQ&kCGR24HHKOlU~w-tXl!jOAVSakOG94c=s&_Stib4M<1{!+SE@m^VQ zYDiPO-X_v0pD7qjU$;Q2#L(pUQf|6ty0S(GMN5y-gc2J^0(fSj3qsetrU#}t891_N z>%y1Cd*{SC+90c_RE;o=@VgG1ThfKM6wVW}Byp?nf=fFnJ`+cQPUFBdlNhj#Ve_-$ zz(~e!qXT`yu0m{Q>3A~6oP6MH$jCp6h?a% zL&H}*4n-et*g5S%SL8RHMb`2P@h5j1loIQ(Y0UO~%P#Obq z&be@X&cfWjxH`5{Q>A9e1VHxggu|C2+mca)hc3NRJk=^;;A>B3ad5X4P@~WRrQZB) zoz+_=ZG$}QE4JOs@Tb-!O>1o36=bTLb4esU5fnRovdb z+-QIH1-mW|@0lv-0e;@@heQ?Z!bNK}lm1&}prNk+Z@2C~T@G83&FzNoe+H^?oNm3w zf1}-Uwubd6ra4^dkF#>LiK|_UiA9cm5|o%f8GUX&WPk4o+pe~ph;g_||6%8S&Q4;wOB+XWa_j-$IXM8F0@%vh56%z@{p3h~`+v zEsrfi&tf0q$3skUf7Go(r{dhU8VihL)mwY*B{*^YAeeGxpAxlplHUFqyrERcPjB>3 zIJp&U>q5*JNcDM->$A~lv?2B2pnmQz%M~7~I?H9j-=5IW29`T1T%826Y6438NUIau zRdADO4kUmq`k*}886n0*o@zu}Nz-ACkljVPxijIYvzFy8dtr1KfIt33*dp7bAcbA~ za^+u9Uq~M%`cwqlY!j+B%xDVA(1JdsdB=6|QUM6Mrg^HC zDk+PQ{{5$U^fz~@cJA0e_?zAp!Lf=MYebu6H2}b2b^cAZkZ9UPrHHKMatE%~hxFKRG}Y|P5TH_EuYlrr3t zYy^Y1!nh995zm4l0j5r(Uh6zZaUUZ;iaqZliL%HvhoRj@37aaGWSi6LuDPF<7;@7N zrt<<0NZQbtNS~xAHT%(z4O^*|5<#}e;Emm-eI8y>H()iDuaHBP9QvX(0cMbt4QpXG z!O#|`egntgQ^W<~3W7HKVwF1gj?lW*ww#@MX-;>57v_kj6>O!on=V*lJTIrK8)U^d zb}X$X5Gm-fzq0og_%Fo{1L+J?M#5fnR;4@vtW8__gim0TtX)cDe5p{q*6t!(JIw(qpr!l&xF3lYl#h#Lc#uE0l$+meZq z-W}zuiYNMzH38EU#U0X65#kB8_Q=LgdTDzv9ypO5T&ZIuW(*wgm~84a-V( z3G-%ug|&%L5%7Am%YCV1K6s| zlgu%9ty@@W%+gtZt(2Vhx!cKAwK}Ja&Yg?9o#V>r&PDf1W+LCVY|;Cm$P#+rsO6e| z$WpMt;2MbAk;KU%zsoq++kXbmz5=T5eqGwq=*iMV-s+=vk0{^Ct+2Uu$$Kc8?V;^ zs24o%MBi&aUS|Efza39Ixk*J{NQWsj_>RpI-H%O{KIvTglRs_q6*_3m9zfMTD4@Tg z2h5*OweiGq=(IJ0{PO*JVBYxxV=&jA6v@ z=*^5NO@gHeRfm(y6)KLSu&dlX1DH3eJl2r6(&A*jvC>@`X=G%v#vB7mRh-t z5peiO*z;^1x89{&?>aa8M_WfZ*mEL_x{ivWayG3PCQKIrs(PB`$Lb8W0GThr+nSRw z?yhhetF@yV#LLvvVk3C;I$`Htaix`*etk^o<03JUwkM_rkRVijP@~Z?oN0UDk0oa5 z(7+1v2Wv4ctt3_$e{?!YyR9W7=*yV%d=QGFm<|zd4if~%V9AZ> z?2M8v-?#xxCt*UtzKfnLwnR=z8#A-QM*aFbFz>@qMsl>(T?Gp~;~Tf+et7U8+ZajEK=kgcZ3(5l-yN* zk7YEG$DWy6(k6vW5Hx9Tai3rg-5&80y#+&*mGRZBWAlF#$t?o>Z3 z79yIQT_6x^Hz>95LmF)(+C+wyZDheX`dX|`BXoNBz*chb-|6pu7}l+{775-F_<#WF zC}K3gzcYzqImdAF2~7yskQ#w1?Bg2b4SvCh)+aH4VG|7<@0WJ(OXGOonQC2~>HeDz zLI^CFfGdwzhM4yXfZ|9!o7(uT;R6dFT+@S@e{#=v517(=NKXcl@3+y$;K%fsC7V@d zJlA?3(W_lsquQ8!6Y zEZYEvg~N=>Fe3HZ8lxvTn72?*div0%Jn#C=B!xYFQbNHLn8cetg!svO0gL+u`FC_7 zjZhVs`jw+Qv)M53Xm_Rd*fFKT3M<+-nvL@GcYywRGfFLz;-S|3PjE-gt3O5%SHBZxl~_wh$Ng#q)oA;h#BAzP}ie~C^35is&9 zYuVHp<}GhGP7lF)1*2C8Nni^Su0>hsI$A{C0}_xHib!Z_LdOqGS0zBrJE9S;yOIn= zun+81dsc75LI&J@oi(gKN!vhyI8Uuh+DucJ5NV;sJAZ7e@4KRVsQ=+^K|~B-!P-Ws zWxx1;^pDEDo*AOyc#`qK*P6{hrgF6Tj*<^Sf6bAi9)DaFjTf@rV6^2Z>nw5+$B&J*qyOQiwJmnp zd91CjHDZ9`dDr4n3E&&bw;jh zJL{kzm%c&?f8wi{+thxACer)L_5$h) zF0E8?8_IvTVJ!TZ2y%efXn*ql%*Rb{xzqjpiL5YKx~(@sH|e5@cu*!a;d>?Ec5hv_@FdOcH?0uDd3LU0E~va55_o2hS`DW^V1;<--^};Wn)8y`Fz+=w-FnKi z)OKJ_N#()h%3#!%{QkXG`T0Mr*XgiTeq4AqR?EdlI41O4wXF^p+9BxL&P=R1i8EgG zKK|!fX!HakCAF*V4AJ{^K^T#H80c#UrypL5%@)Ub{k_@|>gMc=v zRoo`jN29^awtGkAQ(FYuw%b%>N)D|f8SvPq|_Suu>vuC9|S2?OcJCdc+LufAA?Pvca{YK5xuay zFv#(QQj8G}!@>@E-K<#;c8wOEY{Evz5$lx1jN}J=>W(FzaEKV9;IOP%Ugo@Ln9PAg>oaW|iEEi{OWzQ&F(5D(?Eqf{9d znCmA8&CzDTx`P-5A}pLCQ}nxt*gAn!WQaCv4Fl@Y8y)7>i}QzIFVMi^dkHst4{a=N zE$SQD%|VzEkAk@PA#(EXAYa`h)iEH}3%*xwZ=N>u3Z8iw`(I6hol$j)m7+VfQ|Fp_ z$e^o+vzU?Cxou#tcP1pTr|cdD;MGfr@f{KE6&b=A@~1GI4l;Ua+fYXYpac3427?|0 zpo>q8LL)~m#3uuH-3CFh-6SO!F+!agR`3=xY#WyVbx|x_!>6#!=s=8lRPJyTl2cxU zG2mM00fj}rajcW9uq0zC4rCe+1hf1o^ALkIQ`OQMDKe4))-iL4<|=u=4mzD{q{z2W z^;<}R%*LX8LkD@$&-uWF-a6bxgFx^r2YylWTTxYdwN_k5UetnRn_+GlYr4O16 zV{xR3Ky{bwm`S6*1yL>9XwcXeii}{|m-GDm5JG+{;K}gc%H0M2+-+`c%${JHkmaWx zaKoOzqb1jBxrX8jz<9}P!>Js@t>cvpU*asu8nO)t7$cXEDfRGq@!>P}qEo103LR+G ziTdjaetla+fl}f%M}A#GTHJ40C&V@nIcOyhLd(?9F(Wy z06yPRG0$Oor+kgzV=e9-3L!q6#(}!@|NH<@YuDlfjH^|9rk2urz_capy7?r|b<(jNT z1q1brffno3S#s4cXKo*Lw8i80qrDIAZwB^9(%jYp5G+5l$8JYQDkVyhYfd$$q`pWe z(EV5GoB>%te_-TU@85N1G-9yT-Y95UJA~y~k7LF{?R_kAMKnW-t@@oN83&_6k4Cn& zL3$pv4>$U)OPz2z6nl;KeiZwjalbf_Xo;A_$)%uwKQCC%uF&q!`WP!&4qX>qQ3hiz zuXR226}DPuXx@M`MjY9kqz0IL4)tD&oK&kFBFLbg>M|`GVg#ofCM!{e^j>s1j(G1K zAV+VaP$t+o!#meU1mD{@YU~||FNR^hR6nEpX&I37l$Mh@9B%svoK2nZ7Y8HJad5^m zC`+C--kRX7{vNQyekxwe8UBsq1P~>7@qC|b{a}28?S8ajNuk@k88ZgbOl7Bf+aJCY`;tT4 zeWZ^=>9uk6L8$-on`aLiPcGgN)qhG>W7p$udcdx+45-UzKu|sZEbtCdW0zLbcy=s~Too zQOuk&+iX4hzLjE@rOxgkW$xdqxT@~;{MM_cMsG7yI>WO$w|NcZW}DO-3e5M3?tG!E z`COIopg&@T!0SUG+_S`3J4K3L=KI=XS8siedomUAC82&G&O;mhK3e&8tyiyWnCp*p zk0vJm#`lrN5w1?{bPRaxf_=EFMPn#2|G4@o(Wv`A=+_w=$oK+fWtQvNrOJHAUS$=v zl04&Iwp#NRXE7DB4nX2=JAc^PitgIDZ_1{5xY|j7_E03M-9hGd?+-TX)IBp?3G0T~ zcBhUij+e;BDV6L-Pf0rKYqsKGE|(bI0aZHl_^88b_#;wZU#3>=s#EVY>a&BJ^T?3@ zF(vyI^A1AVufL{ z6vF{kI$;h*U_pP&Q#R;w0syL&s{O^n-KQ~Pv9A_$H!Kt&oiP0mSV_~C%%-W)$K++c zCCc4qo`V9Nx}k$?Yz+yzKl&LiYL^Y^<3;-dMWu=Pz+}lHm<+SvMz+zFiJ9->^aN0) zcg)CanA6V5OhgP@E|TKmnOTruq0JTtFBNbJqpRS27} zw^oCGr%IRmnIev{`g8sFT|64TC8Z z^#v;FpZ%W6P;?d^JuAMILr5$COp@ zaQV3_7+E~%$9hFa$rtZQnQ8tb*^;|QSjbo_MjcxBs^9Z>e&ru5WaNxx`ex$Faz#og zKEPQ*b*(ax;nRPE)*m1%r@Isx-n#WEUu*VUa=r!I=@TMk208DRwAQqL?DEaoHez9> zjajwyh8ptVW&q`jNhNO?E^-YJBmbKQMX(*d9>;>;0A4U^KSGT&_+j`%vHq6nGvgQM z61$}cw%D}yh*djH@AHS-U0GfSqI*vD>Q=2EG5W=#gO-o;5K@(i_;ShFVY%@XugnLJvlW#rQegvr)t z6aY}kx#`j!@;f|s4-y7jNJK37(v00jkK6>#_o`97arg~Y*gA;|iAE5u?69PxJ+JPZ zEOH|R@nf*LU0E}vhIfYp%xO~u>sFn$^rT}|=HI5nhSjEzSsNC)aDd#DW0 zktt#Pd>xj(>U-e`V08wh^vM6K((DV2ynG~=r*-P=!+%nlklqD9Kf+|a&Q$o?RfEfA zx6i{qnk&_A*|v}RpV8YcWUK<1NNH?O@c1udcy!DvdoM*DPH=BbOJN9%EH6}N3o$rN z@hOAr^YSF#uA=nXqt%1ycrB%gG#DlQIvUrxE`x|9jht7n2N6mDg>RRFiAy|ci*GujP?C;pbeyt_tT#`?fawoZ4eY({M+#dg=vy=A`Xx_d$X&iSkGddwNawI~<>RzgO`!YR zWZK$uith2c@;O)lzo^scKX)@FSH}2xM+W#PW$Q-RPkkug=8AucG943lyhhiA+C)5u zHnU?tp{dh)6bVT#&mo4k_bsFc`hBbrfkThMPht$e%QrWWogBJ#zu&w`X7T8}zF%gs zJ>2QobuyNgw{8!n&>hLm7F-al86*(+z(y4jxgf)bHCwNUXxqHJ#%e#7j1xWI7P{FE zv|TQ3_hb*?f``nz?(Qo%#E3FgP=3gE9Qn@R4zj+M@#F2tC<8{7#qGM zqu^o|ZH%cl_Of_5tD!T_6QtnfL|%RPQzgOVq%hyF#Vp|d&X#FlqoOtZwDU)>2#$66%5rC9}n_AHSC09yz=f_y?Spa`roZ0v~s?>EeF{*;jo;RKxFzA(+OIa!Nlsvr0`POG}I3y<%cWED#?hq z4lJ;U7Tme_^lKLeSi|T@RB)NR;Yd|6*Kj)=R&Vt?s)a;yYcFIDg}pYBV%-OU+B16Y zz>KD<^@1Oeo;-DSubcwZk9q2&RXw-@WS#T-w_$q=*SbbS$XB32)UM$)uxG?=HOZbr zynz|^nsUwnCem`?wchAG9O+dxm&1;qnPYmvyWbc3gf+h}Gedu{>I71NclyZNoIkbh z*7s6Ha`-cHsT5ZU#82R=F*FzlV6nK-Vme&`TJC160d;#B57>cL9Bt&i7wDbWDy*(n z>+D0-pN1<;iru(eZx3LF(^BV0yoFno(dbz3MV>O3vTP|h1>tE znT+x(T^q=H?vaei@HBonHA8#oW))9x`Mvc%JBDlABx?os2MF_@VDjf5DLwZxmLcLMGC(4*K;-u*juqxBGI|{OL3TYi_3fGmK9L#l%eB5l{u~Ncv|WCo|1J)QiWvSma11Li^@Vf@ds<_l zsWrKd8^Sazy?A{@=<@8d(na&fSd*PW8zQMyx=T)w#km7fo1&+@oF>$1!`VkDQf2fwmNh1dR6U`)`EHdVyhgar`Slv5|&&+a^NA8TSLvLE{In=u{Cz|V(UhqH&P;aZJM zXDqnP$zLBaE~xK6FGcT;KX0ibB~-27hV2Ke-Uf&Z)=vog6&B=xUsd`Ax!kuaj1fJ@ zJH)T&a-Xapshocnde8!!VgPLO&k=oI40PqQx?7>|?0dpOJQgl}hxiy*flwKJCeJn| zGIT-ikFO@YHfm9L%M%}<`U37i2Yr*~}Vs$2w(KTLBbU{2mqe!aJTw6@}S z&OU{GYBHtz@1oD9oJPgbS*)|4h?F|>)TI#Edv07 zVb`fBt>@L=A<5v&a6M=R8^Hp3GTWmOYMIp5&Kr-?3rt)bPuV@-JvR!rD#&3=*dNfg zUV7RucRw7>ddeS4+aDY^!kd>@G&78b*AMVpJr23;j%_x$%S|Mf)ydmfWV1d3Iouo^ z%px64*Zcg;`!q>jvpQ2OaSdpqthS*HEhPGhtPMkOJ@1)Ql90PP4Knp;e&5DcudLoO zDe-nQ(G{I9yEtC{0`kPP9wI%pZP0bw-_Iu|=3|ZHh{hdldij|>F&uk<{5pWGPe1Q} zJy_VOLOyd+Y|JVJUG<*SY-A@$Q0mi1fS^AxVUtAsZ)~PP0(hdL@hq1hx3hMR7G^yd1!$S&l_H$l_#t0P#%7}9|p7u7~$@p06$cCQ}i&|go zzMct(F`o#VB>CC2ucCcYzFerUCyHZj{SwilN8{^(eg^C030~f2hu_}3sOFwNkN@-I z$cX(Z@S;m`&mB!bxnJuXQw&c324zstqoL#7>zXgC$t#X_O-ZjBy{U^1AO!XncCc3< zCA^i=zc9wB<4&ihq}~jI zOLTP+=OFzHHEo5PuYJgp9F)fUO*f|U*xHU#J6qW z>csQ5k3_m=yRp#zXg>8Uz!^>5=-LQOUHQ&eMed(%KJtMUhdGjPt!Q=Oa^g%ik}E;P(og5A3T~ z68wL08fQeH(d1=YbYc`}|C0Ry|D^Dkp}-g`p7Ff+`<>_Fyzd+@*e5@|)n@IIk}aC+ z=>iKp5B87fdV(dsVgFKTJo*)O<8_?S`rV$|MXzk5`NUo55scflrL-`e(I<1WOCF`K zR+*0S3=8rbm3tIbg5*D?-TyUXn>+ogyQ_zFCJ!NQ)u$bIdA;=?lL`1w0y6 zYb-YC8(y<2SXctvH!7As^4kCFFl#6vbVol%dtVvRx%r|$A)d}glh@-QoAKD4Ae!-r zZ|MFRnWYP1goP0l&lr<($I!8I_T(0sPdYW;K)tbB+WIEP5}mQYDj~+?Ik72xVn>tG z_18I8i5dh+Gsq1A7;UjJ#oP44}d|)Wb1;!wM+bWG?2g$;Db5ynU9vh%B218=kPa^-cAlAooLEDpu^6o zQn&Iq;R3#uLrU%JiJ@?FLP%%xa~{bI{P!aS7>frC1@cN>$^88HGizih9u{*Coj%># zC9ginn|hRuXuLbtcPHnuhe7ayB;*7mR99Vg3pR~qc+rH{l!@0+W|ik09TtsB9I&u^ ziGQKi21Rp@yxjr9G~KLSmvj2d(>D2A>0}A}*VqC;+<$UCvFCg{QHRlPSMV49tc_=1 znw|8&|5+3W6f%11Z}2exs#H;fRS_osC6lzTekrCFaJuITo*O|=7h~4~_H$<*Fo;N0 z=G|_)KZoO35pF&4c6z2tP@i(_xkzLYe+x{BiuIsF)bK-K-AUJz3(nd#`)W?CW zh9YYVir(*Ttc8e`*nzhp{@HgDn63*{Zcu@rr#%QY;vjz@B)SpJUZkiO!4bF@@qZoQ zGuae7LDWb@L1Hd#DZz*8|1tQ#UE0Kg7>d}&;(7-C4+j4$ z@-h;@;GsY=`m!W#k#N2L@mwenj5-i7@Fxunyan9XoaBv@N1f)c4p0w{jt!@C5aG;TrMj2*lSyQa+ z5~L8HUvf!=|Hj^tM~{#&OwZ+;*nD*2G#di>TzHPGpgirBz~46I3E3xe;xbXjL zZ%IjnWAl(eRml?nxs8Mj$`UfOmAlY{{L5ET#gkIlg_U8u1iJP?{+2FI$aZ;SmVEK+ zM@OOc--+SI86{gh8lrBFrK^F=y42hwqi40X{^La`mqQ^k#!7-!t}D!5A##VNhYeODvs2EV3hZ#2vvW z@j7upV3N7v6fq!#DB%u76`6b76m+;5gXzenj(QXb&s)n{bmLhtVjN7@tYS|{*4 z3xZ^YwVLjk14WUr0M2+|CY|%fPZSgSw@RGfyQ@vsmh%zEM(KW&JXF8MFnae#oCI{&MWJ_YQ5Lq|3_^DwGOO5UaGG_meWu;sGQ zEtwU-cmn-N=16ys;lexhq~!T}wp-I0c|{FNf1raV`|NO$zT+{!t3<#_r$_qne1#Dd z+nJ5xtJ_DcBHtTc2-EK)C+{?x1YXayBck3jae=H!1Hx$cG73`s-INVcG zD#;<#rf%*0_0sZ74)goXpif5x^opexAN90&q>i4^p-~oNlXXG~E(a4E!Zf&qSw%jv zQ-r~xktZ?czI`^1^eQij5A5#1>bQQ{AYjWm>BmPtd5>i6uTW_v;D37Qw=LUd*kY*# z{n4nxW>Z3|GRs)-GJo~b#}Y!{7RVqnOM%*J-4!;7Bp<5~S=qP^quq7EhG5XC+CJfR zv$%&F!Q-ekrO-IX)ZE*{8IiPD>T*={riZO`&Xzx&Ly`WHcy?~sQfFtwGX^xRtrW-oad z_9uk@5YO_IjaFrx+TruiS3ky)9B`Lz3di^VkEwHD&urVab?oGeZQHIm6(<$jwr$(C zS+Q-K72CFbSM7b)IrkUL=b2-)(R&}Q&3D2dlip+THU3zS;k<|GOKXUHB#$~}sk2Ru zQjvg9c+~e0qw%oFUNlg^Z6p6*Nn9bWz{6Tttuqs#4TvO}rytS8@|tH%nIf)fmb@jQ zEm~Ot)PxAECc^0i#Sd+IzFUZzM|e$NQFEPg!5~e&k0~$1j_-u30tJvY{{|*;baKPq zYV)V12)5A@939MDdlz*;U=L%pyr!-2HCoeNolw-ZON}q{SW&2K+{3dY?c0fCw^F9f zpS@o;GfK87-*%7;?zk?H`=*EP_^8JRxtZc>odsTy$-}Pq#!vfBX2mt$t{JK`@BsP3 z2HyYk6(~7uZt2L6_uo7>06j3^FF_j&E=^hzefjEeMFc3*T>gtUo=p5#pJKw=_82_T zE^L=sSlLyPea&N=>eJPZ>8*Ix@n?Hq9ljjZ zF51oOqw6k%>9Uq$=Ton2%O7TJsc!Xr(${7pvDFP4S&0%h|E|Mf%wN-lT0b?O>V#}y zmTf8xeiA`UE^25NzVN4_bpCeUaRE{kE!&??1#0tl8_Z!Zx7S{k3x8nrZBZtvxh||F zsGOki)FcXvn%crp?MJ1G2#JApd3TiI5Iq|sR#z( zQswNlH^0R2xsD&A%+147#eKA^e$*x>7EYsQ4_rxtSAJYsc1&uQ24cnGy69?c;-vfu zpu3)}3eA|0EEyJ)xPo{Z;XB8~%@c}udi-Wd=nwMpDgz=SH6ybS4=r$zpLDq=wyycY zgD68M6)52K4HIGDi}sLJ7EKD)`19@yAlCjE>(*(C{14iH8suRxdqM|0BNPIgxlFh) z?<7f()k^9i7=ilVdc(y=_Od~Oy|^5}0{pOFZe{@L)#tv*mGM^b;igWaK7CB4S~Uf) zn08VoM~3I69FL@C+-9%d`CG27`iQ8?D$J*a**rckvgHt(F`N`+KU77l zx|tp9t(-wT2PG`JJ06eNJ0RsylC1Deo6;s>|37(6BdN9dH*q%wX@}&bJV=Kac2Y?S zzHkSEM9&!8x*zPGuP1L5%PJlHXKf$md2K08Na-o+q#2Ky0u^7*r*TT#3ErbCsFTm& zEWu>^2PH)qifw~=0rXCT9H0J%)WOL^6*Im{=PEwuXRyiTe=B62=APs~OT8!Bc+P}v zd1Sv25-*ut2cf^E z&LLjhhg59gnk#cUEGq*8YZy|@OYHvjd({&qPrdwH`sOGnS>03Fqhe|VEcU%CU24%(BP7EHFG43x?wB!vL!O#UYceDi?+8oC9p zEZ)-mC=Bh|%HYqiI`klGb;^qRrJkjYVxjV3>XM z+c{J#0Dg~ivl^HA2H<#$bu<|+2~%>kC&hx z@oMI-*l*Y(hNuy8fc1ULwOgtV3u~;8STR_MxT)`>mPXB8t}igzG*qxG5z=;%FPIwx_Gwne}5M34XIn@dGnL14DW}ALf;)+Bk zXs@z>ZwIfCN8Snqb>0#_(VI!m4$+Kabvs~z?wSLvWx>9`FnuHkCdwC75)a7%$3XA{sFEb>S-(Wr|Ufi=R@XJooT)M-At%8sN#6tJm zL32$rd#S`Fhp5F_`ym0g>``Q|1H|hbA^M6D+Q3})8g zT4Nll#}4byX1fDdEqg_An$v}D-~WN>)8l}9*q-$BLy4b)XTyB}AXt5@q*+2`Sv90g zBQ5elAYTGt(Ps3uM=YEshiG%;8BU>-y@s10emv}fvwKeIq)NqZ1PtAJF)^%>q~{ki zW|<^hA?FH#!JDf)5<495yK;j7+mep(5MCceY!T=d{IlP)Z+bcEQ=|UygP27r!iBHxJ%o zZuX@-;rCH;ee?O6Rb{}rR3*o|0D{wKua%Vd0Uno^*C>zF?;8ze1l)+hDijLEcMZ1G zmR!I>Fz$uHZqIcW*b#^;P7?)RnLRgR9%$e`;x-W39e~h&KZ#ARZT=9pR81u!hP1t# zWWmjsH$z#h=xN6|AnNi#C4}q_g8n`4lj;Rys#MSO8rb$PDc_r)X#d$7K*YH?O?Nlz zHOoePwLe_M;>gM@-De(rk?W{NWyoquLuB=NKS^C>19*1ci}64b6n?#|LTqBoOj{`i!X`p&3G39m+G8@sbkgw7o4#g-Too$M7^76VSVR^ zy|t_6BN9Jv;`g2|k@EXjCQVnpTeE{<&Uk0!Ye)>l!tDy&F)aTL)lX812@Ss|afY2( ziv5dra@FsN=DN;pT>jSb=aIih#ch3@V?b}XD4|5GV*#u{nkO%DZ6UsC%l%ZeDxm+B ze8~!lj)0^aUnC$Y{XdPiRydT_S6f~`<*`ky2Bx@2iJ zN-tvW?jRh+6BXkE6rgq$!-0%ScRIo<&z<$jK?K2u+9QNXPb$%$=!LwCk`4=*m1yBD=Uc znqxTk$kug2Wnkn&^2p$voM|^s zwe2P3?jT>hF3t0(c>|x4oN)Xg;U5B%ry$}=OFAx&mkyMLX8TDjF49gZ7$Snd8n{l@X{4M`tPBOE1#w{Y|DW9|6Th=yc{ z6<&tGW_EKw@}7CDB)6iI?W!mpq%eIc?k#29H7@l9?n=Y>qcE?Or=R9kS2-@Jj%b$X zGAUTWUMA{M4{n&>^%6N(HYt5>1?jB%5;gs2w9k7ft@FCTW&+LKP>+e^2!d!T0Mat0 zSu}h^;{{bnh22)YMEZCh4rc|C35YW9s(Pv$LLtMyIfc%Fuuggw; z%t?#lXKba_W?hTf0eIES{rWHTxj9VoVPQrnjivUwk;Gdezk;Bm%<5GZ3$`vMk_nbr zx=()wc}~>YxTrDAcnMn4R7r>}ksU~{g^|KHw@9c_q1et@Q^8yad$E3<%zUCSR`3XK zD`vK1l-!rGXm@di12EoS$joE8;fq$pcuhAv-6h(*9?+3Yn_D{CikUHkUKYPt_EQZw z_RAI(;6)t}MRA^Qza6dJhA-IIdBU1UMa9E zcT%*%Ua-KSdZ!V*uQp)uN9XI{xZLA&Mq^V2^Tk*piQeODOi=!FoUR9U?(K?8F@^NH z{F$XOX2le-|GD#&>jUFEX9?nM9e3e^jb6)Y8C$kviBhxuR&~3)toGSu)?aovkAbw+ z_v5ZMaD0U>AzwCGZtkj;U2*rRtf#~1VN&+nm&3#BXu$TTMXhx51r%K-Og=$QLW!}_ zO33Y9;l#y)u*VA>ERmY=pv9&Xj0;?`-{E6NgVtebHH{@TiTXB&uNEZ1NXFbpJ2=f1 z6K_M%=BCz~p!N%ExzTS)_7xt#!~SZf6YN@{y3uXYW3c-eN%;twyBqi3g+~ACEx~my zTE@0|WMIZmw-P=|pQ8y8-s=E9=)b@vfg9l_QSOW6I5uRfB!lIEhK(MiQOQl@ftcW%&B94*Py*u3HK5HelN_w zFWsgi&fxd8Bm%8)YRIfd)tu}(+>JWtbD+p*l91fsUSWN?6%MH~Kfj^%c#+(O`YS+w zoukVl?kANwTZpO}Q(l`5vEl$V5Eh<1<&XUsiv8f;AO92?I2bkYVWX(gzZ8As#)nHR zcr450-GPa>O7krP5s6$h-oC)zIR>E~_EbD9m+}BiQu*)huYXjgrjfAUjajk!(n@Av zIR<64`}zVg?~>v2ctUnS40Y!&Cu7Zxd{62$6F9We3fqJF15>OjnUc{n;hu|p68ow$ zH3N%#Yy@x~UfmP9y$|;0VtPL%C&{713xuQGxw_{OHLCruGqi!!3gRH>U$ubfP;#-D zN)H$0`q`IrDjp|I$k-A^5TC30AC~=|8l3R*y7$kgqr30{jnNc4UA%(on&5g8yuyV3Now*=BG-cejggqGTz=BXCT*xbdcXzjf52dj#!a1mONt=@Uue+_1X(!R3k&Ql-Owrpx zYPAZB{_%s9q|t;sX8*yGGCC#&JWUxFiabp0xTW-hN?hsk_)Q3kde&aO!dy;kpx$$Z zRh4q?|2$GKLFC5=@MO2vip0cS{no0(Q+47Xi;M)s1sG<*P^}-Aw>8+6ubLaUk=0*qzwut9{&q zt;sQ)9{wA+iV4^+f7)^{lWu1)O}S5>74|?ED3WWnb!qxVDGd%U4iqy85jq;wkqR`) zde#!5VsuSPD?!2YlFVe{L(g7NxTgW0O3wW;{(5>0)WZglmGXc`W&(MHb6t{InUES89gee6kk01 z?3w?YC+N1J#7BuJdK6u>%In11v{9?PcW$(Fvo|BthvG3--}p2{k`L#V(OTR;G2Kzg zJIL#nP^a*{1(0MwP4KxI_V4`SUz zhfp*p`(kjiqS^uXhoPABoMShdn}o(&wy~~^YW0sA>Nnf3J&y9luRj%rqOuO)-M~yD z!qIDDo9l-PhnQ@aga75~M|bJ;^d3blf4q^X5`7(4Pep*B9FRm2WKHV2>MX4F+;5XW zC3fVxN=~EA@ZV5Y+HBjCgWf71ZAVc4yw1Vqb2v+}X+nR|n0nEazI<4WaMGX`7!`mT zzs1CQ=m0>-F3a8nyl>zB#>tq_Buj}fd;XY0coLsV-fjiczCVQJC==CuN^pT?)m2> zP!0V_%rtus28s2K7mik)R@r)?(fZ}f@vVe5#Oy@h$?6FFwA=jtmoKUGH^^Lr`g z7_7W53EJm41=>9-^WVq6KbI?mef8Bfr)!lrC0G48NML}n{kXyLgUkn+6F7Dk&_ACp z-Tv71_0XvKdZ{ZVgkpc9q$lFePW-*UtdB1wvyO3PpGBX26qym~QA2xX7oN>_8w|V>SbQ^U3H} zNR2V6N$x5~{Us6(+m7;haRv5Z2w^dv3ZW<5B^So^u;}jfQsFD4^rqomy5f#Wr38na z1=n&w5{pH>(qsm}GoawqxboxBq$4w$8e(360if!7MAE);Nwd>%(o;L6z;KX!$ZL!3 zWlh6nFF@EYs0c5;zibFfcxu%nKs+y3ix=oxhsBV8f)F>CHi)1J9V;8PPyUz&MC&)H z*goM1Lui4t=+WHLywK55B~)0lFg4H+Y{*cBuqKb+8pH%1 z%5g~S49%X-@g~Gh_m>(a$f%)94$_UFf?SfE*fY^1%yWv7F5kNi+vD~}o&{xxoe;t6 zddv{UA|fc8iK8&bZI&o}gk~{H@1^68sisQ2nzVq3u|n|Y8&j#qhhQo@!MzN^!3yxY59Vb^7_&+C?!?@4rYfqHV7`MTm zFm~%sjz^`J5YCG`{oD_7`dtKqBS|)5ggIk@`9upy{JfK{0 z!aa(hsgM-G4o_mMoOv||Cy1oY_NG@9UvMG7g&n5t&Hg<3URPM5Xc|a7=u#WyPY#Us z2U%HCOu5!DDde?qdb?u&F2%cR4N*EVK9t(XIhAC8ho~Er_$62{g<3$Q>>}-=I^SDb zo8aV!*;Qm54e_Njv;XW-vKi@hh~<*uO~>ew#)uz-h)Ja=X)FaY(;8*0?&rRZui!jh zWWlx!@J1ZTf!Qsgb%UZi2nkmMV<>2E8=rs|zrkX(8ZU8!d7bX9?ZvVoe%+T=+A%Gm z?!HD}Xk>#z49Nj!w$pFA!t{_wr&%!@FIJ-zsSFMg7hSEz%u!8BIu?3o7RG<-i;&OY z-_!#G$u&@nLbL#)&yl1lLkY9!V7F9#(f+5ZmefLwg{LH9itbEMQ+gItb}Yr61+b&^ zVWdznW1-}-i9=N32$LHUsJlcgn!bAOAP@|um-$PnR>7DjuUb|kg9%bskZm`>N{{5r z0gk3mg(Rkulbs^!++9~VEV6rlqXk$71&Yw(7x*~VnT>g74Y{^-AU|oXKW^$vIf)FH zwLxhv1b9*xagL*oW0}Z0ZF+cKknmA0cBkxP35E!Qn60fq8Qm$}xDLdtZI6F=ud`fo zn^4^x0+($1|GA7Ee1XHcwk$Em^{iq_V#E`axz71$c;rP8Q3Sd2Lh*bgOt_>ojVuh@ zT=*4&50L&h3*bYBgz#_J%0Xbqt}E{#&AICQVV(bDp}l$lTBsI8tK`H%w19;w73-?) zvn%6zQ4b4KE>aA5I;=jiW5F@6|Dn zghW+s1O7c|j+$#vTY^+J$gc|`uvWs65-8jf9yldLI56_25g)K_hE<`%t=Oyzv%6KV zOml4C`>~nD0$f7_{FH(t;Zxi}hwVm&&qsig^-apSIcBFYJ*AqI$E8$tbzvUU6fO{u z$%JIY!P#1tzS;N4Tq?+2>o*1$Rk9TH!)$}Rt}p-o+xJCb3zI|n-fKfQdIht>MMKZf zSoK-Kae?Vt$z6JC^+Mx#VO%N2-C5W%cz8*ClIGpF&4cj1Wvur!=CQrRdRZ2~iA<(a z!GFm5ApWy43gsK$TO5Jt9)Y7!zn15FmX{vC4>T5QbA!E(`j9+?>`>bUA#Bt0|9I8u zaL{XcxpZnJQwenglZ|uy)9<(#;u~)bc@<_$Ty8$EpGG{zoY%i5kuioSbLez4re-5p z{k-y5(|YE2$W%b5o`?4;et7bIbLrCfjLr4UU-zE^BRvet_1>x4fpzc>k?}+j#!|85 z$`%s*YWnVTK?+LC!rkQ*ELZY*k};+m=`UI@U&e3~2VZL~+IXM!OmaB0I;BwX+FhQ$ z-66eM#gOyF%p+x5V=FJBBIoxSs>F$-v00L(dBLq@%KnZPgJpJf;RDCv3zo2tIkt`R zT{QX4#yes1VO?;EK?TYFXm}kh`1&V3LaxZBGjU1@3DV{`oJZ}Jiq-*5TYb6Sg8kB4 zRaHpW{ny^1gy-DcPTOzar%u<(1q9D~i*>==tNBUroRGZu8zylE-*xZ5X#M&^37a%| za|u3{m7nRDvb1h(eF^ELeKD|6Q4qg+@F!%@Y%{e)=F^DR#ME|RTmE^6h>x!COT4Sr zfeO-YP#!C*=c6%PwwEaXw-5z-kN>EVQffu~VDebl*ZB8!-b@7Z(m}3IrNTV*YM$*BjqRti;Ii*2 zAg3&1YvK56zmShfxb#w0RP?f<=UJp{3HQSEVjUPjGvOMI{8l4ufoL&jM*LL%Z3}dVrlDhP_aH9|=#4&orme zG3tG;(D{U-|EJ6T1@zZQDr=(bPoPwj9axJUq#_1ax72bpYvdNiQ9$);0>GuI)k+1W zvtyzv3Z|eKl{(mDP)lYtUcaIqIPV0E8u}*}=)Wh?p!?SX)d-4GMm&PT_)FZWpi-t_ z&*yC7sV()|^9)123yK8uU?EoLAR7Qf5(7YQ2FJsD0T|QOfk?U^VdFSzk+G?OJ&uuM zIdLP;>W(;o;aJ0n{t5x2|Iq8?g148o4Tp@tP_Fz&f;{05T^94^x1SSl6`u?*;A z9Smo3(k8GKe6%id_2Or(2Y9vPQhsf**E?~JwuUyms? zoquxzD^B3vjW9h$RYK{|&sqc66#E0^m6@icBY`#!A{%RVGi$L3u}J;Xzan@QaoFNsUWI@Y39x~s7myn9n#3E>C>F;J36m?N z1hdhY4iwr;&>t)<{%KMb>QP56n_0VEqiLRW-pV^F!lvBuf;#+vi2d;!xG6ydu5Dmd ztkUxzj<1{?ru1#ix}rboe`Bl-%jrp6Dxvj_;g{xMK0UBlZb zLNnS4(R_b}=-?zsFRYZy8kJm}y8(0xH*KEl-J+Rw3KL1N0GIQm2&hd zB+B^C*b|V#Nz6Pp`#F#@7M@r6+7ZbI+eq*tRJQ{Qu2Z3GXPy+KP(CTicc3kt0Eq&q zt~^lK`LJ2#4?)Lx<#&Fzrf?Y?$WXwaqKnGmxI#_)fgkU3_rQ2XL|Cg}uljdV@Y6|Uvd(u3Q3;W%iWBZ2sbtd>{!iq&R zi^Fsi%O&Xlf}%%AoU9CGPWY9jL{hJK%OV96axIvEP>Ym~Z3-Lvzm&V+W>?ZGx9`3g4MUCjs9Kc{nex-UeY?rFYgW)q|(|0uYomOMb0|~0V^50Y~%%G zFdzft*Ry+D|4S$e81?k&098rxlqbLdkTi)aWmVcsV_-xq%L{=my;E}SrUsS~xyT6YlWp{00 zx`B30eX*Ot2s_kDi0$AWrke<~+tn5yH*L8uSW9+0oIO1kKm6C7u1IT2 z)0h88`LE~?e^k`ppV~69El#KBqg?0QG(``p968+3F3X8EcSE)SU-jyXxy>>+GSV8g zO~pBFr|XPIxY)DxIptVWH3%BDsPGj_H@|=9s;7~O2OuaE8_A-swy^BeR$*4HLoo#D_yOd8DOWwvR$TfW5Oj8%%*S5L|H1to5{Rw4pi0I(U2WJe; z(QCnxGky|@ix+-R(8lD|htzq8O8#ResK2lK-x&S@p4>^Cx{nxwX1oQghejtJ zX|bQnEg)!JhBmdVHhPt#Vj5O)vg3tU5jN}^d%4G*npf}r89nKIph~;{TDNVC`t@ho zYEii0ic^(ZXY#C|}7cV*7Xo(=Z^;eQmWRSl)p)|N+X8Q-Z8 z#EK-zHPu$YkBiyF%h&_=$(}cCQsw(ZLTJjH5t_zxns%Ic$n9C^zZwkWgSP;=t|0pW zLb9SQA7*7uXxt)Cw?Hx)+Up3St#XLGygb*+SjOT?S$sIjFUm7>25!gOg2H}qV#0%QI z2d}~#YR!miY1J$myj_ppDkGN(do4|m>rK`B8d9RJ7M!m~7jndmt46C4&CRI*6sIT~ zl6OW(A%k>B31ys&PIL@!R9u(#KivIxKRWQR`(D4@^Pl#9S1>&6W=|^f8}SkWKCv#+WQX-Pll_d4wYhUW!z$(B3LZ z`$bnPg)UOpOTB0f+b2uvf37uw%UHv#ZC^Pk0B*?{WI z0Qs&`;!DOB^blWpgV!)lV-osxNXDqV*LPgG&Fpy@b4>1Hok4f-rYi{XyUcho&$tia z)`*7JOfiQ6fzB0bT2Ai-+^Y{68+N_2L0zN8a#PThg{y>Tvgt~hM_Nbgjl=B89n-uf zUH9kps~`X%h%uNVZyXP`^xBZ`I#5b%f!HOWAV6ny(WYg8kGmtT^b!G}C}0W9d2MCwcw|7f*T?Mq+Z$Ux^MDb|-_(xP?K_W9eiYygI4x zdP#~9zl_hRY-%8R>$~z)(jLWca8mKwHiiI^*a1usc@$MlNKk^*ieNzoZT#)mlxXPt z%ogI+An!W;`2gH#QJ}K;z1Auv=2SGdT+j|qny3eCtWo+^2<{_mxA6xhxCXHV?4LX1 z$Ax^vg0Y}Y9iLtc&X_<7VfC%M)%aE%nn65^&|)eyb;KP18&!{k^GC}h8`IJGQZh2E zR>Ajqf7F0U&Q=wB3^x07%P{~oT5$56$`8q<@>?k)aPag!M>a$r;Dz+YgT0YO^_zf` z0o8~<1&)U_NSLUx$(-eNr01zmLFAJytgMhfdEL?h!T6(4@{1EHnnQlD}&nred|1^lv z3@-v6(0>y+2Sb6c;Ico{|8n>*Xlnlqk>I%3FTA~Gm46t;jm(*Zv@ibYd8QMqA@cm% zyn!W&^!-B9xa@IRmcbu5ep;5AuXLU<*I8f<|Ggs2ky1*%+dmXF^_vvj2d>(67bDQR z_!Sf#>FF~~J9G0H07aYBvx@sJ4!#8%p)}cbxO>4lflaGVkVav$^{wX!b3d7|jRFQD z^kpZ4IRLSE1=w!d!`eY^taSSlidw>>89(9~)Y0MD)B{8Ih+s{h z;pt~WG`2l`v*lUN?p?QJZi?dSOD(&k>NR-Iv+U*PR0o5I=J|SMuL$=fxG9E9O#Y8U zUXb7ACx#tA#%b^)WsLK*>A2&h!GHkDv?j3NEm_k?RWaFfI*A3hig*o=t-Yd<;UX;* z^M{zHN4Fx7n>9N!C|Rw+PYzkvPg@@M9q^22^ESeP z@hD-I9}{G4ImaYj9sks&W6Ob86VpNFxMm>)=1hZ{|Lpji@>tP*csJzl= z+8*9fMqC_j+1?vluvm$pKU$6Sh`M@V-6-qD!bne>DSKsyM3@^4^!)fR zauZ?wtR#~%3L#-DSXF|RbTYUcbQ+|An?Zbaz{BG!; zTZom17_bnoME7886|s1XL@<=3N+`XDW?3V1PulC)#4jyc&jD>uvk##qpv|xOkoPY* zsE2{?Dj~tWuW+R8_R=#<@7?^j^J)i1yW>$)R1?L@Crv{e{f3ShS}>FGeH_eM@d#4K zPfyrvwO+r^I#bc-d(n%^r~|S6qEjb>UPrk)sYWZ*)REhp-yWNvKl zILy^y+elliH4F@;zkw0nw}CZr{-aO9_fnC2_<=HAh~$J$VD7~8>G|| zYHvA&249j{+}&PM30T4(@!r^~PZWVDAN2FT_(7W7>@dQEriDKP;>V!B`nI#jjIzuU z?(rVf>t+P5`K{hTyKT9>amw+y56cYiy**FV26(r1_cYry@eM9;FHa>--1F1>3cwQS z9NHYfiCfF!ydAaeE*>@TB^&$1txU}f$3Uan^b(Rl-bq6szFbqJ988M`h#Kuo zpim!8?q!z8(NSZQ?Nk9gf7X4PPZ+aLXVD?K?&$w_URxrmRb(g=u|&eV!~g8CsCqqA z!RQwqL~k{)&w@+>#DfOxP7GT7c<{1#Bm(+1fhSfSyKd4xX*YA}YU#810PeHHrKzw| z@v1{74T&T&TvJn@8dS$y|7vwQtoxOl6ac6A14IOczAzGjcjFR6EQ9KT&1I)4ioiP6g(eyiGC-pTlLNg@oq~c znz)TzrX~ocr!_~hpYtC^KCDLFQI`g4Lz$aZ!I6*gp&n}UwMPsaMX5;~6S^=+&F09> zq=J;`j%OZ9RchC51>m0fsL%16kw?mwk>x6r(^_7>& z|M5LXba6Fwc>j4oan43nNwo5veYTBy=E%_1+Vz_E`Jz;azSrJ8!z6hhM0%_=_tX1q zlatO2hiN^6k%*J5p!^l!BR@ETV+;l5O?4Ht@N*tq;QiKA&asmW10+;-cjYGboN@^R zO~s+cgCDdSg|=08c*0E@S9FzIx#~M*d{Hu_XIFK`_<{QS6=PO<{MmWZS^~J#c!;^o zdGXKcxedekTh~UeD$*yJ6UT(I&slrDN(oCtR>oL6%apEQwnlf1SWVy?%LeP)7Hqsyj_1}Ddj37QW3cS3p-_@Y7NS0GssrD7uo`joDG&IM+y{#VpdJFXgY4f@wLdOA#GCN|$YnwVusR|+#%kVO zDvK3OA=NV))`4~M+#<#b{{{7P#{D*)xsIXKbmq~Qg85}cg6_^HXymnWrJ>J&wZbjY z>M1&x2Le|A6`f zMO2K`ilp)K(A)k4>equECm=c=k&a^qA({OP>KAjkU(=#JEm3t*jOdQVoN??82b~Pz zz0t5*EmVQj;znNq9vSg*B)ubnt)Hibu=@MQh!RxojN}N(=Ar;^t&D~boz1gTw%JNi z$VQnmRU(P$Spnq*VQ$LeB)bq(&XD6ifZdu(a0+7d0|7R#qQxB-DdpR=%1cme$nhHK(^8 zjv?u;fHxui3JBcC{(P55>Bx`sk5!F%7%oe#Y_uAYHhqy6iV(JcWuN3&5zMlE9UgdP z3Yv-B{m*>Mwfd>|-*6-kQtE5?LU5;q-|8xPES2XEI3_K?%_?eKEK6wK`<{Ox{k~p@ zu%VnF(C;9!JE6@SPLvEMZ-rTEH0l00>k>*?#C*Y=O(#5~$H_->A%;|g;5l+kGB}KQ zMvo-SZDH&7T76dy#3jNE@Z6e{P*x$ZAUE#pV2g)D4m=boTw`>`SR;Z=xtnh4u7ko^ z2XnaumNQb2=lI6Ip(^mvl#v#4hFX_q{U|B!KTR;h01i_Mh+)@7_?i>K1WbLk(~knc zf(pMfe*Fj19}5wvjEZe6$ZNmz7t$a0vQ|IhZ-=rz%|S#8W5gx`H!6;(OW4d@3-kN1 z0umkmO#rDSgAV2i%4=1{J67v|zjf|KKw62$_``Wu3FGo+k>g11>AP5o6RY)+XwlWs zzfTdi&vVzP@{JM1cPKI1pm~|f2mUVm{e(kT*r11keH4Au--(oA3Hzy1s_Q-deecgO z_6ca!R50U(jDI%qYES{jS>ZM&SyzrmS-R3L)$M*hzFX7MdyZ)L z+phm-PU>X81>TgQ;_iN4+Nzz$;i{!e>}_|SY`JFoOj*nw(Z5%?EJ5W>@mW@u6j3x< z3f;K7yyj6}e5}#bUh$2%5fv#ep{B)Xjg)iv3Qu#%GtWcd ziHWsn_at|Wdw4u}LdUVnzVSEY{(0yZPHg`62MH)HOrsdB1EOC*=v7)YP}(c%YwipO zvHr8akFOZ0#zU{TwNo70TE5!z&U+ppWtHZrb(Otf>|FrwJhM37s@`pHIpky;w70~$ zRNc3#D_LJQU%#o(tkf_-CWO)eU3 zsl4|5V@_RVF<-`N^);Z&2kWmy^l>nJ0JsgtYr2v{F>)O|wvQ56s*m{_IYGzLVwQ@C z)en<~=vt^YazOf$slF(if$3r${<;mwyzYeBeaSifE}DUI&Go1$U-G%f=#j5_I`(!M ziMk34m5KPv>vx`+Fn!Uy@vSRF}=; zHWfpHL9;9QYdF!UmSlWW5Z?Lc%zyT6_*}I-narb0!;O=ffX(v0pLP8fPC?aCX7_1V z$TT6N-7rnk7%D0+f=!zPf@cYV(#HrRhtgt60|_O2gvwJgGM zos&%&c&-vllpiTX(h z(h1EFCvYB2-$jpRF>J`%aVxu}TK{GN2)8p#IezR!ZD!tw{z@0O?dayT z^NK7WI%lU4(jmZBVF5+^=cwlm4fWxe2Ml!ir(^pa?Rbj^B`dr%)|4Al8sx9)V|Zdw zvK`>${U!#*KdFTR|HPP0=N%qp1V1@NIB77^&3Z>$Z36@i_a2FM>;7sGBEO*wu;Sf; zXu4T#YYEJKK}|u__NDr{i06bO9L`d;fCbD0ey=WXx3Kd}dxv{CTg_#5^{Bb&ZQA*W z8aS9B6oVGR>|C~G{=;JP95(UgDn^VDc02kcaJ6-ig=)Ced&0F!ewpAAW;#6wW-FeWh8{5;mScnjx^mBca;@TsBkpmkx}Vd)nj= zKV&14{a^EY&C8=q;f{57E$-kp55@oi5M0pj<_x;(;oyz`#f>q7hCE(L$zryDiJ2gRM}kdd z2VS}z>c^y$e3`pwGVs3E^d)|2BBFq*$fV*kWqOD>-8-8x3w;%jmm+fyhvuG#Ov2g{l)haZ`oToN}R@aqLT0FzExLW-!i zi%lyxZmi-`s9in*z8kC05vj~2sMtFj1KY)mpL@n?9bPkXT{*M$bG1`NE`6ZK#w5h_rZf1+BvrLP9Ra8hXTF~sD2qCi9F!K<7Kg(B4X{Yo zHLhA*v{c3q@_)lrJ?VCwlgG-KDsVul>OQg%wE5O(h{WdiY=@k5>&+w$E#F(ROwQydoG1bJKp%e=O~C;Kf8TzrrlP6y)a1; zs*X1m7OKOt5nmVlg0Fs5PaG-IgY6ACIZ8mO-zn@&2yeWLNt!Qr-uFzMF6O?<$|}F- zbEv5)x{G{Hb=dgZ@8I0w9>4CunA9+gwfZDZyDy_i`MA!LSTz)&5r|oHZ&xl_Fz^j< zc(NXy`76n7N5-L|yDh8|gMF)6y2s;++>!nt9f|&1OHQvCB&F7!u`xhklsc+fVH81S zYnNNi17>=jmJ?B*VD*y_?$>hA0c|4Xw`#1H4l?SN7^4d457fUY!;*L^Vi`qLk;K>f z9g+^y=qC%A(L*5MRM1!;X&Aj>+@F*tX;$%unAi}aESivsHL9$?Y2|v`*e)=eMA>ds z)D!+6W$zeeX|pWqcB#v@HOsbb+qP}9%XXJ-+f`kzF59+k-R^I#z4thGogZh9F@Me1 znHiB08IeyYm})s_O$Ma}Y13+llvsR2Wo3DW$-oNez@!0rH#;t-UJJ@3>Ar%|X)xnA%o#HD&8JWcFi}+)rO@2AN(-PN zAIgu5H(Q@^l)6?g4xrIeosMwOdg>I$^`YSB^VeJDH+~W=0R$ulGt!A+iB6ODI|nOr z&(@QKIg?SD2;|DFGJscg`F5QBieP&T&_mbfTftt*v%x)>fyq$}X5Pn`LtyJUhrZAm zD@tM>p5bMAM1wugeUncpCIo#^ZcYl-G1s&vhYUat=*6K`Wq3r6F-Tabwu zr%FDe2`LK4>pX`DqcFA#UC7C|OZaV^5ziC>nt3)VnR75#IYt&;%CcL(STuczf#Gxb zD$p09!2<4`7?WUoIos$;;B;g}4K=3^CUp1?gdXzU4Xl~9HR2E>KBzs{M8%|_U3~z6 zg??aCweGAD82}>&JzTu%8_+urHiExs_1LcMxsOJjeiVsqR?$~N-!C7CkH34Qzb)*Or=>)?Nk{= zkiiNyq<9vy5re_8sGvi7^u_DqhycodtZ_YY6_|GWBp{YA{D0pdEiK| z^vY%CPEJ(e6I=ClG9d#g;-!T<(_usGG82Fr8vIhrvT$^&^3kw`qne@6P3{d9 z@WZ9Mbp30@CW1(nJ10_z^Xoxs+xY9b=c<2c?MxKP0ZC#q^^RUPe3ag?H}P)@Ki37X z_Z;}JLD;UiYim)_a@kno@1KGvhoSgCzJ;{N6yS79@@2kDlUnxNO7^_5)s=*dyi+~w=;BWH0sjB_;DJaG|iy%*`zd~PkQ5MKH-vxXzv?**G*PS}n+2mdKv{nce~EZ4y*yc&M-ZBHr9;J(x^n6o@tjn`$t&I0wps0B?0 zQL?d8b~rBhJ5;uY^~^SO_x-Z^8eLJnU!B>l-aA#H<(8jQk=A5Z*NIx|d&o-pHN`no z;yqwQhS9zWe{s`P>33P*#kW&jl(U%AkJN0&^2nF>!;_(^a>QFoOfp7HF_0tf`|KmCr%iXsIY)XM2>6j71-f2uZ^B)_ zd7rFn^SRx&pEUaILx@x^{jM?$WWHeYNAIJTXmyzHSMlLEVfUY=*%YY0?v0WYicJkv zwYrNPKl=}4AN+)o^-6KC>D9`O-rF*=-MD1dvTpEnc^Pw}Mhi6Ff!=7-*lpD!c&t5^ zBVXQP0Ch<23d65ub*}kP6k~4Vp19JM3T@usP`L2;00L+|!;or?F>c+MvF&vS)(nuU z`;E_=c=3aQ?&XD_0CprEho z1>^vLEn(pIpcPtRI`Z*FA0$a2(!&5>{UNO4%5;iqbw$wh2qM(;I|!_y1}s{D2h+W+ z&H9j=5qyXqdR{V7;jao6r+Io63i0iD1yy79v>d)|t?GkzR$(Eq3iYndm`;&jCP->{ zL*l!~ryi!GS7Yb_Bk8`qN(+_?V)dCc{(BAbs=rj+HO9)hOyM7M2nK8`pMtY-9TV># zu~=e6Rd_<{^Q@UV3k&(1pDw~cPDGmRVk)m5#j3L5FQ;aJW;em;aK6{m^@8zW@YPCb zJ%O6J*O^^-6fhe-^;Tir7eQ9yB_1lzReMDf&;)&d7*&VNUA^c|lzP0F$6sDrJ(%y+T7ZaawGjpZ-) zMXJSsUzwQBM;2VZV$)fxe1g69gSuT>Yc*uLHR88XU-am&)K`z@WEnCiE%nrR=%d89fPrYTz?tC+141nT(Dvm zQh8%dHbB(?A{K>EWqkN?y7Q1B2Dg>~FSz#Wyu(PiWv+3qWoWY>0Wu?9>jT3IW4MOP z!`_fuurO7KtN7h|UT=HEm^~)qHIi^jxdP}f; zM1S-1?i2eA`>Fa-1_1~HCWKXUD~<0y&wsPn0Al)2bZVf$RrY`4QQ{Os|&NgqQwZm-Oh4~#AlbOEE#{S%dXYX+8r~Q zQS^LuIjv@U-c>E3Cf(b(IeDV`ykO32*|VyP{xfpHb^7}V>zr{128*BE=G)mm&g1#` zY?r>@zu6Y-X->f1zl9f4=%fCnvk1qvZ9|$hMWxbl1MMtlH@{8aOid*0P9yzwWG*}^ zFWVm{TF{E<5ps-%I;Yg)HtVM%98X8)GC3<(+)}+de_Sjn^WM$6AaL#|j3|+0wZFcH zN-^k_P6FLsqE_Z)j)lAI{qE+{O&OTqr4yMUJ?siK%T61o7X2qTqWu24%G>raK<9I{ z8if#C>-T=zTWQb8SBKbpg6hkW6QW7q&DwOS&qrO!BTnS!=?e7}<^W#HTHs&G@gsqPx=o7Ceh8S2nU*aktPsiy*(Xb&{ z0~0c5U!qFk5`u-X4%+7M+zyzBAlimK>IoM51uu9bWC>MgqO1X^chZ&bwK;wP)dSBy z;_EEfI`dF}i2ti{Reel?&d5aZShqcPW`x8T>o?ABqSIv~t5vR;k<{bms59iNe!qR_ zL;ucp->a(Q8@50X$l`Lx&~Le9?;VzpxV}T!^qemff_Ue3w|3tQ41vUR3>D?FJW-jK z?gT}p+jwoDd2G7mnf~(DP7#T1qq|W`v(^VjH zLncJ9%HZWmJCj3JfN)ff;)w&*tSmk)1zLdxnaXB)HBNCfm$}MM39ufnIc+~`Z|^dO zwp^`09n(ZhI^bVE_@Ste9HZ3w@rOQ8zrt-v;`=vqV?|aR2J{P&w+d zhqkOUsG}Nu>9n8x;5|t(| zmBUbqgj5+I=|JDNF9XDYp%8%$YFcc@(#=4+R~v93$9q8FQKnits#k2zJtk}en^VNw z2@v!)VCoZPSM7Ec`EkK?Rk>mJphj&P4MJPNKL99yQT})Q2C@9RMpZytdT3#GtJM!C zs}|jiN4{inrEP47ezu|pe3U|I66l*7d_%u5`ZW}xy#Jjen z9!(c%4fXKbIE$Dlv}(eLOE3o=AkT=B@~6?g*W%Tc{ja~)k`3Q2w|N>4*eR1FM${n| z?WWy=nNSd4+Z_~xK5jB}!R+q3LKt$pELRb$!j&*pF>uypnJV{P&!sJ1sdz(oz{Jo0xfxr#=$ClymZ?#bm($Ye8NWjji5oQTj z4l)hAUy5sx1eO6S1Ls#ff=}}W4R~IZL_ve!0l%Rbzw3IjD+~J^4T_We^n3ZyP)@(! za|gEU>UF%!W?2OW{bRzy?n|^e%J+{K@pk|9qIAGm&i1w2qR;BP1Fzuo5wTso6}cm) z^x189*{vc5?CGG9?>EEU<>LjI4tL0kG0=@I^DmUYv`A^>kc57aV6R z-6nx$4jDb28HrH+esy_f%xSJNhP9~q;V)+Zw6NDz+btVZ)dBD{jW`YQSuF2U;vn?F zFLe7-`&!d#%CGqN@UuRC?RnpC@HBd1T}EO&qZlYOUrTAamYIg^dFKs-C8=dsrAPx3`GkJ^Sb5oV=0_2a!jbbk1?5B<(1mel)Crh5=eDfbKy zQXu%Vqiw`7u@}E>ztu781-Icg9{<${)n>H*x{&*K)%0-@4E|JQw)@4`8%l-L0acK#Ps(9DoyluAEnjm zPbvn9=vF+!F<}Gk3vM2AlB4kX4N8nw%@HZpC{00GCL9~3+KNpYd;cN&FaIU_*G5T* z9vXe=2b&6NO&Yco0nYuw-`gRW2y8hQ(`i0`^}xlI20?-rbl5r-DGx*RI+B6%*@s+U z6%K4nE(eYn2Vz$B8M)N6WOczHAnA5Y&F;WbUs7T`(|nKCJynmfsPt- zrt@6s86`zQ$CD;Y?1@`lnF_w=!&9a>Qn0^#sr&2Yh_JMQC1b+*WNm-fB=B3ea8*&Q z7D|}(C7RrSZ!`NJ#P?4E`c7>wm-Ody^x+iOitn7$^~|R80$})xnQ5z{6~W zzz!!MsQe^K@DK^+4_B_o&{~#8_+!B=Ap*x|v7jHaJ5&(uC;DSFhv%=z@b{I_=P^IA z;Q`2?jv!X~=TJmujJwCVvQ=8ZSqe~11+%~LhW9+*@*??zP%)O8+bp|l|+diag3O>MqZ82#QhUP zfsz_`OIlZ+j~6wF>3a|JZAE8&f_jnor$nUwzXBZw8Bbb!-bgeM0x$v33HX27)mv&j z*Xaor`H)!tV_N?f=`wjgAU{!t|<}FPmUQc z0dqZ4@t@RhNTA{7&mFs-spm(yJ3b)ZH;$r5CxI&PXlsG0;m~!Gf1j5a$-fUaNKjZz zD(y+SVdug_)ju6~XsnWQF*>L9S%g8VBkyP;Q`LGaD0XTs@o${JkbaOf4b{lDlDc4; zp**EnNhFDiQiJ!Z7!0tU^pYEH#Y~;&Lb`4ZRu$TL3XR84?e@-pHMkT4fUyk}#qmFY z@BDwk{Aj9^G117k2LHhVKZx;hy(ps!jmdQRQvG@nHJ1l zSxp^`j6iAEPjj!hE!>stGdL|B^t_#&q{=M|-S%gO5QA=hq0Q76yXWjP4Ve)|p?AE) zJi;DGU^P5oDP~2Ku&JhW+-X6;*IcsNk(ffhF1F9joXp?aXUqr09artP4OXPD%<-eb z^zotpfj?K{1qPtcC+iIt2LJm=truu7tM~FFf+fz@Z(3`{hxF|=ez?C4;S$>mIW1oE z#@UJl6xNxGuv%O~$ml^L2}*(dA5{WwMf}|bbh=%{*#8$)e)>NT7zJZV6fnvbyUqB2 zr}F>v+8n?G(3Vc)BiR27D8DQvFc|Pp`_A5mEow>AY<-}3Q|L-*i z)cO7Q12z4Fwf`62#gEWRgg~GxuO`frAo#zy3IdUxuq}544JBUezwT3skZTSwwFsdQ zrxsE|5xlnZeC8EwK5FkbU3%yw6ukC20vBO&>>iVU0sM}VnjqWYM|mB?p=%A6l?tIIR07lKwl}DV+q@20Dhu65L|nhkhT|P7pZ@35e-TF^ zV0YczoN13Dj_}=;6_U6lZ7uVNT|!0Qil9RbxIbYmA6@Sy1P^Nw)8eUAAHi*>o@a{W zutfJ7=yF-DO%s9$t~rJM8zYwtiV}&CLE(h;MVWA>#LxNhMnV36ZRr1Z6gJg5ic*ID z_kU!VKoJ6EI9aqQ$}$tT7WfPfwmgZKE$!LK2FIV7LCWt8olX%3vtT4@i}Mjwbk2p7 z-yArGaEUxkonWpK_b-G*zj3bxYfI3UTZ&W|_tBKnnxLCZoia6gux~ntY6Ap)K|p(d zDim6f>Lg)=916G$bYaI1v^av8#$GGT(#9ZI-CklrCa@*`w3XEz-Twp`_UZXpU}HN+Y|A>myR$p=Jv$BsY-IVB+LqRG7DD; zI<0^3bH-kJ)RvONZ)sA`ft)Q!Z5Of5@Y}e6W^*ridpm7Gqi+Wd4aE08YtzOYmR`n@fgE7dX1;`058R@?HxM`QYV4& z`q2YBSl|4y8*SWuiS`YIveo_;7eGIjmoKSCRX23?yxh?+r1fP}od?iZfj42k?WUA0 z7o~Z-b!EK~EAjxa~fc{4#WC*gEDX zC5QOsCjK)mSMRJT3NF&}6M%rP&sEp3DYF7si1bEupK%vUHcjsk4jk?L^RT9!QIbK= zN9-*_AA?i6xEGRAOA*?#naE>z98DQ%>vz+~nBfo7ndL~kc&ZE{|0*)JkL}$%_6z8v z4G~%05;gws`o5`ZC{2}&J{81os_5@|x+Uf~f^~JGhD@8v)~i=-UYq0-%UvYltf_at z(l$7UVkh|tQXD6~;Uca92GqfeRW8)_u_6XOWqVta^jJ}R-5W0{>8BA^8Ecd22E54u zI4vtg;J(WUt2^Hw8uMlA0F;&P-cW0!QwE(P#<%qHUFR0NPwZqsz={!x^3aIzS_@#^ z#e*gGQ3#hq(3UQ$FyIOy78rDVufYb5*`nrlDjG&_0g-Jy`sMbu6IiTZgi5U!-CH5+ zcy2j6Oz-2XAOPgLSVpn|L5)%X#6aC(2|`Kp&P#Y)D6EI>%X4*{cCHYYQ1vH~x}op= zZiDFA^z0z@|E1Tevfd2-&tBtyMj=hE%jj{%_Y5^%|8({dz+gG-6#=YuUV@4rp9Xlow;y!(H< zPse)_5-gCP2sFk5z)?(IH9{gX4H1 z_&diT06(Xr1@#@|Ktr9Y4p1*!p#WIb0_P#q%gI;04PBQ54W&bDE&l~ixbXBMMsa(= z&>}C4bS%m*EibkhYtA*cDh)_(QJqwB_M5Z|Qt0ZZ~Z!t}{6Q7gxKLUj{E{1Df?JtS*pGc0)#PCobHMqs*5@oEnrZoQY zd)x#yPdTGR9``A$?4oQ?oSmDqToJ!aIY&&;tLSoON&^ojfx$|H0ItOJGzBjB4oo-U ztgn$^hQVoU_?=}0@zP;n8LtDfk6FDDQb7Ma-T|Y+MqWCY=et%ZwoylU{bJAK`0w!- zTT>bh6^#cqmBXG+y=2e4f6(%oUYBi0n_g(GdZ!2sI#Qk{URAtuCbiPz+0t!k;vYoC zi|g43XW3E0BHxZd;eJ0|7g^8DF;ZOSb=zj=&VNY7~7h#KE!`Hp@=0ImlwkuB@IqfR{0xa+tfnF(N=TV>jX==e*)u&5teAiM-DwGRFrSawnhq@J-6h7$ZV%dOQEpGZ`mDXVA{)MNCjo&rv z*TzMb+#l2nVQls!#}k0N#EUKpwivzFZ$mSL)CpZ{HBQ9v9eKE~&f}T)+!;tkV!p>s zBsy)fhII*G@th~bialXvECq8I;Ss$@WcJDzzcOYq!{rW1Qe5QiKb>Qam7jH8G|TRX zGMFSBRKz@-r#ll{WjdW-Xs&u3+?Ctz+Tc||URgQeW(YIkTIgQY4vkXEi|pu%8BFsA zJRue;%~7vpOuol{RlF+{y&lQ=^p&>ch>6>c6og9Qfpgd7la75J9<}m^uNK=V*=KR! zpl5*yCB7OKjQZY|O&ONK6@bW$6>=&I_&j&nR>2vjmglTtsIbbz2?7-3=)M zT{g6QjVBwo5qZCZS)O4T%@A7u=&^%6tQ^j6^>SDKm~_Ht25tzdBEZ309S?9~vWlTq z_;Zfw!p>y?h}n3+pT1`r@_e}ir%d5+bnSVvj66fD3Du5?OP63GO=oW*J9gb>Yfs>_s5{T)e{wWs5V-ZUogTm3N|BM zU`fE~x6llC)L|K0HHzQ{$w3r06!G7ltxTenHiT9tp^A22!orB$+;!SQQ_-hrQZ8I~ zh_Q(+i(aT|zJlP5)?kTMuSeQtx-Azkkc``l@j?swO+}?;NR?Y4qz=^f_QQz)er-}~ z5!nqm(^&jHBAwPSQb`dt2J`3jZGi_K4}i0KC+QgpJ5Mhz8@0|Y)TnuE;aFLE9wvO_ z=GDDSc$fMIV1~)X%NSd#E(%a3fGvTDNL>$|kDU`OE5bikX35YGFYKnwCU_e9YYjSH zk$P;joOGl*UCkPqL4mlkP;@?VhUH>(2+YPh0NOS2`5tVDIx9-!Ft?fA^o7~0et6cK zHwSg70A=hxMuyh!S6UMlN4FZH$im(O-3n@S|D9Ps5Ddn3e@TpG!=W66x}@`d_d!&q z&af3fHog+%gL3)eVDFe!tsc(3G0cVUH;3)sGJAj0@}(g@fWjjR&1YzwjT?uhS4>n- zU!3$&lQtyyZE6^a+4zc4!zNypYIX=sa5AVEwTV*mN-V=2==3>jJ1#FrqZ26|#7m_y zENY5`J4J36#R$&Hbi*-%cP+qo{51Zw@6H9|Ym!=3`Wnqh(&%SBA}pGAH00p9y#pKE z0H>9xRgBp1e1tBOQS{%jbUxJ9KlewO(Sp0;JIJE04(~o?bCqNF+}xmBB5&pRKb4lR(^3SMZg^kCN*+Kxy_=P0 zv-LlDiy{@x!gm&znhAr(bM+@{q6C~`m5Y0~qn5o#@;!PTK#ArhP2;8%yhg8E;0c;f9FG}||l!On@- zliSqYo(PM+2e@_Py*$~^TrdCAR#)QFVy zC4%$0g5L;zO;|ltjvEQF2~R!e{Ft%J$exL9dQJPIq}(;(UKTIg8S(yXFW`tNy9qQ1SKwR#%S>FdylV( z^PK`Q3NPMUQ|&@qaqWHS4NyAtkCrSo?SFnvJfq6XmRx98B7MvBzRV??k`Tg~X1CWD zgCmd;p2B=srE9%l9DkT@`M?;S?Z0YMoMmb;dk$tY&9lp}y07XuR2-k!(rTO)gThGwqQtex0(zbil(6oFc42LobI0Uo?2frik|>uXsfbsZ}Vk zuc%~4+oi9;t$Me5c9sEq9OLKX7jM$`1kBocwq0l-P?B9%St@iM%pWM$v^mF zQBbu;)Ly&+-PfcC4HdMNYvT4I+SC`N-KIT+rrf3tXDyKY)~t1zuB_NC5=_iHCPkrp zI%6+(3+GM|@l%jUOIk`h!Of!KXz7l>yvxxe{SdB*<+n-FBKcE-ddn0v5!G)?TBF$S z)N4f>KNToq(60ReMctl)JhjA~Rfq{x<98qi|S?V(X-gB9riub$Ndd<2~!RdPjbp=!T zF%OleRfxRnt8!c#yz%1XfikSt2@{ts3lcMOika09&?{LnD|JiIX%X z*7a#9+-ElyU@NKg-iiE`^_Z$NMaXlwvnbuG$7PCmp3)$1>iwpJN0B}n)m`rk4O&vU@G23 znTiEyn0B!ryk@xHp>cn1zh`FUBtiwkAq`&wUY=?O@3S%N{e13CBF=Dcg`yGn`c|?+fyvohhg^P4|2ED`29#Z?ST`MmO_TB9=)z65)N_W| zs7m@o==Qx4D5D!#QuUk~Q_$qef2E5M2748@tg2CR|?IHU**m!wvm_uYOL`U#J zo5uBP&lJGZ#;HxCPWQ{V>w73a*J~F+d-20a#C;FXn}|Q!_v-AOcr?&D0&lFzpAD`8 zfG6kmaDj3jJkXvd_Dh@^d{Svp9mkY9q_K8SsfnHrY0m;6$Ix?@)2g`?=!Ec{!?cxO z!H-TK;4~OvflwmJq^b-V6X+}=G!aBhhC>Cu4XZWJ%|6I?PX*hNpOSs#8i%XOW-{49 zAM7Wq$|V+3fir9?gkBkG`*ALW6KVZbOW^rJ*W`oCr|Eer3SLAic!sD&JkBO5`vhWT z%fhPSQ7EK|#vHp(zXS8OUri%{J?X&KsI!jp=F|-v+ISLw?3DKUM~!A@GHVWWb1blO zsS`Z!-dFLsDDShx<@OS7!9$6x6;uQ5b$1htLC60145L+0AQ0X)<{yG%#0*c&OF+CJ zx?ZuF*ZOH7P$#$Uy)*^xli{?H$hB7sJE|4MzUI%dXD0h2B3Js-3>J7oX1Gkg4l+Rf z(ndxEMdr*13s;aDoG}xwov1DZItYIF_oTGj4sTVH#iowHD#64b+&Y$@H#Cs zDBRcfX+y14+-gQa>A5fCM(zE2h1sR&+wabNx|ri#tWxFon>F;Lufq56p**CkWiRF! zA?B?Q?P*FE&(J)|o#RBLB%I`8aff+woN;AInUC~ufQK^-6-}~aR7s~*< zYV^>NOeGcgy);DojdnyHjdloS(TSp_L0Yg4Jf~=nk^Y7aYLA7i`eSsFw_*K^Ty{i^ z5VMSwvkQ!mQ}VXjK5C5VePNUz4%*AO?3o>Jz{mVl1}^a*%O&%Ms+E2mobPIPMWA61 z&=3@gM^jFTDM6C&9|-NioI2HgS(jwP=li8dbHT97A!_A3StyuRPNa#hr7>nBsQAMh zHM>m;6{qe+r2Kq;K6ERHlB*B*l5N-}NV$S>Y1fESo!zE4bihfvgqoU#v3?IQii!wn7md%QJI_d z?#_PvH|8D}(j}qK-kV%Im*_k783STe=!@1m%^}Y=&g{gqt%nT5Ea?#X*G4+9RyS9q zg|BC5cy`DdyX;WkI(-h%HxvsTFa0=QGwLO?=t43zmy>FC&{YOn8EhStr*m{5%?N3~ z>QJ>;I|Sc8xc$c52Bv7-AZ4#82wy9Qbq-xVY4(#Aid5~zg*Ed}odbi{(GRgMR9iWL z1Z{$~DL7I)oio3L{FmvovIO6b-^A#3sXc|SFD1OHRji+tGa*ikx1H;H68gtabw=l! zY^x0WA6R_~uZ7Vjr|HHYI}k2YHN=l6NwZ%5{D$)*%HOt_2bXx^jY41m^u!?OeP zX`LPMy_0#!&~-gkrLtg>pg0B+b%y)C(NFnp?LCo~%R73`GoXp&-l_()RX$lQc8-J1(wlYVox(!JUnAtgi%t0R3E_!&gk+1elWAn*8eD!cLuoWJuiRFqK{-o-2hb7w!7tQ@NdJ z4-ioRC}x0tL7=!lMPlmWkeZu}i~sZ?gdn1sL;Ecwg+pZ@hJs{hqk<0t?Gk#tXu2O9lGsWmOC`Nw6Zz31;x^4%o)x2ly&h4+Wxv1m z%8wG6pvF`#8}Xxvp2bimolzcLmM;&tUV~@0fHM)5W7J6dv}uyj z&x?Q+JcXEnTC*|>4~F7y%sLj@-)FQ3a5lH_WWl8FIgBu-U(etHcY_9k6?~2g$~MiL zz#I|>8vDjG5wGQH5c#1-4DGprL{8e5AE;hd*6 ze^bq?+ZsbT$RiFA>PY5RNVAK5vJ)4F>To$J!B&0aQ%Of}M9QLfX%-$UubIc=KX;C@D8Iz*7$UKFO7v{)`avz%oY!Y3mb5!9 zif-`esGpMa2w&kDxlT5fh;rL7N%iCuzCoF$TBO_%%_U50cj!7I@&2Rb|-Q?r=V z=g#^KtZXkV4Y5dBQ37|kh*!kj6kc88i6v_E_EEZDcs3D8AdR~GGD0X9x4>%k8>d&c z=;qnI17dB%MDXfk5^OB zQ5UBfLtP0xec;9wxkofD1j219;g&9T{Do*XtPq>8YWa>}DL!V)#g!)S2lo{RC9gc( zTvV*ELmuN~6*FVK3YX7kZ}h-VMgC%39+wn0)_F1*s8+s!W%z4{56f`vjfpEF@Sx{% zIW_Tfyk)5j7CFn)HXnP$~{!YPn!;OdP+oKN(#F;@NS1oF+b*PoAnD!D5 z7s?@GwHX_!IhNLgVsYBY7)i4jRb#RmPEX)@J`E%}gfo(odqUEtB6O0bU)ylU$fh8% zYj>e3@meA>7SV%D4c4V7mt_?g(zN5-DY+g}U=x6h*~721!)j?DP)?MOPwD4+aw7HI zbR-6eV>GKFJB{8f9Amg|2Nl`UI9>kOTz5hCmxNqCOPGiEeW*0){%ZUo13&fQ>tcJS0m{|JBSODkAfrc0Poby32Qtv5x7;&#sO z?Zg3LNMNW3{l>%C>E?I=^VQyGdwPOcd`@^`2fv=&I-6!YHtiF$WAn)Z8W&@Hy_J0Gz6rLFO|NVr27Qq_c5v1 zfiL*|zEiNI$M@wUwW2+oHuiw5gXZ!NgEI(?&FU?fIWV{<#WJv%zssA8#ifslOw6W> zRrlP_&Tx*NTJ;OXU8M6pk367d>3p)4*aRZvj_Bi6({rQJh($JUT`+n+M0!kT@4V|D zHNl$lj{v;=2&Lsx*B7rnt@XHP%i)iixOR^>F*bU^Rnl@E)+1F&Fn#&VuON|X(O#Ip zrTctFr%c8x3qA5mqCS@3C7X{|nx8A>DR-(-B+tq#MonscHKmSroB)9wYNs$HeS6J8 z->s5qWeH7^{ygQ%;ZJU2+su@R03hm({7Qg|DN}v|`AQIsSxZ(geZkfy_1l}`^W`CV zzn-v=0E9muKG_dZ_0g!gbUjpj@u*<3_w1ZAa?^g}tiuG@598I?CY)<|j=lrcWyLgb~2c zJoFdBs;|s$Wd>Gv6H7I$*K5!Gv+B3=>i3x~7DI^&<#6|p=z=;c%fq>1JWwtqWATB` z(z?1_j~!jC=XliN`(~O+09RXZ{#tM+g?IR2RILkt1*)*p>R)dg@vpaS8R;r!9pBd=kv#~R`Ey7W{foPX^gOUcos$GIbHGSx!TDUFhvk@9g`s!1 zk9w;#p~wX{JjhvuUz}ypdAk<`pJJXvVy?7~g7h)8xA-LQ+ZVxb6HsCx69V1g?CwY* z`iG^DH-bTK7>*(yvSQc~rXh5#7pl79Y9OH%-i>#Xb$Qz_0jr6Oxv?2SX~L}it;8g7 zTA8_PA1&?X^`Nxl%wTbXH7~}jr8t~|LV6Jc?$2h1Qe~@XU7ubefIWO;voDF~)M`rN znU<54@e(fvJSxbuqZuegztK^;OQypV9=t`Ky!42)W!qW4!lG)2M&uy_+iqU|)hbLr z0J(FfWjW7ZlczIt0l_XH*qW2%`7t>&&$&xp*c@7Ad)#0GqmQI&sn30voExM{<&2^d9Hj|Y$2vViM5AOa;rrrwUx4sB@3cn zt>b4aQWJF8DyE`Xf03{s zvtt`QcTp*ph!cs!$gFAsYjkQL5B=y&&c3l4)uFr}!NU`cnejrtB9Jw>{*gi#CF2K_ z#K4i2U5zTZjD&zCaPW`p*W|`po`f4Ez#^=bVujrhICV8-VEONuEGfnU-a&*X)Tvbo zG$>68DuYgX6Hn`WJmcEQtxOQ|6@NAA;pXqrtJkHQ$E!5#npc5l3gvO)Oq3Mlx~&j(E?C~BJw)hX;@I!X5(@Q}nhS`KB^HK@m`=?Wiz(%JNWS&e z8FEzTDWu^Ku}CAbGyV0>Gq`r>9b?*IJ5Xbbbr3VN=!8$vx%_PApuJ8d zO zmRDr7yB{xY`0j4+`Lccu8Nfjvn9ns$D0Hz@ws^U!zt5f2_C?k^BEQd!$f~cqC-MR^&4FTiM;4q#Y zf*P2kKOR?Bp+8Q%kctAInkp)x&VnSoLModsU4&tNDXRAH z@45>H8JD`vjB(UPX$6_zl_bkS*$Os^xvGHRTQqi}om$SV`?7W40mz--ZEeoEyX(w9 z@NGJ`Bz=u#&T1}%>-i@GN8dCl$G|s@qS?MY$ zKg%^_y1|7xe3h1nNIO(_y(|%Bib*I&l`W7WoiIa=>Ld#e5dAXQI*j$SYw^Scbf+#o zFUVdC02YI4XiWB{u5FvYL!j~EbU1DU4)SUQ=*cbLUL`w-dLHfok0e=CJnz|5Bsy(8 zYlhfEY`o8P<$6ZazYT%gG7nGLr@P`G*7d%!il6k<+NC1_VjPF#TOY(7+Qs5#n(V#) zVLVn^vekAIV8Nz*vN=#mBOf)4kQ~I3gs_8HP%}s~@2H4s&l5}eWSX_e z_hs6TaC^!v~8x_h;3@)}f02~w>wfy_{ zQ{A|9SHDfW{9;@*xv9tN59B)d*CDN0jTMv?ddgA)`~k&X);wkRZ8LmwHA=tN8+1if52`EuGYZmGGJ}QR+(X!=$s*&!T#7S z%8)e7&W^nmuri}joFS|PxfnzvlZ<_zwqhXbk~JK37^e1XP@((V8m=^UvBV}q=r+amsRDoo=%33tEYGB@_SAbyYO3~Pip!LX$BZ*ODM~ARn4K>D ze7W*&*S5JgLwz&s-NB%QZ-K{QSAr8>XjD&I83k_p)|AWIoO;y~*^~)dBgtI8xLgo0 zV~tHy=p}lLkP^-Q?5>Xr({^O8co1B_AC*;j2g%D2=}q)=%Ok;Kysqxhwylxo07!kn zsuV)Ri8g>=G5QJ&NFokA%E;xlcm+QechN*7;JIQ}oI@@$U|WW-?_0b&Cgk&Vc9Gy> zS74|dos{D7vy|7bj6^2{iTBitkQGF4dk~BLK6=rMXHSE!Hw_C8v!f|4< zR{9d8;d+8e2|V4%0m*E;m+ui#ZSWJQRJO57F03*JE#Uj zE1*2Vde^hp;W^B%!8V@e%f#%xghu!Qtbbf(4du3rc1OQV+#BXaIdx;-6V^f122q7i zwD)f1T$R*Q4N}l@o_~culMNxTF?VNzZ+w`|u|6dK1~$6VW5oVHM&3EdvgYX)p0;h< z*0gQgHcq>zd)l^b+qP{?+qP}pd472BjW6Phc>miKv3KRkI=QN{D%YyS8Hg56%n0NH~|&NOj@CSmQY+g#PEy-)DFaXD?tXOurF1`J3wI)%KSB12U5;AR`BN@Z`nbq3( zpUA>{poqyx?=)Aj^93d?kZ;>^vLCb{n{_~OL7SHwJ+5e*`ntKS$Z6x`rsaE9l;z!- z5@#O+W(KL?L&?~JN1Mcdv4!9CGZ`gRM+=e%*g%rHU_WvoC3)nE@f;lcHn&t9U+YYm zho%N^gUgnHChsafFW`|x+H5$CNV66GE@8r^@Ur_=X9_FgVzM=QUzsY9Tg_>LX)$Ya zF;a?HODzOLC5KNe4om{RsZ=XbHGmabX+Vj4or=1bV^w9FrE+w^`E#-QnXKDxlfxb} zNZ!%N(WnF{IEKU8jB0|k9x5)u4O%Un_iQ01a~P@o7ds>DNa>-=b%sV}%m6S}!i&&% z>RxRD9lICr$8~js8PAL)t$5i)k>)2IqGp%H-KHDZ;E=p{5+y5t8L`A zP~<+I2<~!Kq$VxD5gj4gQBnHcd~&8F`&x}*jK#>vK)T{b-pTBb7YdGL+2~aJRDbS| zuBBxGdIK1-#l~6Z$27*r!Tj8KO;`GY<|i-ql3#T>f%;BO9}fi4<80+kxGjQ^zl#=L zy2X{~pK16#H8n#$9W7tV9^qcf;D#rEcYj0irFGS`S_fUj^*wtaf4syOO5|A$l`lgv z^~}i-BGU|JKAcRwXRqlAxJx+26Sput!WtKS363_HJDnbsayYwj;DG1*t4np?<9(yL z1zyb!qN-khKZZm~y*`WCtei9)W@b6yxRf$@HICKyymHnbOA(|{@Ivyt&p-=hWL~eN zC$z|AK_4tFB-AI$*f&N5z2Q`M!>_M~I_2X1CexaJ)|lCPUGcE9QFom2sx?|TuEX|t zj9B$F8}5~`K?>C$Lv5_+xdGi1x}sURi>Bs5hQ}wHC7Q%^{7K=_bmW~eLLqKYv*~)3 z_Dm8rbj^PgT3&YTer`IWD~txLh|#<%=oxQVIZQDWkP-tzj@A=Xq!wcRi>{wckJC&0 z+vO1sy+DUL)@GCqLTn+Me;h}3pG2Se+(e0&4K+4yf8Iyb@~uTr`>eb}l)UP7V@$`T z%F=UgBo_3327BA_QXI29u~^!&uv>A`@9|C@`|z%$63GSQ zcwdw;TXa?!kL=1&QI!TxqaG4Vl~oJ}`Cca*><8FzZ1&hidcjAokcVw$P3EcCW*h3Z zcb*3WWRm5z$km5T52ZZS5Kp<4-}ODNwe1v~(mJ+2OWBI0<}OiZ^cdJmgKwKOhY1;w zN@L_aIVxkH$2;wbmIUba=0)ck@wuBr>>U7tukRI_Y%%peteD2ueHE8#`f3}a)(_I~)jN;919pkMXU<3zw8uk%8_kr9wvaEZ0Mr zZ=ws$0=d2BP8;PEi{XnCc6lB(m)0hyWpS*VCiuuy_>p2?;WgPLd=*f)tL4@%j) z9(Cf|ZyW+oxDR-KbmcU=vKkzdVWC>Xp&V%I3QLB8q9!vICcVmYrAwt^_TNiQWpeZA z93tY1&9N>x`|dcadv#Qy%hR)h)RxYbg6pM1$1R9CteefGU$W6Oa55eS#QVG)g8r>t zpCwL#U)`mIg*J87$F3_C?uw}s1=-ifLqDTPq9tL6uR`+^LM`vR-go=5Cg)E*fuAu) znuIt{_fPu#o}EcR4_{1}L0_U%JjLw+K}bRDX6jq(bxlhN#uf3lsW_BK~QLT!?Pn{LM)xK54Zn~N+aEo zJOXkX#*sW|-KeZPn_tcwcZl<)EhV~M$#br9J$Pve8f2JF0O7E6E!#O$PBI4aDcFJD zOwDwM%6$P;NRd+_XlOL_L$aGe1%GhNXMiza#VVOwmGtOjab1{Ux|ghBqFduyn+KdY ze9&%8QxiYGkSQ~Kv;#A9D3aJ5#l8j&(P;?~-CosfEWe+T!iRBrJnh9o4nt~DFzpaJ zkW3Oz60w*E2QcEB&|#(#%_l6gRN;`?OP(L{kbnIX>p+obR4c8yCIlujrvoF78g>UH z>W_F}!i!l7DWYgszg>nv>TR(`kDT;pV*&eDR-YhwijiJGCeP5TsAoa8-S_U~7FVy*hWQ8e z^pq#zd+ey`7KiVrgft~hW(&UP;Xz6|exDPFb#ZAWyCsfiit=HJMGX=^fR_81m*;kU zmZvJ~m~3ftEomK__+7@zgnSD&%ap}Xssf?{@x1}J;gYWU2kGr?pytGw^lLu=4h;k6eLSi z?rP27^8yn^Zpyg+RJ0Vo9x{dumkH9h8LC-LNOmR5$dfU?`R02?AbGcZQ&fARJ`DXg zmGqP@#yqa~terw3YS_&sSknYImL2?t*k-6uE~?BYDIm?dvIuafEvx#XR?% z^rG7P#w*fdo#umGMScx#CzJ=}R*Xtl^SXfgRZ5@xiG9bU`sDRlVKWP8efO|g_ie9p zJa`%tGa7QqY0s1T+;ew-I!zD{tuiABIdF?8VRDe2C!R;K%wa3K3Qi7;i0pZlwEK$r zh6p0Xcg0DKOfr{6WpsyR=$*ynm#671UMIWZKNcd)MCIwkB zMQM(xvUgzCObR#aTt|izyIzr{)ZNe98Pr5q`rL(Y9tw;2pPZn64LX^oS$g} zpgQwRyG0qvI?T z8i>S*>|E$d&Pq&iN@xWD)>eAl47o}cq29~_rYbrfsZ;T8?=`t{cTO}@l;>T!pbYab z8sZ{_Fx31XuB~qac)iZUS1b9Jv^yfcKS*(Z|EZ_yn63Ij&b-(U0=BG>YUCXY>V8;# z+Wbb9eB8MM6iZ91Ix)sWt5zZrj6xnWs)fS8Dy1+=H$Inbdt}0vL`ckz@DrVj_ z^6>LyWTBy-qsx!tA7q}CT+m^&EgrZzP3$d;*WfC-qSXLM7 zMj$Ss) zuQY;BHJ|rFxUp!rym&5%tCb^YRokS2K!5j-7_|z)i(MJ825$pu*U)7fIM?WtNmUHyfyPYw+$+rtd4U zLcHVUiR~6lTM`{FPdgl#Eu+ToI|%LtiV)GnASpIhZ61!iT!9ds{HeuMgJH?Db1tEw zMqA*~HiL|;u`O8NO%Ucbzr!s#7(AdHs1@dbAR14TJdQWg>$?+QW_yoX@)c3YioC4@ zY)4K3L)Dt7;{w;vXJ7FEo=8MXk1AW2hQX3(fen2x6%CvC&~jo z9j`;WyX~lb$-o*uT8EvqnOP6FIq%Jg-zn~IVyGg0Sz}pZ9SUyHIC^jZ_Z(IyP2HflOgaxLx6T$qsX`-+faIj2rU+<3d3HP3mi@UPx} zJK)vw>$3fug!Y(UM~8%4F$4G?N|mU2UDcr+im1+u`kt+Fe8LwPUpiM$I3xz2*I5%j z;C7=pRXzR@D3p(1uDtxW6szuPX28|tRABi#DGt4NW~SLbRUvuJNISM5DvLN+We zsZ~UEZB%w-HCcH;Pw;>y(SV$soNFpY zrynZ6Ab||lv+zwEx%qT`OJdD5$2Lk}$62|{_S}F%xq3)-PS|4aC!Q`DBdRT1{Ot3r z1Puu?7IH?*H7G5(x)$05KBNtfKf^EW9AH2zh+tINnOztyUM+a9`ZBnPz>U(+lA#)tj zEMo3Xtp=Q-TGmq zS6`u!kXbkimYfQY&k9+XmiU4oIYnmMFqu`I*Vw_2h5&~iKX)yShr!hLQ%r;xt#u2R zywIP#;JMrE59T2>@;Uz5M}AL1LGt zOKM>!9#&oP140KQA{tHMpv#9cQ<#H?y1;IsEV(fA_oX5t3i1ffReT~Wrr`lP8lg)F zpzt|1?tQ@uttyoaZub&bGfOTR(!0jTNpuGa_>Xpjl4NXr=up6=!oZ)CIs@n4XF775J*xp4<&s57&u zF`^vtTNYMudtm`G{ty*9+!-`XQSRB?cnQ-bBJ@KEpq|k|q!+;DSvN{?@+(2C-1y2> zwxvZFDDdV3z4s4`DEc6rZh#w}Iu<>he8AcX;$D#jNTX|hF1fQ5>&q!X(_4q@twL@DFOl2^d)!B75VK_SSg*6_GzXY_r#?UiTQsQv)}cI$OsNA{R8VZOFG6qo6QobM)xotD;d-eB&27cc!GABAM@z@f%rkA7F{1*p#T z$fFmz)||-P=wd5iVa2D8tvGw!6fEP$HP$wRk*|Z=svdYsH?vDa=uk#OK+}zOyMMHj zR7ab3&I(Y%_%9b=(wPbIKs)L`$3N03HF`@X!HSNZMtXTY3%?5muSe27&c@~!8awV^ zZp1hsc-$Bl<-PC2x9YAsxpQ__L@bgLZTqU#+6E%s&4Bf_4eo{moj2hw0g@{bqPx?E?GmamV<)&sj5l+ zX!ZnSTHZsdcwWjgdXUJn+%6(}O6WuM8fx&f`+frgu0ZarzglXce;?j7quGXiP1_~L z=HWncACZ*s>wf+WBVK?vxAyHHB6j~Bw@ai^5t#yu9segH!B545KA4FZgd}xR(m8XF z=BLMTgr63-(}9FgWgitj=@W_D#Gd&=blQaGz4-#TRBgv0CA>A?*Y`jdgu-M->Ni&46?D23RKR6nsnKTKro8qIx!W&ZA>_m9~ z5uVhB8;p+Y*>8EpN*W1{kR?=HY#&M`Dl**QG0@w`&xzAsy%kxXK3GHbb_Y?U}?>?At*!M5(j1hY2<%%sgY74XyHsyK}n3L7y$w*4l z5q?qa!XteLDv%?8FTl#+zY^BPT9jXS7`v(uxh}`YN#2k#rmA@ z@o>4T6$NB7a@XGTSTB13D_22+7)H(Tla4cFMUffqf!Go=85h8n=nifzh>0~osc$br z6ZExUKA@m7XsfePyU(OMIc_iM44f#J339~RYT8sZe(up09;AIhRksXI}C2{}4 zmLbkcnHQBbu*gOww$v_g+2)nwuO#~A&Y9he$=KN6Ac=pQk_u=;-2=BKY*w)g_MC8r zI9mC+OB<=5Ffh{RSM2<}kWi9uS#pB^Wqa@=PBqw2@irn)ciNh;(JrcIMT`+(8^7ml z6pR7^?G?$+H1)?wES9;{Qry@$5g#EgW62jLY}Q{rgundFJGUBmd83V^`qeQrrKN;; zy5{O{Bq_$$2a(E8gkE1QAt81nU;4#HyU{@uIBy7jbdO#<>h)4=I^ux>JH_W8Do=An zF#i&iJwN|8p)WBF*OEH2h;y`dDz^kG=CD%L5Gyx++PtRDn2+k@!rX0&vPsjeAN7T=S4a+o1dwx)xnR$W!>; z?&^3~i-J9YaBZN@(Bp3N^<=Zwr|3iaYej-HU2N{l30x)cyLGHx!cMZ^_|n47`irqlGE z^+wniGdV$pG&{N4NV+C@a25-ijnDE_y3KLM#yt|0D#!OrON*Wy#)QSAxf|!c((SN= z@8g%AEM($9g71Z z4fc-it*r*Ct8uHcMJ6bz0#5O$F3LRto#CJ{zPt=8O=_-KmE{ejWDTGwMeg&M zC2vtQhhsNQs)^#U=3MaB?eh+CsP(SFKWN$A|8*{v!bXJA4T5kq zr`p=di1}Q;{W@+wo=qVagPi?--ZTI3NrWq0`t7XxzSv<$O(n!Np&X!fJcN)}d3+ru zfnMp%Z9U@K)!H(nhCiKLGk*qrk%FWjsp)n?WL`=WgR1hVYu8mN<<4SepNr*cvL4oOkQH?v1(V@eLGa?h%JR7QoD4b3Tuy!t+H$W zUmc7lT0)SO$#iVSu%oIN`u(y`iP)b6ifT)9RD8Lg4!tR=kGszw27H{9-ut%&?go`Q zuCpFWl)NA1((T!T8iCxyGSj{MlkDjDupb6b_vCsgY$xx2{p^QPErkvBQ6iqV?L!cV z`W%}H;b530&&JpVxw>jHEBuGYd>SxePlOd}TOtl#bRqM?Zl}`H+2!?#tzW?e-saUh zuXZ`#3Pm`78oR%irLNs?nu;PlC67`S8~ElM2Lf-3QZTEYH(R!L?Z*;5Gh7EAzdv|m za8;>M&$j49mg6XMbjV+wt^-MbXTN5Bujn{CbD1-@4c4Q4x?M`68p_*U@I31Z|4wC3 zz8mKX)0!A@eIWH*Al|7(=iK06!E4g*ffONp1XXN%-16+NLD?5~j{OS#ED^`|5iTp- zNKPVMLLwJ+VP`UI@epQ_`>b7Wp^z6(80+F|o08^UTU__$`JRcayq#=5CexGLAlGYu zNFHhUlsasu;8W49DmkAybQ(k0xM5zj?2MUgQ!Bo%b7Ur@3li#IEznZ^1=-PDu*%f) zG;5dmxRi0#Dn3E>h~p#Dyg^W8SFFKj>OEnr(*1hq&?cq1t~$m|M<-4j`$b9(f2;kj zMVQz~(;<`-jZr%%*WkzjZ>EBRSx!{Dsf(G`6W;DpsB zLd$oQH0u0QLaLSh?FfqR600l9%V~rZe^FHc;A6_+OgzL_p^%Xfk$`W-1I6hkitl+Q zm`CyJYMmKB-6iEwMX^&e0-6=8)Fm3FBt+8|z3VoU>~RZWt=eAjgL=dBf>0?CkZgxT z2Y=`skm5t;8DT)Kx0=}$9^sA-ha%SJ@qEbP6$?OFXJIxC@u>a z=5QJ1Ea4AcQ`MNMWDQ~$*MbeDvznS{dSG6uh766Dei8>S=_HrVmjJ&3IaqpyabxRA zg?b9nLWkEFig@W+iK0{T>IxKd|BuY?zI6GlQollfU8#aSzXdE`qFuN`*r?4S#1QN+ zP)nJLl|SxKu|IWZMy=wHu~M={B9L?H@hL4aiN|Dpg411+*h6SK9DfufL=sY>f=`%= z6(wiyfEXVF*M_N_OA012R!1S`uL1~y-QhVOEw?jL=UB7MOdsMb z6B*y`eg$mYGr)HTnLN;wFm_g!YNbn0){7xpJ`7)x-Y)3~&_59fdN!Bz-!^Rw68s@} z?Q4-~kCnNJgaiy{%?*W;^+%cV(bhWhmm^LmAC!`g^x4*9OvP{t_~Q@{z7_^YzACK= zU0mcMDY*f@#2Q)L>7h_lAVa{Wj#5ki1aNfbmXsTiz*CnIN%c1z0;i*2VYI_P!|;-n zK~Kbp@6YBgS>sei%l31J9k*_x7-HRpn}qSqC_>2w_yv2$#8&&V7K=#D9HMspke&-# zJ=(`e^y4*UHgglG$g58Y9oK^jUqRP{v<~9Gf}GSmp3D3 zrE_&bJ&j3)>|f5)Ks6mXY=M5zAhNy>8htZld9k?~ra@TubjUDwX{u=PbNk8>)Q-(W zAy&WCpSh_VH10G&mEHfi^@oe(=41NO;j1_q3Z6={))1X^b@c99@%?AlsGMTBHu;zpcm#XqvECRMfU6XYfuP?3JR zDjiviG|d1L(-^bKprkIsuD@%vBgqfZM zI9Lpa7_W~iU5Z+hkt7|Jr=mbG7Kn7)qI-oF!BZ*rP_0-|2=-Y@4Z~e{MKa@dJ`zk& zq5vsHU`u{e4O^>q7+6_^quiaRNw`#ZVbUEL*4aL$oY?CUr~5OHMgYx4tE)|2MX|0S zl$me_ZS3Ol%z;!OC8gc6BJJt(X678)-oA`tzG^3XQUig8JCi10i+EeE-cV#uc*d~SoHW;e));Rl=<(5tq-Vq;f0t+A*RM$AzZ()P+Fgza zhwppdQ6_Nvz*kL@GI>MgNbdQc{G0AxTKujrH0qbWJc+x957Q%n(_uMTyu|Y~pfO&}}2p`q=+N{zDn+Me*f}&BK zo|)GzZZ%gAFl5vY-C)RXG4n`6Fc$z5efjfm4QedKLtaLBna=L?NVb6Cy~oiiyjKJ4 z*c53pzGOg(yY_ox6|2fh(5THW^Ahb+f?W85J0ADtwZz6U!SCbQ{DoLPi1eS4oFRaB zN8j6FsYSTRB$(XfqINr#?w2}-fBdR?M-wlOv&APtp|KO#}T4vE#Vzb zVZaV#rPW3-*QUq}(<0Cw2Hz5mj-gmgVUqquHYn8ATjzQ{-{*Qk{#U;&LGOx}zF?9X znH3P`lBoGtXFyB=eA;i3xrAygppZ6%nwYVC&+3HBeYVMJuLdk#bqJDJS1K|($;Sm7 znu+<9Bz#y}4yn;P4Bqf^0&-xaLwV9Po#YV8P|)qo)oS=|&&zBN7iX4=ucODF%9O_E z?X2a(sJEq@3GM>V1i`QPKO|;yF8|Mc2~=jbJ^LlE7txF5`NEJzPV+<6lDz9Jk9O+Y z2D4{H8(juxUaaN_fG!MvLzVFycJ7_6V0TXk%Qjh#;7yp}@e518!v3ABUvzJktEeEa za-cF#>d!-?VwOJ#>WfFQixofLAo$g&mcJ@jEp4q+pzvnNtkfYt?#35E6MWf1KPdoi zrVOLc0W#oiNQa)B?7aEB8!jH*#+GUtxymqQ|itYy?dfIb`=Y>e? zEZy%LEMjeM9iJ{)jvWgaWoA#x@=4I`$-`3Cx5IObzcJR6zQfXog@$~EQM-eBXqYE? z(*m<4cLrhfIHD7F4XT?ej{C87%+|oHBXrp=nWbx)JI8?Ao&bV|kdlX3Dhj))WibsH z--CU^`kIHqK0{bzX4-vEQ1Ld{BAm%Te$@X8@^V!n6$4eO z^0(S}bYIR!Cwp?F7x(o&Ar3!anmJM_<=52T@J|=TUIdVMxSAwSXxKCH_#`-kUO9NN zz$^Q*22oOm&lR(D5K-XS=Mr@(~&49*tRB5o>2~!-Q z^bEqw{eyB2LAHs9R_IKGgF+PBmhK8f!$^LKxTV@Nhga)~J>7WxhBsgh>@j~X)k|uf zRG1m~Zb=mgwB{%zE35wL{`h211xJvb=88ga$||cLzbi{i%5&YS$%6vShIq_$dAk|}tv^vm8sAY$VdjA)NDD?O zG=%eu*OvC4=;g%w_(lc08Q>Mhi*1HdnM+A6Kd)rQ%UEwYdtMh=jiLBR(RnrW*3!}$ zU<&UlV604U5JuO-b*1bX+ysGoq~Bd)rJqZa6fR9A#T6ppT31T+i$`XDX}Ey?DJRyE z(?E90G{d}9ic`3VS9b&&N;SATLz<)!%e1W^@3p1Nupy$~mCwuxYek#9OOb)kO`t^t z6*YvC(x7!P1d2TiN!SXIN|h{l^N{Y{Ir%!(V;oDFep-BYduzzj4Y)F*Jtx%j6Aj|z zQ6z5wq5pM}-HpI>k}f{YIHskWk_WMm`A~=h6k^)MlQNU_Yay|7bzAqn7$D2`vGv*C zV0clzQu&00&efi6&LmNaJ-C=)mA@jm*{UyG*v#ZF&+QQ)0!(4Vn|~I985>5LFmZD^rCk9+xV&&)InW8-=SI#rl`NnNMP2iJn+0UI2T_ml;CF@i}p zl{H5wMM9FoUjh03js?|SnX=GvNgIk)qNY?ZVOr z(RXB0<9E62x0!FnoL*h`e9p!n9aF%;cbxQlUGNjBTQ*sF9z^i|eN>f396CYt|ARgq z=Vpi9VA}ZBfa(u3gX=vw?uTMMrE$w6YtZK|)F@#km?!S}9CP8Awx&>h(&=zlS(>_V zGG4CljfpLG&rArx25Tt^!J_Q?pk5)*4_I7#`r*dca!a+Y1xef#bbP&PlpybOHN~m= zUiSQzTTX75f=hc%Pw*64k4RuJ0SLaWDqMRempk%Fc*D?=^mx{007&k6wNdN9qz1ncGVm~6DhvOM zH$u2oPQbhRD!4L8$Mld_yW^$Bow;&(-ndbK{_o#Gx@>Ix;8FeXBl zU(^ctyxHd=y%#4+vuZVwGp)aSAV?Kk*Q>^CkE(W}LxxbR`gpze#8&kBd}6)5bW0dB zc5@Lz5J@RTdMrcfw-K)$iQ+i#MdDozmz2MX7=X4lFDfuP!T4(3f2T^>DXFyVi$wC{ zTD5alP2Ze^ONA?R$ZB=SuGDX^Kt}&wWKg<8kai9?|Z4pCy&~ zpL^QZLZ8d$^*rontGh^cdf#|u1m3skh3{^_&gIA{wykehLWJn$UXxn4*bDL(cSZ+f z;*J)xkyQtvrVr%lRLTqY+P1Jcn86evD+&3M3u|dw0YG>3M%tk6=TIvM%ieU9K6X0( zDfzNQS{@w%g)x8!tINC`S$KxN5)fwqyq+((Ia$13fedl%87at?6rw9C zB)udcQVCRPzSKT(kWN$(NSomfN*`i+J+Ad$Im1B%f_`-D1aZmBH01nP4Bw^K?Qj<5 znDnU#!lr~~p~6(GeoCvOJ4ig6VV781mBxRaL|b8r727U=K|j%3&=Y(&0JrTo1ePbb zifhhuSO0G5c0fq?ex(KG9ik-l;qwFZ;>ubkf>O&XkCk?=6f~RFFB;=@Kl=Jh6J%|L zP%%xeVUTVyVzvp>#0qV45@h(TkbGf{{^uJfad?9|yC2`11zO8>n^HxQxT~gsOY{Xb zrOQ{=ylUdku-S7O)4tp0T=XEA(>yB@C7@ZsGtPXcj}~CQ(dqtD+0k@QFN>5@W*Y6o zcvf8Y4@ffx*&6W(j50!ICDCfd#~&Yfugrbs2-Gg~j)tD8LiTB2@ir)=z+NFD8Bnml zT>GnAYTxIAh0rNnhw-tC51Z{_E;RXS*@9%QB(Ty2fC>Y(Og0_(#?p1e z{cMo$KJMN#d?1)`AtNpcp2#h?snA|{ZnK-q$Yt8dWl?j?D+!xCPQ*&nl+fKlKL@}OEc;Bm?3-Ws1P!)G06+6rL`dx5t zwJo3aG=c|3n@vuKFgY7+8&r0~TcT7)+rqSr@X%>~NwJua{A97J&oATg8x=QyOjvedXA9yZ+W;4ge_mpNj;- z!gRE-9cBvAK(=IT8dM0Lt6sf4n*h-$N0=0&p!-^Q9t&jzoKex{?^6V@ z-1kjW)mpXyYX5Xb3vnbF5-q}62S|&y%L|D9-W@6UTkV`GgohwK4dU)LFje@5-2OFE zdmETAXdXP5cAP*>a-y0y?yW4mS}X}iJ3G^wFp1+S$*S8ULGtHR{mGTftQVVQ(at!D zVTbuR#*&*Qb#p9bbp6!NHJ=4zq_N}YDIzlnR!wEmr+~#!_HV3eOBK%Xszh1@_7Ne^ z1r@8O5kqK0F*#Za7}fEMDpYscZC(IjH>#T=;9WC-LZDaF8YX9k!kj--faL-{zWA%- z8Em_qkIv_TV#{8c$m71^YVh&yL zy3J|MP{kU8s6JkA;@f$fVi}_ha;Fx3ZRxJ|v;pTz)Gp!&TE#YyZi8b%qx3p?@)@?x zLD0$@9k`7R2^;XOcRikr>CX$Hxa;eYu_n8!c-Ef!X0}gp|Hd46G^d51H-0>zaKiC8 zD^KY|+l0B0$AGr>C=~`;0T@Ec(sIv2#xUZrm!DE9q_~|kA|Z%=K?&-+#-B6zqMBRZ zhaKL5h@aZ)!V(x|IbUoG&|m!BJsO2pQ9U1~lQr2Ny5-Go zE={~2y$O)7&vVnYD`6X{p?2J5dM6no^?!kWcS<&;F~1W_ zEy?~Mj@|UA+`!vf!lM@4Vp9Llep5epLR|#{a)(%ni}_;i!xa8kKI^RSB^VWw`W*GC}E#Q}&G!fOap2tV)brCIVv!sv;5m&m}QUYS-Is-(o z+^Uff37|FhTtmp>jH1~RQ<}6$-+=F=v2P%AGoWD9O#LLvJ3Ux0Cz7v`hX_dsZF3w5 z4uoe&__b^ac*z;ghB9DY0DqZYY{*oXb(8#SC4xO6I4ujV}{Nq=i61 zZt7BkpVIwWHUxMfzRB7(D9LHvi~zB>IayDV>*^sNtedy(3^J=vk9+P6BG4nj*c?sr zJW=+mn>gFAu%daS+F8_iVG+QnCumu;=?6m}?_i7+&;Cpu#_9D?0wd zF@y{P2->{$VzYv?b76)|A@x6Ubm04JcGKwWeYX$nyJOhC)nLsBiKQ?eb3C_e&fQO? z`s5V6y5CZ6dOeghSKLcO+NV30Yf5s5p%lxwN5W0EZ z3VXg<#berGy#K+)Lj82`jqSM~)O80Y5B0b8X5cC zN>{@AdVVN_+;c!i1>3cA!iR#0Tt}I?vf*obN|WGlRVWbFh}hU2ZpQVsl*7_~=W# zd5Ay^sEjx`WiN%2Af(SKXEPlku2kc7eCdzc7O6Dm?a3G<2dx1ZM+ac7wpq{D{UgTa z7U2?u?}g50UjcIcsq&I?vm(W2!tYOZQ~yp?9$j}E#!&=evd<-%;)Fo;=Qh*BL&8oW z1%<6YKlUnAM4R9^3>1tiL5P>tdeE#fq4j!0vz4Kb8@uvoi3r`;ZKUiv$-IR-(99Ej zwn~TNT!90Ov|~kgTM6l)ukb_jP5IMRJ72BpJUBj=SU#|PHTNbg(GB{~6~rSp{Kj(1 z{qCHA9YOMzT8S1M_O9Snt!a=c4&Ke`3Zv5ho5P@Q4ABw@Gs11z#ntZ>vK~Pio1jQd7fS}llzZT5B{eG_>2n0MX zhyBif9}Msy;(;Fx{==cbwF{T@cRT!9QmCJE{17;$$O~8i^z1ZGBswWc`Y#)Lw|et& z+Q*jOuhK(Q{;Qv)F(9S=gVDLK`H63hYYYZm_bA!}pQ~`+a0LQ4qQXi6%$R@DU~G4~ zv?6?W)|!9w;tlVNHY-(o9&Z`)5dGRT{J0-32ZjKFAhTpvB!RAN_OWBGuWr8R7!W*T zsM2r$=+K75_>etT0ABAgfYPMeJP8jSeg0l3$DRJ>Au-tw)vE-1$b8a`*Yx$I4d=QM z{yfbeD6jX{6GB5}1~gUK1U*-EZ_~Tu|Bv^_ejuzoQDE` zNwmkPWGjKH3Q4KEcX*O9(>hcF%1PVJ&w#xaZn>T@Qi-kg-ziiiY-lkSAETD zBpy-=&Ni4rTjUi0R&icleip8PIvlOp@TOXDs(P&lvTy&2eIW2FEF9htZXKSvEtn}} z;@BU=TP9oImt6U#$yCX}G#FKoQ5>XT^`{nI;t($`#k8Ng$iFMZnFiAM&AD>1Z4iI{ z$7PBMAU!18wsQSuf{uL(^3@`|1gy>lmPme|GZe9|PaZ3*>>uF&t|uF`d+YZlfBLZ+ zAh(6|CZg=T4gpJcCcHI&U7q34k~^0f+#B(|w~r__f4+PgwoRBPIHeg+R{xI%jhfR= zS8dPe_F7&6<|4Hq`ZD=%f+cvhr2Y|=3M2M&hDL!1arb4+ z38GLpSy3s?^=R2y$CkUMEOdCpB+tE7=yd6(`>pVz<3CD36GgriOd{CMWCqcc(k8?g zNKejD6VM|(JzfX+7bdszx0Ovd)g$vJW27HL%iU|0i8)^bifv*|z^irgxJBw05PD2D zb!zjrn-3L;5ApWT?A^}$rZmHE@&e}Q%4`*2O;{rX=nH0FxL5f9MN6hW{7ZKWt*GT4 zs-*egLpt7wOdYIDpEH_tiTw2|1`*9zW;)fG)|i?a_}p!_0zyrfBgc*hMc5#JdNGX> zHC4SE2LVfCaUe5LomVqp-E5N);!>t_3yC!n5QK(Pc`YzJ=M~ZXh)TP{W-i+*)nBYq*bjNX@=5aO2j}w zue**L`p)1uP)EjUYxjmR9&vp+wy3+xoK~2KU-+)ak}FD&3(MUXX9e{ zSvJkgS}AnKtZ$6%oK`e5K85J5EJGpe7l#Ce z2c%JZ-S;;CtoIeDH=FZbu6VBTi0wQpic_Q+W86J9~XJ&3(h85r8@riO0%^~^ny_aP*$bqMM{#(IEX$m zm$Kck*!4D8X<{}Jm>oGpy@-&Zy+{v(z>d@S+C&{~eBguI#$K?+t3#Jr0=jV^zCm2p32|LM?I7Kx*!0BRFx3_8(ne_?(6{M)e%uT)lbvZKBSbm(u0D+Jk0$pnxBcNLGn8)v*yb=3neKormVXbx z4`D(^;Q3AtMa3P?6R2s?^#E6u(MGxo{SZ+~oc5qEG5&RjfmpmxV<_<=9V4qKi;;kuKmaL6Pg!ji< zNW^x!)^|O_LjJQpiS6JGZ|lMy^<73ygK!nZ#P`6t=()Iv!axABDw-%kfuNV&6@n@+#mwt7t*z7D67bEkV? zHKG19guZ|ORfGSA)c;RY29YCPN5Kh;mAtb~{(t$+f0_dJps5%t_7^J&|7&ZEpwV!C zah9IX+%-_qZRa`)88FkGxj**@yiNaiHvipR!3H`4Yhp9G_}|;b1Qw6@F#+b>m~R-# z^uM=c0I5LK+nHOsV~oJ^zyFGZ8Z?>|16A9fi@M5SQbz=o1HVjC50UwAG$TQXHdZOt z*lIqV{#czBrM)|(L>Ymp`=z$y7Q`-C{OP`4f5|5#QLq13U z>qy{_22F8N*kS0u#_azFGM$Zvzo|rU(1G`9Em`U3-qe;06p{kUNuD^%>qp486B_}B zvE0nr$+pKUO1qxN6_-_iJQ(*h1#q3p(=GyE8oy*0$s1iq-uLT>{sN#%9q}w{W{pwu00Zn~GK#>)T_>ZG5VSnv+h@{M#{p;so0xiu27eT;|(fuE;&au7DczN56Z5xf* zGvlwr$(yzuo)UZ=Nr3znEjrx#pUg-*Lwa{7ofnS*706 z&l9{^OZ9&X66x~$8lPxBx*}3QT(ZX|MEZAYUD9gRh~XPi0U{h|eTlFheTKvy2tQb? zVVBc)lzejDt62oPF$8l9-xwcB=t~knmebXne_or+F;xmPc7zVw77L)Bmz}6Ky0Q~3 zePT2n(J6_$=_~|YF$eqWlNy@}LB4-yB5)w+#H1v%6AH-BOON`}_6eRiP~kb`s%8mk zqqCam0E0)SqP=kb!&oZ|G+W|PBwT67Z&Ma4iu;96PDCKkR3l(K=cnAk<3Pwj5xJ@@ zOK+2xPKJrVQdsp!wTW&6#1v1txdjX10EErF^6a@u(l&&0-%BwLzks?Rp z;zfxo*Y9=RCgy^`uC3f|1WLv1cHETa!{6ZJx3`dQs8vtP;Au5V`-i8du;&M?C)E-C zs1pB<%_)C^u*YU0Gb&5Fa2#d$!&(szCo^OBGnTWAaX=FD>RVnm15%ZqlUH0_s660p6bj@NOt{5X(vyfStbH2DX=3&3-k9|n z<9YwH6<<0MjPFK9-$1HMgyCxU=h^e7U-gT{^{s5ldOFVjC$7kL+iBG_2>a%Vw6ptN zvH%G-FANR!<;9Gr^x~pa8o^-dFQ}8#ELZkyEMKS^lXqZ-U|_6cuWW>H2>M!SDZjD@ z7?MM06RdEPs$m;`=ZnXge)6W!L~oH`+{XuAhF-rpHeuMth)`5>f1}WjT0UOIoQ=ES zT5gC(f2{O6;Yxb&VZ}*cslCO1O-jcK;&G4J?PR2*ygLQxec`v{&wuhjX$_@7XMvnt zmh%pW7Gn6!?2({IC#tr~qrYDy#bh9b7{ANVA^n^oE>02T`c;pPpuXp`NRcwj%imAt zb)E*3CB+<&qB`yXs^rj(G2MAr7$3ARVs!sSv$}QqEZ0%whdz1aw<@$-?0eP+L%UTM zUHMol6d2U5%`X)2M{@1%Dx3ALKK2UzAzg*dBgHJ^OQdL@$M{G8=qN2 zr;3K^=6Py?V+wj|EuY@RMAEKxf__yFml-c%5}0la@y*~`T5ocR7#QC;2{4RD_Eq`9 zD!juEN0Q0skxn)*9g-2h)jVc^c|)cG*_Txg>@@JAiV^oJX$&|o{?`DpU@%IssW2Nf z3lSu?zEU99^YM*@!b@4`c`5ERntrP=5u=aDvm=3vaP;JFGV&w(M~@M+rchF8PsCJ+ z0}(Ug3=q)<1CGL!*F~HB_Ns`tIrw_4N?itLBuK>j^xtnCTYIh<>az4dDXRZ;Pl$iP zqMhGSd@tc7WOJb*Ur-_JqMTcGnq^kQJ}8f(W;!c2c5eT()M`WWQowVKm7 zuU$t_yycTtH2Q6Lu=Y$Qmmw=fx(NvunpEWx`4fAcd@D7r$|GZluZryN0WGi**=?fO z2GWXJ%h6Ep3c-eYkA+%fdTx#ECtbkq=67mpm&;8vA z&?OY(J9AE7pF@vAOpAv%XDUQe4=dO%n1#gLhDzj#ctU6AK;l|+3oLWeDWmhQih4lo z@_eD<#|~9{e0xOu2RyN`4BR$4r7fCHedVa;aOsRoY960w(RxKaY>J;<7ZjV{k%4R- z>`|89EMl-gklJY34HPn|-vplVp#F7cVU_kRHVYV=;FA;lq&2o+FmK3WYGmN2Q!KTC zX0*Aqz6-ltPO!qXsut#sN8>wO;yz{qT$9MZ%tTc>1$k5dqdW#P?Ph59AJf#g0{5ch z;(y*28>T<#Si1Q2MR%uk%6Qri((h1YPrqNoofn$50vtyqW2ydTxQM}e z?&=yA3g9uDHEhuY_Gk59y*AQ@=gU6jv4sx9?+xB1X*N~Etht_dU_7+O6%5|#Pj_gv zgFfVp$mwn$8C`dq!pK|roAgfj>?gu-15Yoa|DrceX4Sft_78?u`Xw`vr$hhR01YoM zMaSJ#qsXWBGB$c3>g+LhY0LX8gB?*?gHz9L0ZkzRc`Ve@fAcZODD)t}c>i3{baWuM z&COotX?-Mj@^0B_LD`8d@e)c@e56TSg{ zg6{BDz-~Z*(nq#uF>@cBMt__sZ>Gi=0QqatPdf}z-xRw;dYkaq?z8jbmS}}CRXfYU z>uJKp_?#bTKm&2DJol2S|Inkh6#EQbKSv0%QpMr7awTwLhrSf5>6K7W56`MnzpEYA zE@WA5dYR%Bt=h<5WS)h4R1CVjWzpTH7Rs+U6y zt*Dsw9RurNDh!KaCk5hc5jPXvrc`n(HdADMAb4BX+ zOfk{^s~jfazPBRt1@|4Z!TZ}4R=uk6K4=-bL!Tq>_dm4fBL~R^Bv{Gaz=w}=9frs% zZ&Vk&p;PRONX0A}GClW0dOh!?2}OjXB5B2~*StO+a3_4$hn`Z7Juy-}Q3}I};5U5u zMn(r^Y170uHWA-O=SfxaIp~5biRm_iF)@lmf>UmXot!PAu=4=3!0g;uU%7-0|(@Ot6cc|?T9eMa(T7Rn=d$Iu|zl!qgi@Ee-b>c zfF!juCT!Cc(I?$gpS>)T@@OvQ@agb^EIUu}cjIN2SfX)Ni2lZpiN=>m9kHa(u&sHK zM91&GXejt6d!H`+(X2uXG)EPz}jk`&mWSJKk~I8G@WD_g1Lo?mYIJ7S*Z0X zy7O#Tf@&z$-RrskZro7;j$u2-Q!`vj)Skfsx?8$Sx1rOu$TaR*JtanaCWcmsi~$e6^xcRcvqD&qGDybRd>}O;fY*gJ+Nu9#sqptDH>y@RK_D zQih}&+bND@C>3h9+}?~W1s>BwzjxjZ+9c6v$9d5*u=%?mIw8)ed3<3wCUfJL-RLsU zzEI$g{DK@&_ty|1v&5({D0ajU;C-v-Ufnd^=Q|R0WF$tEukHGpfFl31 zBpoPPN>lZ=Woq#mM`!#URR=)QRWEeyT5q{dH7~kih?);?DzsqmZ>|ahZ80O`Hjz6n$PO&g$$Zz;*$5!7rroGG@0=sY(M2DX5-^&_Py^g143uQGSnt zniSmXqv@4SKWFNMX74m}y)_nyawXvPihFpsf84gCncX}MT#W5MuK&g5QC)`8X#>;| zKXE|TOe0yL^S3{*mfSx|MK6TPCL#TJdd2*B#LW_N-c#K<-an9$U(DWVd3Yf#X~_rhj=T6E z?llN_Cf05#=wtTwl=a0?I--;C@fl)fwyE@&$+ zaM_6;Y7WN^Sgn?9z(Q=PR)Ive%|xI=cvw88-Z)wV3tMzjIv_(>nfX#9npN$XeV8<52TVHSW;$X3>;( zSp)uIPrjS~#Yivf|HH`j@`Y8>HvIsZ&nntaFGRgjtB z)#j@PQS~d)vxzJR?R8hKMcobDc|7L-V`unauV77FKi2}7gRuRM_5SPJI`I8)!)qLxSZJ}gCdx&B$ia%x`Z>`LHWabtK&HI1##g^1G&yUVuu z$kGgnN*a+!7vBJQCxK|aQUXeXK^Wc5DpB>Qhu>45F8kjz_q_Wbi2qQT_mi2+MY+hAvAWSE2?%&H5oChm$%;4iwqzcz61-1KP0wedTN zn0Sx9n64VA+1Z;T4Czeh+ISyRs){GLUS0-~MOh0C+a!Q;4@sPgmP1}wCjc`9(qO@{ znlkQpV`mWsu>qYs>m!%Hh0A|UBkVV0JE$X3Z6kMBU75AEuU4bmMM;Kt7)__{1Mb^2 zIng+gPohTQe%`5bcxv*2U~!#s^g^=_B2TIx^pEs?O`X=SldAxxepUZd{@}J>(RdLw zid%{UVFW&%{tFS>`p2Tq<}3TmN$5e&GzO#QlEJPJDR~hE{U-8hOn`b1#?&1+g-Wd? z%(zurU3mv~xak9PghZcOQd zby5%IQUpY2zHo=j&q#}osftJq(YS5TuZKOJ6*Tc`5emRw-6ij-uVP_Qcm~rbii~;3 zIJImdE`)(A9TF9ceKk>r2P%4)I|-c*pR?48FS6#Qr{>GQ=;$;OW1kh0Q62mVpe~7V zunJ`R!cROQKG~!{DZ_5KMs+OSnZX%ydFuIx6z(+11Nn{;Il5rZN|;A3P7gePB1xNi z)<>1mA?ekC#s+&9`3vWI1lYbX?dU>W??+WfGd>a|0bH40_Oa)Xw*M_E46oAeWUw$>ogL% ztXb#%DitH;K4G<;sWw}3sbIJ0MX}eitsJ&|`3B79zsyExs)d()hu*WcbEXbeH0Qb0 zW89yaohI(EsqO2+x?QVWN$t6-UJsFnrZ`PfZ@%M7$ z?|9;vYk0S#snU0PCn>G#pGggX9HLbH;>4$?-l?> zuP(>Cfw+=2DVEZ<-K-5!hfc`RjRhw@<#9mqCZe_2eeo&R?nR=x=G1Se-Jet%5z$xX z4?(CF!^;kZWMTd{yi>p>Pp;f>$pi=W-OT&ym6r25 znZcY~ODzaq@i9VGP_f9A!n3HO_#aJ!f_QUym&5||sU7>j>~TaFOiBy$l+Wi3sT|VPD?kTKq`m`_uzdByPZRj#3?^y_izS2JA(AGT@)sKA@AhUjc z?`gQ}7ON^}QHd*iFKQm&j@ppPPs~?_hwOZ1_cdi(C3ZQ$Sm~S%O9z8T3N?*Uv@^MsLq&NZ1Adcyf zu=f~Ys)Mff?Ia_bAfa|d+P1z?S$i@Gv;stSq{O*EnvwOa}am}Gv$Y$$g2`@dN-&b>O_9nds@9JCl zTy19bIvd$M)y4FzY1y!XhGzXdm%kLrY^3YJFHV0Le`~)u6NQ_PR!Y^e*T9d-y`lEJ zz5z+I%&fWP7k2QAw@wc0t3t)6tS0PfOl&a*n}zHWXTG|EU+#&Xl;V=R_cO`0$Gp z)7cqPxv41e(BqJFz82knBs~R1+^d(joU8*Syn}sW0Lvc2BQEkKYHPEN%B>VEs9Fn= zmx2$JW6QKhfnVi|w5k`>B0ZR@g)2g5{85~i@Aw%sPdNTmCjU_02aUnyj$`EJLP6vt zkusUXMBki2&gUxjg=C=og!uHgzc&#iYw1-$M3!ijThmm@@+p5-;bR%wbl5cP~VtXN?1Qmr!1jW{wcxOJlYIBT8(af5M3#9MVq90v8bZ)uO% zLz^Dqm$&q5(Jl-f6n+*83w)MT@{uR+-iEIZzKc%QtGf;niguw2G=bSK*L{Z~d&u}o z6JBVB*Et*{^bmI3t?X6p4jKv7b%f6hZKJYuDXioQObk%|>?d=mrbE|hX}czokr>5s zBRn9A$MidN?#>sLz*NYuf-Zz-4a7uxJ<6@>b0gLTcR*gkcw5 zD_nYUPsFZeX*lduOOUaIX|pXP{!B@G6H7*e+5O2}*N=+zKN*{t{4=kH(_+>9q^>@& zRhiHU0PA^gU6N0s5Oz=r@o_pYKkpzv^QORpC161IN?t~>A>F;t-!{lo#%XT|A2o0h zWtK_9Q;a&3wi4Dbh-ovU5PEphkVTWbd5ZpME87>*9Gm}qhf)rxI1%62=XWWiKg2<5 z*6&kFs&&%e7V)0TU$+!_FcjdI06K)J{Iods_bgN@d%i!f_-GMgUVtU4=ti|}Hi@x; zd2JTYvM@#ul%MZhs+-^W(=m)R)hkWqQ7{-ljUS?HjoM0e7$i^_iw!W6}*S!dB+hpPIh?z-ED!C)%5n zWT3(;_p>@__Zb9t1r7%&l^xN~n@-*N4O?vjh{m$R^d!k#hCK=Hi;t?09EfdwrjDng z?*gW)A%sB`oU)Y8IK#3k*Z>L6>y zitVB%Y*9OT;BUR?aiEs0J9AkB%Ajr|hNilv=4?f!D6(2KVijq2WVl!&1-6YutvKo= z0&|>}|Lo0-PntGlc6yw6Zq+NBA~`t|_MO7?A=Bm)K`5gALW3hE*(A8=YhS$=M2Ecn z>4LRtXK9g0K1Z58h6u(6cmcf9rgV69)UdU1(?`>>EKi>}3aRPB8aUQ`mPWoeYpB(% zW}`)fe@!sF*{kbm{cT@|%@Y$Tkq-)XLdKsvE>o0u5)wDP9pC-$(9>JU6)s0C<@~WP zbh|qBDfpzMzKPu#E0+IxEP9ZD8Q1TcnU|Sbq6p?4+=y8;D1OfKlpmJ$v=?fMzarp9 z(sj$?Fi9D1VgVDkuSdUIvPk4o#`7?(LJFA%@O{+G9X(Jaf)y=-pFPWW?% zv8lJ85N$zx|3b*zF?X(~>laM^j_1OJ2_gG{)I1e5`!~$(s?Q@{$xFdes$(HScg5*H z1tz|ohj#vmjteGYY@qNy-tmJ?moP#|+ks!omU4^t0U`9c;f1hI4L5{Ic-Ur znJafl_>=*y_2_fg-U*gkI-c^RCNF)7`i(Uf)0&Dk8-gMVo8 z>KoSy=Tt&Qt4cEqu}zU2bMEdC59P$GK0K{K_K zZ|Mc9@eGE~Gzw=h2hA?4k}nZ~0Ah;YgS1ghW1hf%QpuE6ntRCvDLW{_BoifMUFoRJJnGLnwHYCz2+ z1$hCsq@5tgDTL7UYAu&b8mPx$7Lbt18nk^fFv`m2)WF7($QsfH+(-1#us5S{4!ZF% zxMVL!js8`;@}+rE%MwQ`V=6}*x)Oc6XLiZgqpmZvkvO$6x6dMq{%XkPtb+tPXaog{ zUj4|l;ilD2oBqkBEe%t|^?e`je;@bzvFeqo;tw zYnihP$`p*24=g(PN$0p5@I{*f-N^AwbxPjqV?)aH>`TNK4%=iZKzMhEkxA#x7qG}; z;+##Vx(;G>2c~w28Oljld_{`sW zX=w*-Q?j5?q2MU7kfB$6O~@r2m|dlX#3n#YzLX;83~qI_N};S%_4t0fNFLc!`Tcd? zDi*GIij4l38hR(0WJu>G1J8Rdc&%p<-- zl9e{eVB9t{R5uod#_BnGf-+6G)aYo6kW0sU%5~&RF?Xl4f znb>@c@s|=Ah$7NDtMBn}@~&B?0wl3g#x$v+LmE*Lz$^6jmFPy&t-`h?nB3+cs0;xw zM@L4-`?q)YnAO?4FxV59;c{Y_YU}%>_az(UY9gOcY>7_V^R4q#5cIP7Bp6tDFf4Yp z`jr-5k|lbw&2l_;jZYz^ur%S)8h({zvQzy`Iw`I<8{4*YIP5ldOQ@;OO@{o8i9Ljn z+;g~_e+E-X8))O5lWNM49c$_sF^cv$RlV@HF<6@R({sIdCbmoNIc_TR~1VH-B{#b=0|t+|iR{Fm}iB}rsJdB9JK z=Y~LRLo&z&K`lMh^~Qn8&?zD+zU*00pP>n{UP5r-PBy%I0nd`9GPCEqBILqTSIOR! z@Z9bG>LM*NF+8eBoAbP%3U}iPOjjEDEC!=;0Z=iYB-3-fYGoP;2XDa#$q{x!(t*c& z?D%gris`%4Z6IG@pl+vi%XYAWTdfU1Zbx4UY%~lC;Px1Oa%x5Y+mPPbR=|Ya+5-!S z{)DjckyH7Vq@)=7KV!GK^V;J^BO?Y=R>cuz8_fj5W?I%KWyFRj0Ld@?O1b#!aeHqM zmvOD;($0RB;xX=v#7KbB!0<*Pi7k$#!#6m1vv0zgLEm+=zKz#} z%poD#Nm%pypc|pyz-x$f>jZR12TXCjA3t@l{D>xNJ!$uDao>B-jZPZgX3YJzcBJ=8 z0EEMVv8Ge+n3`o&6ueO>TBdYeXknZPJS}c_Rs=BW`#f3!FcQMO>#PAlfxvw5Yq`|6 zj~n25SyY3VVeRZ_ef~SS{)&; zP+eVl_IopTV;Pd4KXmQt2Xp|Z-S(d)CHu6>JNpdTn2Bu-2}_R`Df=r}b&tTyi-HV%FmcY;e-|{le}gYzXX_p@Jriv?(Pqjelu3B_RjaMUM7T41%}uzcGk zV%5Ag%jK=Xe)L|+sDBio4mLca5+h}#X*R}$u1~c6YcBr|fwx3f9ZlOppYRUiO)LKa!FP4~659tRPOrHv<&O3hl~@3;Stb7LLVH z={n%7%iw^wa|h8`_nK&H>Woa9_^uu3=1w?5QVGQPmh z&xz7-gY*P|uMwNo-^YPQf%K|>Z9m(4=s&O>b89VpGcP97Ob^Ucn7i?Neq-h8{6Od~ zOq9guiMtJW0QIXu&LIV(`Sr@ASVi8r6K<24f*;{*Hz#o>#`gp%YgNhG&66#c>59iX zuD!vGuj6>VS1CM=mx*+rC!rReaNbwXNepWSlv-)~kZzCm z$iP;Dz6&p!p;wBs0cKKf3tq|Bn(v2+#_VBB z6P?gV0)dsO{K!NC<DE2 zw6S%3kaG$sq@k)ul1(2ORi!veSP6kFe-_aivi!0NQJw~=x$R(`V?a^>L>KoR zvBM!1?)RK0R+}`9zw;N=2GBi9nVm@UPox;UEmkITLnJRRQ_0Nid`wMwn6@#>DN%|c z2XPeXN+Rq$j^jDz5kBCd{X;iKgGLWhXPG(AY?maUN=-Q%S?EZsWUbm1qYV^CO`H8} z+17z}@2r~K4J4xBhi?6*#^O(tEX$kZ*o*w=*-hDioHl@nF%VYbgVKOxt)mdvTJ}KN zf)7-!#T6m(D=HANnoT*|wXzM+L!pshRSMPMmL|T}s*M5ZGi3-cL_6Xb5+M#4vg)x) zFa9Q+a2&G1wn4Dt-!J_5QjC`4+v*A@Aa)FxeLJpdcGP+cg_=V;q)^exGK*`R z8rmzq!tq2bJ9=)D8SeJeJzISH-_21cJQ6ACBtqhM_ z&H|D7$5Mb}5B1)T9`nFj6}4ecELV-}xzPLz6i=Z^u??I4Zz%5YtFZ+Op%wI-$0SYB z1(GV!0qnemdlAFoKYhH)NsV*`<(#akuD&_Jkp%w`8Yvu;g^`Z|KnLs0efpqWF!9b6k*4%Quc1gG|1c)=``D0v(0!p`K2gCIrto||>8myMI>^efX{&>mNqUKXl zHuA;H*`%{u`vntanhK5%NK!B8dZa>7;D{&Y7PjW9H$@`{hUnXqB>)fxB-^IcQ zVpyVIQ{qi}$adT}qr+l5wwnd@0)nBKNvF1zF(CWIL5%{hY-2UK&mIEQweCOXzY%f+ z2F1j>zV^wt>vhFvwap5{v@NLY)yv5Z^-8qm1vVm%Gb)LK!ZU}1_FEL~B}W{*Supmn zFjNmyqdbpR42Y5$2D3KAd3=2h2Mv6(S)DsRa$3pJ0ag%nBT2U%XNN_Kx-^3eB6LTB zu-0qf1$nUZ4mB1X8U=DAHqf=L~ldlevq&IE(o#1)_aj3SyY zzathnFf<*P67kp{DqY2Y%AXc%u4?Be@IzZs06qf&X;`p!=b4Im@A-N#B!A9S95xqJ zDR|mY$zOq4gI6DLsUGkMHy9+57yIzQ>vM|}bNO?%0ow_^xpgE{^!bRj?W0iZ(QHs6 zMKiJi3S;k*ZT*hR`}N%Iek55lH*3G(#+T;frAm8Ob=Sil5n@Gp#J9-%*nX-q`(#+= zbnL+bRD>pFzydli2fDcxGKin~uVCt!I;E5wPG=nn`>Y{r)sjOJk)RGJvzkjO)G;(k z02Q|^PsQ~(*4@STB0NOqdM(g3M%O?!DBntn;yp=vkn-Kj{FAoN_#tz;NzkV@0-Nqwl$%Ra zE9!GktiG_ue!B6NGv!AgWTxaSnji92Q)weW_8kFa;;?=~nsTVyh#hTwNq3V1!#m0- zmC-M}i%b62^Ty4AL&ovLfOge~2u%l{$z^aveq3dj4UCw_)wYLPZmaD)|GJjTJgr4T zW)NSSr-RbJd9`_vD1(^g0o)wiu?jR1iO6%hoIpZhJj@gXf)=-%F>3%6VLA5JeSm1% z)UqRt>7dibo1z~`OjWgmTI5Ubjo?ARb??O)b_@q!2r*f-f|_znPT}Xzi#`Y}{fruF zDakZEFsHA^v6ma$sCKWosHeUg0mAP7C<tPJw1eJ`HrR33|Jzt$?uC~6>K63Md@&yT9Ni1sI z+`Sk8IiK7+-Kw2*%B0#zG8?(j#28!X4QvL-{8Rv`jAeN?<% z*RH7W5PEevOcv$`(TVC=Na;GKrObS(EpE50goMs%#BQ`IR;(A4 z2-y`K%>?{Do#hA83srv&p^Equg-Q0y-RM)|{HDV=ExQ9XQKDGu4nEkkHrCCEm2*bu zY1MnBfe^>hZqm$FmvlbW@`6x&N+Q_5a6u!m$FH;f4Sz^!r6k<>o+WZ9(jr%hg~0K5 zmpkSadgLdjFkYMcto@c!?60VG^TLK;_-1T&o<^ZSA&*wu-vRo6CVz=|&$LPQhFy1Z zTK_4BF#995kG}EStFJ6gDBv#y%()AOT!`u4@iAEUU_*XE*lh$M5A*GcF;!hAn>J)Z z^ZrEvny~Lw+>$-3Fb0}m7}5v6zv)G-dJ3XfHlaHci27d;z7bSoQ``nzM&ULk1$|v< zs}S0p)W3ZHMkYH>GK=gIx|6y*KFDwIT*yoXl_nlCu$Zqb}l)slRw z`CcrUkkjqaLp%UCVRjQM=wOhK@8$HhsKr;|paPrX>g4&21DbW&T}Aw2WS8yhQ=3Yi ziLqL?=V`hi*kgzO`$DcS!rZ}*XizUAOuD@+L>VJmk)#gaZ~H62*)3~sX=@Qz;cHI^ zqL5QFoWix$f46!;t{3SO6z~$w$9ezmgf*_}_cJ25csj!3{Vu7Yccb;*q7gB7Yk$8n7Aw+HOBL?#-Isj_PlWZ#CJl+TX;^ z!)(zL7#milEBdEPn0DK;akGeO`Am(#-u7RsJVY!gqiJy?INoBz8?R(#eBX&qH4=OM z&l%&Gk3W~E|0Cf%p@DR-%>$Q|oeL{i2)eNIv`@ovd0`BqNV@uOqe1S!Oa0Ga4!##)$fGBs7?HthBYA|#yuBfB-2z~f+dS-c-a$15&|ygw>rI{DWW`%&JefFZPAckgV>Mge=ec0H@`VQWV9Z8R z117DLgb|hQ16Po|Y~Can(=ke$p}53@1Wsf|@1pfB&r!z(q-S3wF?2;HJKrs~d0hiL-7tM=Q zDmn7_o5#9r9$rmHxE8Y={-t)&-_*ufjHEt;XBZ=(@I++S*~$julk{HdqB2BgFGXHn z-YC-ZBnYsc{Pz?^h`6w5if0W zwkwQ0FXD2O=k&koDVZj=3|(a5EWf!Ce|DGm&2E;=y8#j`PPNtxZD9%~w9{=ep+f0e zJAcR)WmbdO(gtJ&JMUz}I@&iO{55?*0j|~*r8qXCPlo8qE;fJGYE69RR5&Rj&%Kr8 zlH%*6y81+tqeE>QBbUdtl_R5)96>W@K64m`2*<+T#(8dLh_!q>1`(brV%x=KtTatj zmcJIhp9p^c*#6IJx8N+JtEhuJ$Lmf6u_FH1P_k^Qk>Jk<8YkVfxj#j`(K#_4;;}ss z|0H_9_gpk*ME+6{evM65IqG;8&S3_@?!sl%0XcL~tkd$zR+5dyVfpShV*k2gdwTQ4qf84OV)0L`VYnqzVEpZLu zN40D<_BGiSwD;q#X(HHKdx4=%=lwSy?FN=$xI{(f2IJ-Tf&EIys{mY*tlN`~x95Z5 zB1GtCl))YaQqUPnjCErql)RliaO?1{OBjaI_Wd4+Ets&UnZ^2$Wm*2>q|wDi9=Z)r| z5ZJ@w*#~W{n>C$e9Jz3MJP)kug=h-7FXu!XD#q#PH^LANs<{k{pB}&$ZN4XFY#$ z*k|eKsbRW5n4w)wQowm+drYd58*DvU_130~?ozRiK+-PGS1)jQR~AEeF%SzcFWv+t)U#}%Yn{re z(RPI5A0D4pHUnq5B~sV|L`8DAw1JgPgf^1n5P6N>CvFt}_3T zW!P%C;%E$I!tsSfH(6Xc8m9)qD+yO8AGU0{`@-*7y#!Gc!5UOU0Js*EUH%2 zi57SuFF*t{K-;d_2t3V%Z$-e?jvCzj;M&fP4|d9N7*yuPcEB3-X!ofmBk^W5%=Q3> zJeFz`Man4pQwdjUm#qvzu=Zi`a{6GVHo)6*$>wAzNm?k6-|a`x9qb}C3O*eF z%HT6yyA}9jX}@S>-NBqU@-Sna#QJZdq9ZtP?!R<&o9*gn$@{H$peU&D{^kjp<22lp z$Q8k8gi~VZZey=mjgW9NF@j9Yd`I_Y!P1mu;n;&kxTSi9hnXPi| z#uIXc+e5Qt*~-RYp0wTB@oeVtZ^{u3*}d(QHh9)87WdU7Ta;AXfu<*$GL$-N1+vv} z7+FdK7-S7Q@;>VcpK&dpkQ^Oot%zngO#okl+qnbYq8Rb-ga zX6JH}Z0B2{QmVB-NybWaL3yv@Vn%(x|5B^Wy5D|q7rfwQ^mg=zc3qjN-&V1#)Nlm8 z>%8{}ki|5h+l_%Qwx5&N96X9JMifq=`#D})}w20ND6si>nMM5nOM>IEsSDOJ_vG1WhY8kO@ zD2ICCVXxK7FRgyM%y$)2pQR8vXCh3H)Ww)w*9Is`=Z{mAHAeIG zvpRkCg(#QPwSR0IJIRq``(>4ab^Spl2x4L*HNxt57-ij=QKQS+k7PB6%W<2wM}Ph* zOC3Z8fpWRKWxiMc*n4v!gd|rjZtj~e?=CvE8uf{elw0Qubb8SQe-wV-UelH`3UFQ( z3SfK8>XPlP;xP)@@+qxlxf{a__{TkN9rsE6L_l>bNepekqYeXvz)oo+(ez`*uO#c* zzK0w5o!03|kojSBOJTMvEyh@=Tp7HR&mP{he>{KKy7R9d=`KgtT;70aS(~AJDnK)I z`U<8O?=Wotz3q&5XGxAKt1kP&Nv;h)w7)k`CkQA0Z|OAlZ|NjT zQ@QqxM)~Web`>ZUhuLqf0e(HJik87K?GB+Q8^E2u1pwceo46N2$0#lJL}&Ajc8Y}J z_caZ+pq85)dx+;b{GoXCqQeyvUM#J%!f49Fi%Ii}nMI>khn*+C@EVv~#t!Y(LnQIb zW-Q`)2mJH9NM%H4XilWZ8xu$umTj-e)XT(yZy#>$F z-EQ@>$ixS6a5N~e65r$BLqjshx+ZHcw$T3Zc62^02?JX&tssKOy)B) zgc?Oo=KkbY`9Yp~ZOB%gfPlSv1@KJDHWZ_Yh@jn0^K4&!f+$XBhvU(PtO2M-S2^3xrEj>-*Qq z$BfFxYqiJiCR5%A`v1@v`NITSuc^6<~Uzl(QqswTax_ zzH!%iviZT6G6O{B8=kB)mBlW4EW|d3mYE`wrO=$EY)+$!L5FM3g%+?lBSmWUHAQ;O z`-Lq<{A*h7_fr~qbO^|(W7+aZeo!T-=er8&<^pt%R`u3Qz#tet%YbGY52RXU&*@U| zLq{k#c0|;a7nTexp_~FiT0Ae^i$IqEiI$fr%Eums%ps4NQjcL3kr5?Zofb>Y`}Hi| z<9JhCwM46R-pYVnITToXiMXoQ`eVcBZ?3&-1)9 zvu0+^`_Elf>)yJz&aUr1XYW&cf6$PI0^E6*d~8|=YYlmFMt(IU(PMoJ7v=7f-JE$r zA6t3tNL6E{dO@_dgnSODEJdJzO6b6q2nig7Ce4v~r~J|MVq<>&Zs7{`E(Lm1wQ?ej z#~dVVEW;b;Rr*l-9k0x2sA0E>u*_3T*i(K`w^-=+*>@a1oI1hZ0<025d8~&PHvhH4 zNa3wN-1A>0u}O^U+3@wZ8hibu@X52Y9Ek8zSfK3Dg7t)+3Wq`uIN@Lbw+L_Wro`s`s4NR_Gb&CsNYjt$;y_fnTQ=wexx$+AvyvnCwx6;nn-^R`84G zka(~19!$~SW9esnaRSg$B9GgxPg2d~bdIUEcduKIs1f5l(JmQBEZrRAmWws-scPc_ z4vFfAl<|2Uhf_$~{_1~$94|2X!Nsr5VonkM1eS?jZeNueu{w2Ig}2yKL4gri6BZg( zH~voakR*G{OcF#T80QeA>pakwFogj+@8T6YfS1p2sTF05V%Sy`W%*of*HwvWqJK-Q zq`oU&e25ML-bM(>PJdvABc{?BqtvphgO3w;inl@(zev<1?JmL&kBp=fAF+HpWf{R# zQppmkd}(geducEg{w0hH>bIyR$Luud*`tibX+|HHRfaXPd2@xDXv}wi%UMJkY#7XC zG3+sD>SQP9ei8ILS}%$yjky6a`^K8VyHWMWxAXcp+DB!M!!77JdG0!&!ueU^_xx-* zG5gN_?KsO+c#9smx08PMr;k%z-Ot@ei1P7X9{IBy^G$@4%Yomv(IU9}4D)v_A!H?|I=kbJZqi zKB{jJlE(B$))2GTAUUVMskU?I&5ac^e6xkBgax5k#B-U^k+?rQbw2M;?d)|tm(O~~ z_`&l~s8mZ4SseodB4u|VgIQ_ceY>%hWAal>H1(opr3+r+J8pS!E&UP5&4ONQPFOAwY8JP5#$rJ=b!snsI zqku`IuL(SJS!j*&iJ+oySJ}v;kPu5lbJeR#tpcM6HcB9fq}b>Oi|!p_pc+4ec|F9t zztgFYrf5|e_iiY7_auImr3}hcCMp#(XOYn(TKi;PLvfC1^JOG|8if1Xe}h;*=8;?6 z4E#~ZO3mkxj=ez(ok)2IJLMU%b9jwg_4@F`a`^@6tu|PD&eIRC87-AkaDu3%wTOHt zGL3H}2r*R#L?vAyFW>^J^ns8*4R0xc^>g>8xpTO0wSE>D;?xRxYM0(n8Sd181aiS> zSGyI}L(_Mr?ix{!lR&~xgCW2(mS`nXIWHC5v9J}lD>_T#y$^`5GP zBIS)~?}W&d6zsjWn|f8F1Tljy*NBJ{ZJEYc+d+X+eJEb1GTmv+*Oq-x-b<~DcS5iX zf)2FUXlF|odF$`rS>fquq=gYui_DO&Bn2aJ9tlp4!Nc-5=3mh}buQJk!JKACt$hFZ zlOvAUvV7aCx7QB`Zb=CUE{ri5+|m*?AhiB~jN*EAkF^#8@u%4d-BorV8DB~6JaPjmDUbaXl0W1+>0 z6#?yqScS-u0*Y9Gk1Z7M>+~pp)oGM|zt0MwTzMgapH8Uv+#_4dt!SEQuZ3-tS~Bn) zWedb2Yaug8tHN6D{p|oh(-)fZ_wlWe!Vw@yXtQnJ`E{C-H!#ntP*`xLx66gq$Eyai zPNbrlLepjjvBsY8W^DMG2zNUiHvrj2TdG({^6p_rSOo>!DQOG2bZQZd9wy z^%Ryro;Rq30LEidzA@)xGuPou*r#t((lFV1+7|lsB8~prQ&cI*&*x2V($qkLsYs!k0B7e(SfAnfK=St^ zi>;<=s$aPueR1YDA%0H@xq(;}?q%#YK52N>?{##Aa{*1tr5C3*K7dL5%w-f+iL1Ro#fRm#vp!VN631~a@tWx2VQfFR$a*-eoe#4aku zMx{H+ndW({bKNGTt^^$#HvmjMb07oS3)>>*2{=GVee?aNHe~6(mjU>Rc$EcSA2u73Vx2FyQRRLlX-`BfVHxU`6mqmWBUuv=^?w-QZi$3 zBQgC{{Y!1=6==@9wh|R9h4)@5`P-TVH$vpPM%~U!t8Mu83V`B$Ive}zTq}it5ORs zaff$vJow$*Ef(hWAsE5Z(Xm%5J3PiSohe6d z@;i)rYo4s^*I9DklUHWD5Ndork<}8fl%Eba7_hKs0V6{jQwib@JT@!hQe+7vtEs6S zeivdYb!MA$B7O<>UJkI0RYOy4vjMeR9kkXQf#3Uo;%J{>}*Hr`K z?wgpR!&A=o^K2!`tp~d90gWk_)jU+p&7N3(QjbWtB?+X1KT3*B-8sDwU8%YTSycMY zlx3qfjDlN-R;;R$?U^ZCIcq&;6@MWeM1#Rm!^%&(8ogChmTxL+f6_e=Q|!FldyF#d zs|YjB1pKMRW>-`eqaY&aeE-C4MneT)E3gIQb~J|h zhMg**N5j?okwb>ikv3-~4;3rFu-(lRyfh#zEv@mc6f9Qn*kPTX1)DIw#lZCLj3f18 zT-mJtoQu1;NAn%4EY8;S+c~#Zcr2I)PZWAhst4s1{2jbz?BQui-*QKa+pt<{>wo$7 zGQuMZFTKGeuH+yP=6sZyVQalqoxO7ZP(rI{%G|=S_{c*U_Z5= z9WSPcKZsTNyc8wwdFfV+)Dlx5#vI)A4KwIRj221I$iB}o=wV(~EVGT?#R9w$>$W2^ zePIyw2B#gkv9iZ#=$Eseoi#p?A%L9}mDfOnD=IKZIzx25$ev=T_UkLB&2M4{sEU}3 zXe$YH)qNa(aanN0UK3=s8V@xQ46)Y7l^9h=_OSZl-+NvT6&(2v4;0B+%NK&qgqc6r z+y4U5W}wJ4a2hpnbap#xo~?c?A82w}h(j7KUl_2{fP3{W^Liw4P#v5@S7276<&at= z1Sc^6j$V+(yBelXo+cJ@A}GauDUM6LY~{lC{CK}?aZBtvpYYmxaqoJkt~BkHs^NI* z19q!H9wy;%WXQewG5X6&o8#$7M+g84EI)TYZ~C1;g>bR(^!c6OyoSljDy<83ia@e;)Fj+TP;-_VsvDqxP2VW?Q2vm#3-Jr;4%3=Eo^tD2e1d=oqRLTgoRsdja1qDC#~gh~#I z7%@S<`U3sFT$$7p0hFMl7S?0+@D>i(@(rOi>yDiaM*+z$&h3N+S2wN-<)NrU6?DU> z)4eQc1vH0Zd8W6aq1wt)JU^Pk+ErdfI~U*FmsI=xV}z|`RLw{wM#LO;Sz*wHNKpkg zBwfpX7+!(VU~IZ-QyVS*?gso&tKxs3869Ak+9Y;H(oAgjj`OJ73va&E&|nd0N_?3;A2kB2=64}RkuSEb2pU+2 zLY1fM=4Qu`3M<#d&KvAHaK3&=_!N*0Psm4`vi!3I?Ls#%X^Fei!tz{=0)!n3n7v`< z2?t?q37v^vsW*OK&Z{wD;To|1{k@DOr>`ikt@u9VQ`TOGFJp)(U8MU;z%t{Z`~6)< z=?urd15Vr4_ci1cX(7eK1|IMW&$(3amf$0*pya&<5#7J!dC=UDW=f^|)f|Z~^{97( z@8{D#Tr2Ih$d+J@QYPJ^mJd`)n3!P6=Hr|^;Ih!NkTJwxG8VSIB!y%}T)@W*w~{%0 z9*5=UvGHLYA<`BXh&bv+xY;uC_-=IO!B2F;A!`svLRUgXfk+PqpLIA&xxSrB0_6At zJuAau0ufFMrS*lE-Ni{6?}U7xCM8sP166BBUvb%Be()qQ*5GN7X&OCn`x~F!MlZV# zUPFUxXDJG3QjWz+F=?DGmQ^`6$T|?svWE7YT$2alBJ7Fxr6}D~uxWm9Ys+&73COM| z&67@~x}~+zs?1zCLyTgb*LC$-I5Mj0iZF%UOC_uF7FinH%We z1Y{T<1NLTEZZW-;uEInrV2U_?`7i60;B4?bn5HvCzuHzEv|KB5$iSd#P|4*^RvVam zk2E8AFn!-HXOpz+3HE*+Rc_( zT?+tQhuhSn1*VA$yr&q2baIA-xL&>JaxXM{S=*+{kmQL!H)>X#44_EOlbcL$Yv#L5 zkZaMreq!=0hB3+@_Z=Aw)oBzX5-r2V-F^(V-l3!CwCuH?RY(g>UCaBZXak&V7QOGn z7AI%hL`9~;(8tf;W@3SPJz-d#E>;*HyEuLdr~B19o-(2nA2%Y*Cc%J<7jMCiMzZ44 z+SEI;*MNUOM>r?7HQ2~6F4+HSTS=pd{{b3LN0EWkB>(B8gn3y0D)N8&XI_i=B%|N% zv~8A}D06M7`@(j}>%DGU@GmeYRvib+{O~^vvs#J0|L}=m2ZlDE&FBWrQ!kCx*D>{04njuUP@w+nXvjdP;ZjIlZn^FQNcaWUAu6W&@A#rV7F zy=delkH0KFfA;-O9JB95w|2q9JS|5m?H|f*WRI-%KV}EFQj-nsfdO}0rYZRwsH>rI zWyy@Yg$3=ZsUH1>|Ea;%XSzpz`3s&sYw(wl@CH8Y9KnoXX^C&0$(@f)+IX2R*{yke z=nGrD)WkS{vJ#Fkg74h=O{rXcR!8|z`~}9*v-efHU}A!F^cQPk@;?WIkF0^$!?yk= zRPxr?FaB2;mI486Gq?0p_Y51MsLs+zO#B~+k+&Y-<@T{6HSvx3koWHCz4GzwK`or6 ztGbeaz#=^f!e6B9_L~LN?wvQGU`^7WpC>RmLlBWKW8_faEvMqS1=pF7#-~zn(-y8Wyj*(-x=md0jTVFHVN(~Wb^&{9nqZssGI#V zIgSoLk^XTad4!Tt6-obBpE=54)%9>$W3+s$@{iPeaWFUk^Syf@kn(RT*3xk_3H%4G zyT$!!4;K&>+0*4UaOawo(&Le5MXx1lbie*^bPas9kpZth(2gI>mI z7B(P|QPgJoW%nO`Gh9HSaNd79pn_dK6#Onk2893Zgn!rQ&kx~Tw>YZdzqct?er;F| zxi3ZX8Cd9WQ2=5ic!6eghW_QogMbvU(W`&{D(inysmB4;6%YI$B6g*E2M(S=lNUz| z^N+t+`)wqHhaHEd$qjEQC!yak;}ib{&EDcA-da*CttOcj-beYDqq#v_fy*e0=rW{8 zw{7@8d0|=(@%3->S~2XV6FJx+IoQlqi=6&YrwzNrcIB{7g0C*-M;H=*PhC&2PC?mB zYDWr3{M|T+c>!#y5sn2TmW6%Ai_v`oBUnBod*!qzJ(f@_5?`akBAEqU~TKo1u z5%6?EH&#cqI}9=IHR+&1;^mh9$1Zuo{ubIE6GCQ-PMwmr0u_o^l^J^wM5yTbKA~ST zGDF2Wd#aew*Uk+83niW>?q90|1h^oZ4c%cLFwRwhqJq#PWS%I7flXXj`Hs# zVq8-ruxouT5%B#$tkG?6I@M9cSx7WMkAT>Lr35@+{kv6*4wQ!%JyPKM9q`3~Q^)GM zzK;dmE%b}rD(K`xX6>GSP>P@1|8UF=1fZrgs^1+v#_bK8Uek*EJ&d7n!9X#~V>%^G zxsvU&{NR>R=JH*_I)VQi$oMBHTq$}={O*eQ7d;k8^cV3jB>u=fPzK=Be4rY$_?DQcq-17JXi`o^AYN4OfYpXdeqj%q zeMuhNq{dAs+>DX|3B}jn3V%XkkZ8eZs4FR#{e((dzdbOZNd(4u&hm7Z-#u!En%BoS ztG+pC{r}sSmx%t71CQ@`Xf*S8%zQ#$tXKGs!&@?mN}LgWpQN~QeQU1dD)N0Uj)4tY zr{9ACj_Sc<{eM0>AwgJMvYw=)_yl$G#;^MtA;J@;BfBM}3}Q#eE2XR~pGTP}7Vhw- zLk@&b#SS$!l8J#P40~}mWoQ>&PPIJVS~X$kQR$L1sP8YnHIi3~v|>8O7ixqiq0j;V ziNm67+|?EuX=1r0m$P=8f{Kh7o(&OJe)|PDUNLTbL4c&fbjA%UPYW_J*TVu4wwMcV=bs8oo#`xD9TjxxnOF;52usrr@UnnFG<9Gl zlf;fJaH56gBrJrj$hmiL2o%Eo_~A(aaE>hp`XK8_(9vwyc5TASd1=Js30muR3Q3iB zB&N&`h?)j}MT6F#(0wJ$oC+-iIK|Sx4QvzFBJ#F^+#$%%?vRwhI|yw#55v#l=_f!+ zK};4vDr-J(Z+djo$~0eNR0>bh%TT=9yRzAoJANH8+^9M;1(Md+u<83r&o-FriQi9P zr{IcgI)`bg&f~%#DmVyIDa*>Y-ulw$mgsecPHmk{U$2k0xeX{mQf1yw;TK4(=-u?= zHe`N+?rs@&fZ*1;*ys2JE%?6@Q!0Bst^KhMV)-w*3~>V+Al>2RSo3i&HNoS(dC)A| zz|AoytE>y>4R>B>cWFfzF+na!n63q4qoeK682X`8R)H*2F#t}HLD);P0~=m z#gmpHm!pLgE?mvG2CKq)_^LJ**LgapKt$&yLR^fhJDk*Q~~o<{Jb zqepomkz-9vSw_D0ipy*;;KzV z#jb^J#CZ8SptJuqDoCV|R7@%z_l^OVwA^8;Yyy|HtuYP|E~R2dkl_0jYPPm>uPi9} zH`g6vVr1uD7?%adC&XZHzXup6ZjsKoqmGX1Q{{ZmFO;oNCAjo9yYoJ&UIC5nj?ACFP0l=&KrY*1T z_>$?79}U6amS%s?#fa4PmX)~8k^*+_&)b=V& zOZ>9nGEPF_Ld2e4*yEucY*wlM_fReaHE5nKd6y^}Rjs7l{ywP$G^1yFIb(E0g*gm?i-Jyd=_x7pDsCT`q_C`<~NV!Ti-n%p@$^)!~ zNCaK~HF+7>BVB1$jG)l0gyY_)pDgw)yc$n-6|i~JSAURCcJIHUvmXmM1Q4aiskQ1jq+{#fS=3U^+J&n6+%J@6*@^?gWSUN+B(&H>tHhBInr!i3v z&uML!B3^8r*&Q9lAsWyoxjVWibFPIZNH086^!W2~`}FK2tq>9~(;oJ*)zH8C zFEP&ooqc_?-or8Fn8`Qu*8L^9@pU1HO3|XI#16oXB~Q#;6X-fn!)r{1q4=$}K#3&g z^1h$enG}z&>OMmKDExl1u4Qc?FTj=Wz`%7vXzCnzbiDa*3fo4TIbS!x<*nrK?-v>! zz&#ug4B5SvR$fNK@~9AV0DSKtp)4HAWBqntE=FH%tbj~Kfe}?#z?oCGq22X$rLFAo z`&GFzEOy{o^-W1+0F3@XdmA4D^@6xPSY4|d&W`tX+JTef4oXwyAJ;at*1Fz+Ncqhr zx-bB;u<%|oziY)8guUb@RPdafg*+}4XMz4ffLbK7xlY0VMk)L9-1@ zuvTDwjI~RP84aVkZyj_6*pWx>p4(${K<4QQ_fpeVU9+|$Y8kBo;oVr>t_Uv|SnbHVXZA7hO^7K(Ee z0t!5-JXc8&&=oi(5%L&A@tZP#*NHp#ATPgo)iW}IaiR8S21W$@L7bj#!hNIIU8B6d zdr$PpxR3Gns2?793IPxig+JBx{usFUX(~I!C2)k?!s>%g+{E5jv9s$|`}3~NskLr2 zllm*Q8oN7>z+d+^Zv5xoPa%Bb2ItFLUoBem>bSy8C~@0JPEQb7=ZQDf$AijyZM(h2 zs8g0PYw~-r^J5ExxmXupc0BI(`0oz1ru37;9))K0&?=2fXMr^GQtN7x&~PcnixUq1 zrYsM!y%6G}TTq_NwIu_sdBn_@ z=*%gdgC}yX(ebij^bL91|H?Fs!FO4w8~!Qgp9u}#8gx{bv&5Kj5o&8M=6Gzyzoltr z8!qdFa(pE=p$c%_sxvfdJJ_W(%qVdcz>E7QP0V-L>oz@+@ol{XhWC}t z&wgcWzFX$No+_FeM%7AM2a3NUw(grB;t*`?g}=4Kc~eVxk01hbUghY<3YA-@3ZT?!MPxl`$c;@rG9 z1HPZbDs&ee&s7irWRq{>Hh-Io-(Tv#2bav}bcbf}rIq5OT0Y>|&ay#ZfrXgf20Dy` zBzOjR-ds5gWiKajn{UZ4(Tv%}DnkW6!XoX#>^$X)%Ymertdx{5YCFgyjJ*wLstYsP z6|sAye=Spu&8O^xwmu2o%gEXjNit{Hyd~we|04Fzopo39U>6wZt*2OTia&a|n`b1~ z;Ijtt#(P_rbU1dDQKD&&NOZb5Ub7oVBa`MkKn2n_RJGT0%z@#XQGtviWQrn-0{0yX zN=8x%Iz1)6fbbg}#3X&0Piyc*isu7<0~Zv+2WG(`NQsic*j2zQ8lAmoe91K2!-ssTJIRC%WtWt&MA ztzm|qgrdd2oKu^)2YNYs9xFmkZGU6w6C(j87O$u}H3QY?6kc=)~iZr7NohupV z)fAs4XCL@3e$s! z!T#e;)J2uFj;ev;&iR4fs=-8GT|W<#c*QTL*K0k&_7Mc9T zE%;hdM#_EN7txGcS}tH9icuBGix`rl@(@Z?*-v}p6z?_E9foy zs4O;22G9vsq0entH3`0Xb|YUoF4~-kDV71W{a$~RSa!efG@jHA@BVQPa{O3RFUH84 z|Mre)0YzjN`rDkLq>|@f?@2wPyTWSHM>^>r8DQI6eufo~U6g}$E;#m%AGXl2xPzM~ zb}Es&JnxmfHAHxd<6Q?9N^-CUaoyjM98EKY3IB<|ZJEh3?O1sCxuHqtY0xO0eg*wB zYdPfd+0^)P#Wp<5MQ0QgT&MaV^2CROENUG3dQrV`;>!K=_8cSg?VcxrIUC~n&NVjw zvQ-44rW{u;3b5!u-KqmP_`FCx?TYr#CVv^fTwu9TIa-`6*m)A3HrgKS<}i{*dxsG| zY!S*{2(fR9>){*&(h9g>Cknqn-lWt6DdF!e7eSY`nKo~mlkC0aTM}WGw9r{BBbg2BI5JLr2+)blgSt>2 zK*Fd_nx1-x(wTdrHFRQBVfX;HKZZI=kycjXyN-!DHQv0Y?|VY zNtmI5fZw`tv`a}d(9-6!g`Mx9?fhRLZ7{$Ml$$N3=(p4&I45$)^dbAzlhid6cpp+j z(7BrOKcW{_Vv68P%Ij?rx98}-R&G^&3EZ$t3gMX*W}ZQE>-}Pr?Jp7sXdyh?-B}`2;sfNM(sl|k+zWM27(AksjMqZ8l7cvtQ(0tvD(kAK)yXftmA|~(%Ag!*Y*3`bR12zTShFE!W!Z3TIa`gMt?t|_ zwOFDRlLYfRwqFY&nQSw>M@eC>23Q0$x4|CIyYZC*e}p0t+VnVto?-_ak4GfuZ4gMh z=U0n65#YT-IyV%0g=1Pd`Oy$rDl+OS(^EP*Wo!~U2onq^_w!ixjV-ZkG9eXJih7;& z4@!+UD%-0_Hq!Q(reX>z6r<;m&^7(%y4UN2gk)49FUs3*GoF)YDjhJmVr3B7MhA3b z6QleH%e)BG69Cif_M=4fqTpkUfg-~a*qA4(N*&I5`9ZsCNmLA}IVa*^N{ZF~^x6CC zq@jTs%GqP=0E7Z zq?$6!&F0W)5R*Ar2IP+*Z=1oKsW)h$t|gZW;cGrX-H375vzEd_CmzheEnaPiK0z2S`Wu~gKWn8bYEG!WL9$;uf(LJ5?f=#dMV+WSN;Dscic;5cuRxoUV z^<@%C;()KCq>znC_8s8cx;+?z zw-P*fNo&*7WAohiUJ4#WALNFC2;;*KoMoK5eOvBE+>cvfxxt=qKENcTS+KrnS`Q%d zawdVQ{gHrUfBVY*XtaP%9ts9po4!6ofe{%3(Xplx_BMi-!Db|5sai{9ImD*c@&AUb?(~Y|E&5zyq-@AEz zn+!sig)(Y+D1q$e=93|*uvmsZ#_mFiORK)mk*d`740SkZy$*DZ(>*L06id3PCrND?}fI3ql z26pJeo!awnOnql&h`xlP&l_=!;-`d1J+;{gS`p$*)&W`?1ivgo8K_R~Od(QS^bhLC z?GJ}?&iXU8$UZWrvG#zc5+>J0%<^CB1Rn;-tUdKLdE$hRVaLMkPgL>MZih;dIxmg- zPe!e4uDtoiwn`mZvGRv@ZxjkTaZ9>ad~PT7e{6Q`kRqpXtj_ieF*fh`x8+0@GZMDVBkwln%j(yOaUa zSSJdkh6D$(a6;4V%VFuKi(C5O2^WGDMcEH#NYrY^|M4bO>m!@SQzuaCRYajOrpQo1 z?+db-wR?{DZ2j-HciX3SL>lx1@@~giv7P%HS&D$PXJu?xBIShrivY@jomV^Qe*ED- zlVPZeJo|5xx<5%b33UpMbC=>QV%P_oi8%vF=j-CkOs`=Ro8O zR!rC-476~0nnO|Nh{n2_;YNFrh5!o@a*8ex>=Ew4;hmm~APnY8Mx+sJFAX4!wB!M5 zIq#4%dGRTrimR-#?VvBYbSvD^D5DHyNCnax4-);<)!&_&;UF?^6YrBk=`bQe&l@daJohv zlA;kXWD|IuGZ}8i2TI88+-`T1tj3gJy~*0d=D=Z>YDVSnPbTVmbUPmf5_ZMvFN7X~ zVyQg3kN#ciXJX8O5ySLw2!2ocs3k;hsdr``%S-AqSNV6&I(0YD>t|LJ7gdX_phuXy z#$19K)x^3x7WiXymz5v9Vp3b=5GH%Q1a|)NUt!a?IZbXk%b+G;CX$#W7wp)^0ekd* z+=)7Zvgb+VKQoP%z5gcjt{Y46NpaUpw)e!1hUqr+6kJCPLqwRT%G<|+R4FQis+LNm zuxA!`d{z3@s;BE(xq;yFW7lAfIYr^5lQX z$cOwtvplHT*l9)UotU*Hl_@$b)iPPIYZM*@H9TNL*;moR|5 zo0@VNrZWN%ysGCQ4ccz;BN*Kj07MIoel5shSe#vs`DTRl=<&zpvlh*ojt*RqDrPy4 zbrBUlQRpbqN8`=g$yP;=givB%blYi`@7&!u`LJ)>cy#Q!Zz-Y7(Dc}SID=CvkMXLI zc(TA|0{MRaGKq6JWSQ~VlG5+4bjB$y(JkFr%840*jUJ>bjn@oG1m? zC@uNVZX#_|GuLR8rePIsNTq|V1$NG7e7u={S>Lbe;OCAZ?WV-WS8khF)%7oBAF0T#g7C_-7Ab=NDVYmGydqk z_03%&WpJYUrnXm_^8#)eK_z@fS<}s`0*FSFN5P^)S*v=X~C*I zvQn${KBzJYFYa=vp1ZfV=Hqz?MeRc{%|LXM!Q%+Z&)60Z@^d+D(u1!PQu#?6X{Eo} zC`M(L{?N(~C#@h)nahRzo{7;h_~&YcPb2}cv+FQ&*b59G9FYUeu^g-E)xP7Zn1K3Z z{qiZD_-u#VZr`rum_pz~9@a*i*)ow4B`7J!ugeGFh9^-@zarH+*`C16x0^fEl|uk4 zPj$vtdU(U-p?FB0b2+O6-VeB2t5uhUS0%}lWplH~7q^$3_VA0C5V}O^qR}e{m&4>- zV4LlH`!Mh(K&)rGxjgfr)>rvBg-B3WYW5q_{2q9KBoIK-4t`JueZed%3H*tr2f* z;7GZNRmQbM{G~$W6MtyeWE$eYE_O4HD@nle&EgIPV;*~pXHj`PW;TNgO*C~6+IX%C zPHSHpyJMWs_aXAwsf@Ljx4OiMw?BJ;fTEpr9$06>oM}Lr)8MZy{+I1T4?L|FZ6R}U zRizI2?k=Gqlbl^E(NvDeR;6#rDqqMJ+TY7J873F40@?9v%IX1LuQ`vqRyD8#2q;x8 z&!a9P*Y+L=4l;lImI#A-udNnV^KVK*)3fy z>jlVwe36HXGM{TSriO--W<-u5eAO3ro+u)%U2O{%-~?>CcVsE)3TyicaqP{h)Ys@3 zEVW@%ltF*}G@xC~N!;bM1MdD15divQW}zics?M##a3RlqzHoZqX#J=57cahg`x8FK z_sT#7hc)9`o9dH7+mEk4y%a>5@*uc*FrK08Kg?<>zn`R7!8&otrNTsMgF6l&n?Q=P z?mY&2l7Sd!$3%;+B!_49K$Yew$j2&+2V+gALVuu!%B6WDB!fWK2#HgA5NH@V!eid7 z9s!t@p>yD!xJfALAY#cN_bisIdtM6QmTPHt??V!2>f9rzkZeCQ{knXPUWjS zSx@9)sR^LV+mxl?63))2ipiQ^Zg}MF!P~T9ghAlvytc)K^1xTsQ8{&!9_cIL;s?%CyaT@5n7L!STH|JIOI)3EYd%(r@nKj4(Skm0b5~0gaqU%k zDQv3VspY`4@r-=2R4J4{Q7?30LJa-Wc-8nv3(oT0_K|~o+PhVvaDw|oR;m6TsK82Sf%~N)HxpuVyM?Ty* zOf=>&*?Vibs++lbhW^xmdN$q-Dfh{vudNC`*Ysj^dVs6MKBe*^S{J#g3CC$hdI}w! zueNt9umGX=mlPf=>5q@M2C@)yfr0v$6z!@4fKMzvv(M?OeicwAmpwFIYRAQx$4m>M znUf0agO6*ndk7x9*+045hR=Drtq@q7H%h4g?6b}pdJ+RjxzzV*QvUJQu2;c6zc`WZ z*$8~?6c_9gI=sV&$h5WjM!`VBX~o^yR`oJ(E`Xq>Jzj=juJHRQJX~Zfn;q5e%U(5w zkd~O&2V3U?(`jj%i0bX+8-(&4$ENPtu(RF)@{`bB8sw%k>{CA#Z331>)uI6O%l2dU z(v%jm(buODtyg|f1nb2ReNF<9&MLq6kZ$Vo7=rFyd@hxa?TuWS!jgP&@Da&c%ox_B`*eSK4|0| z4zfQXF2fLcCJ`A8HNqHY3K=R4m8?3-aoFT|J3R+JAWLGu60 zzdHHmq#(jKw0Xnl^0dlZn?;cb_hph#v(uGeE`vxx$9}U52QeF6b3A&L!HxP0u~X{9 zffXU~8%`c_b7ea*SSRQ}?a&GlAtK*kZ5ak-7oBO)OXSAsoO|VC^?eLzqUb9A`_R z*`kvT+}^lCSThgqvzMg2-LmC4x~K}GO8?R}>XOBD=*mk_OvDEpi7!HWGb~_BY@W`Nuu_!7-Z8q|C{cLB8GV|Q#KP&ID zgGpup8D}scs>1s$x3j(dGYwG|KmBK{keabbr2<`%8CR2Z@S;EGMy1N-D}m!=)ngzj z{AHlyT4l)a(Q1*H@}P3me7;NTAc8&VfM9nihit2b`x^8uyE%7GV+^ENMIt$5f6o&` zUO-NU;n&22>j}2;z%R?wwRoo`dpBK-wmBy$&ANCpb;LyUaMB|U!9QtZlfj`Qip7lf zs`AbCNkL%5)q!UX(jYa|pQqTG37Mfrf^o0;BP$Y{m^dIyp%{Z)X0{hmgX139L^Jgc zz0J)oDi!uJU1_myFHiTtItjdEn?0q?r96+!I!!2%`f2;I6$(O zYg6Vm$(R}RB4zDKtC@(QaUr*FOL8fSwfXvUQSB5KBV@wEJg6y!uY2VpYO0l0lR5C zAb4FIfvxHJJ|;VqGh)W*{MfS?TtYu#r@9lHLR08?ZT2Ysn8JgTT9og>uTdeLUH?9u z<-Glg2pQtUN;Z-sm!az1aPF+J&`Fi9=JO>$OSNV{8pJ!JyT+=5SnYd~P+U>Lj}qOZ z@uu3S{Kljdx&r(~6l>jTaryS~H7$z`p)ppxeeX8GoZE(X1W?DY#l#sSms7IHn)J*7 zOLMHZhWm;?82%~D1CF^mhDaScL(YivA5oq^prI! zr$6{#FLWI2JDi>rM_l5HdDo^7v79cW{fl#Ax}C8-(l3MxA{qW|x8@njN9nWQhCO8; zP{u;3uLUMT#=6Ib!cRxwi&BH5*Is#gHC?v{j&X4&;a8g_Rvyt@@4n(Z=9;x5?Tu*8 zU+_AtLXxTnpP`>|k+04}wC;CNT&>Qo%I$gBh0vAc5I(-1Z8v1*3 zoPh7Mdx4`tZb__t26m-e=1jl)rp|uIL~6)lmPl9tMA9S`jVY_{IS>kI;b>hiW`1f~ z*69Hr8x7Eh80sr*)?{)Qt4KFd!f%_mk5)H9^FZxx0s)JgM1&sL1$t5Wxfk_ z@?rbi=JHj-XAQVus%D;J9rYQ4kV=q&MJN(OsPZ_wg%(Z6r3!+nnU0|quLp+A*)~~? zvVKpgU;nbd@h}im4l?wg?gH_VBbEp(hX-w?fXtj28D2C4| zCPLT0ozwTMJ}51KlBg{myNR4VRyP0;kOviZ!qsvgp43-N;Kt#_7~1_N(K_dHSLLQA zZJmZ3`W+&mkI2vqprM;UEt~>AD;Ke6qAE?kSJWqrS&ZZ!ZX#Yc0xI_gGAz+hfDeX< zmexXn=u8DUgHcxww+ai9Qe;JOst!G1^Eh-rKCNEpo^B)z8!j#$&`$~+f#Eim-8H~E zu~+Z&J0kLtts^H&5lCP~0+YlXlRJPQ{P5?`{<(VVYnMtVZ!kMlK7Dx~#mYX7pY21M zh-82U$jP+y$e`m99)++b6_g?j6%lPU=BF`Y%1FNjhKC~=EIEFnPPx)l!XXeJ-P)aI zPxPF6k`nXvAwvvt12UvjAWq;Bgo(_xh<({%^+>ijV*!R*v)l$O?z@SksTR!5X_zxl z+%r=hFoh!sGyBsU`VXDfNrg3lHyEjgYlyioST(X0o|Ubrf*bu7kilFUV+V}qKcNPO zk+!48X~_Y%#*#(i52n%79U#?7C@52okwC*|Zm1Y*=s1C}7+@;BKOIM_{{sCc^<*pT z;(PPgWqZb!x-8hHB7oAj_7rS2)gR$ z7xyR#7A>l5xvOlN*>FnM*A7=sMi8h;&E>g&6m&k#Zo*V#EV8v5R8>}g6&@_)kV3UV zvza}McckTfuswyrip|k2HFhsTZ%Y-vZqvg?=8;2nU^XYjtn@LL32oso&H7cLK_T?u zSU1a)-u$Jdcqj6{HtnEXT2`u$_H)Z|T?kk-7iHC>Ahk|w>T`A2>Gg}-cz$N{@g*^i z8vs%=Ed)Pz!#3(blO$Vy5WZID6t~Ck)c}s`uq|T&2UG3)ZarenTkaOeA))&* zwU$`Q{K2b%<2tv0086wian>)iFeBr&)?&QX-Sn0zZi^1dc_-e#@gUNhF{ zA|@58F0&^AqdXm1OgwOHT#r}9)r^{xi0`Fxhe@rCj_&@SmnEm`##(%cw9XSw`qe1Q z&2ET@zHN?%^wnxB(GdYC>B}cPZu*=kfM5290h*9ONQEkf@`%*&2*9IoG2`Wnj+Yn) zx%yno9M*P3XPJ$eDH*W^ zqyJr~0zvO&DvCqEe}@XZ@L4=j!Qz1)x#B3C9#M*pJ42b$rZ_zj5)rg&pVh%l|h zoG^=>cN7Uo)G=?i12KV{w**&y)d%EDHVJ?S>1x&NBy*iYd8pXF=g#{&;ZIo%5-)9s z+~2>#gQ-zC7j7-*PVkP~o7Xz`Xgq`{8WT?=P6eVY%q;dhOB|j)9Eb_$WUsqjKW0>D zNNh(h#RZXrwhbV(aU+EXHaVn3gECnfNpWy}>h!ikx{zSpza9(Iq` z%`@*%@D8{M`>hL3$!Z?Ri=dd)trd~jO>}x@3h;|KT(NG!_#_zY3)-$<&l=1hz@gqh z!XO5Q!^$n>41?{5KBfE^=@bD7G_Wirxy-1-0Cy9UC;pByR86tgMkwR}-0gXp^`ogW z4$A0R-_<&+TP}Z4EjbafM1pn)e^G=T1tXmXr^%UjHH_3;di*lXP->w*qH`H!B=C)K zP`xK%;H~3h(LtXtkvv1Ib^tk)vi-GHEp6*OTbxyC)SwMn6C|L_(T2fO+66!G4e7G?2C|l1 zZj3Mv*!%oyH-XoJj>?jLXRKvm zuciGb4DLXauea3`h;=2PRQ^>3M{mOH3^Ar;dkL*|ft3folG8?f{);85_q<>-*!Ds+ zxYI_JUCuT)k1yY*Suw^Z<#cJBO|K&5m|JEPCVUHwPvpht9ik6x-!f%5%gNXO8ZpI> zx^T|xGtutr;a$(_XEjIeqoZlyx-DOgJuIN!&@1kA#Mvdrfjiri;S@fXnq?SW7Bzms zv9(4}wJn+a#>8YeyI!?Y?=uL+(k8t(MuofxlOIeyVR-<@2W;wj#T-fZ$* z12+^^q~4nmG7Wes4_6m93}4-X=<~JXW)Vr49cp?{>`5v-2N#p8RZy_w(H0l+fuKrv zjrI^o%&cfapp0b@mYAWhr{!^jT|ch>yfMp(-<|h+t&upjCUe*D)dt#St&Ym4xCsr zv;_rmj(2X-BxQKlR0FaAhgptH_wrc)(ZYx2%?B|*#!rs6C3d20y6JaPc`SZ(d_&J# zopb0Z`QD_1K48+sJq{%xPpur2Fa(ZV2}6zOx!!FM*ttK*OX8;|x!yGF&x^^AXhJ_# z;;t}GQmqhTk?s?@z#wMQDA|!mjKtheFu9;LQn%Px4JaWo4wO4C^@F@PFaCe`0>lHn zpUF-$9?QM-Hw;M#!KBp1g=kt9T)r%FNU*jsPd|zjYzj59$|PPzgNK6t<|*G1{0w&M z#2F0owcB4SiMINa?`w9;cCpS_21Kw-W%v!>x^X8o&tj6YOWuwTKdgAC8<5A$5`$O} z%OfTu<3~VPo^E=?uua{9Rcoe_P?@yM(lVq!jA%zpK>J~QBB_n_fPZ_Xd5#%FagY#9Qo9Lc zGZ8&@W8OXGy%7>eXS%^8nUvbLz6;Ic5-J)d{Mb%Cq-IYgJGX`?-f0|j@zxV3%(fR@ zF>M6KJ*jU#nL>&brk1Ps%D^YXMUm}BCNXi4RE=kXBpnybSvN1-W!O&8@W;2FENj(g zUGa}%A+^W?gA)ItP;S2FWovv6LgAnh5-Qz94BsY{biNg3HjyBHvlE{^{vnQ>Po+Up;1@9M)h(DUiCsG+W6tBxQM~`r5=%};1`u^Oe zIjaxWq61KSQiL>D6e?~P2vvX{QwYL0kb|1B6^c=_5_;QCX|n1X5{ls{>a-!DbS4ASo~a`ehZ)OzQCaZ1H#kCs*W* zLX79(%8=7uD1bCHdhPkk8-=hks>7-QfK^yTrJeZ9uvEF@Exbb2^J{I2Ad$(|F;-2k zQOOFJH$mp;>Vt$+=S||Nm@%W-JfjI87n`(L=b?8q-V8%Hz$C6Ms;~tPafyC?6nmIR z%b%+)j>MY(%jZ(i8}SyPmkXSwpao4J-1|V zk{zL!Bj@IbaKyW;L=7!zB<`a?=$Xjom;>2Z?yuAgtQ4rxA&0!8|IC)8l_~)Oz}A!)ED3KdD{NyjV5Ua zU$Uv5MXH>1*rRzq0-+PDI#$>nK2-b9^J0j-LPRsPwl(Bte0NNO729v;WI5I5J?TZy zS-FZKa$t>~u&k+$lSdn{ zrfjCEL#ZY|HM)@tx^F3B@an)u$_*h;jTMc_SZaZW1Ij{Hs@>*g$LG7knc(btXqeXdV+{T*A#;|sX7_!NnQ(g;?NHQ1}`To z>EC}c7v+d{Lm+7aPf>kr!?G{C$TUu&C)P$pYRS^OguXcJ3)|Jdrwrq@=ju=V3-fNl zQcl%N-*|S_kv{T>L928PN<{d>zUGZ(OSQ9)OyUhi2j}Yateculv7)pP_J_xd31n%l zX(Pq1n_#9IF(mvietJy+WM27r3p5>`=m4rp?kpdw#|bu>2NWPOQW1zKKvDuC%XlTT z`UyRhh|v*H^5Ii?K-yTnA(63qoAwDpq>Es)h@76@8soU#eN-tZU4wo4u}I4ondU@J zz>O9d6zl=m{E(dqX*(ETa|tvj&6Y9dSepv(#}TmxW~a>3 zpb18|H_c?;?2F5VmFno%bof;PR6c(kINt=#Y4JE6WTPG@GTS2N?YJ^WnXEXCMhddj z;T-MEe8~mIN@d4rN#uG_qKs0nD|%oIYg%;YCTc-sG#usB0cgO7qpBBF>;Qb-z7%Gf zTG<*JHHL*P&WMN%y5Wlt3pust)tE{gx?rWMe6NGl0Pl8-lqbS)QpO}ljiW%wq`MCf zay|o=?ZMP1WziwOgBZ7HzBL|@Cr=2YI^<50h(iii0mYkP#x%}gMK5-!ggBT00vh-| zRwp_V9(}}=7@RRVTxHJoYu-PiNv3lh-Kj0#pn>#qDr=OTcCVTYtXz6_gS8RehB?Dd{9jBN`5uk4DR3iD^O1F6pEQlg8WnVlypniHf0#Tc97i;9K1sE-NR#N9B4u&gw%e zK~WC2S>M{>OpY%Yx+3c*e^6&^jL?Jgv!pd%4E49x`mqFh$Y7SA@UN!4DK%k@o5K3J z_q47&^!V;qfyac`7o1*p6cAtl2W^dVYPX0Joc&8R0=&U{y{pu7$GKMmA{Gz);hgM( zPGg}3+rtu1-x#{&Q#CyS9a}U5NPLE-{_yxUrp0TOElc=2m{m`As7m09R1QUvVexp$0XY}^gmDix``s&9NOFv z?Ife{!JaqS8_kLyg(jYm{EO6^i@I!?_B%y0>3VYVgIi1n5J39~#60TklSAd`0=&w= z6an&ugOLwAJS047P-4nq7pwE~BKoe|e4r=bw|-P=m2Yi%GM_ajyoj+25JERh)3z@Zl?k_R$8o{_P~3qgIPW@^B0XIGEO)w`?*82Q(&6_e#pra z^g-sQpS&*{#E8(@Y2?OeD^>`m31fVT*hD*-g=@g|Z#ptmGz)6GM-KLuYmOb(LVR)` z+S%wR!2WbIQ%0kX4uqM%F6ZhrNdV^s4LoLWvhi4jwVDiF^9&3VBY!5W@y^XRSv_`ytepQ)G=1e`X6pflF3DQz2XSxkQ)I*-_P`JlX#IHH zjtn1xna1*D`npSECFCte%1`k0eLMJj)D4anZu@&iOX`N_{mrV|wIG*zg1DYn z)_=Rss(?u_J058Y^`XP4OqfA1mM9htR^-Ho5d$k zhF5JKklgLq&brBy!--)G5`iL;P5{As>FfNq#Sw7kLZPSv4R6AtWjhzcw{*~E`L+t5 zo0lzH0uxh{RR$uk5eHytsA}i^L(V!Q%a5dfAHJZvz85ha4;#AwCpPFUszRsDZ)D+l zus2CB#k~`4U9j9PjF=30I5Byg&mm0lq&fdF<;2?k&qx+&c5b5k!T6tPW9&mTXjd!N zWSE}_K|4YL!b;@f`10*0teAtcg`fgc$DZ6BR-&T@Jt}j(?PVP>4aAjcixnsp zrl5WdLpcD!`p%fr;lGX5g#v$OZUU-r=76b((f$?PhgvFo=tJ>#<>c$%5mV8Vb{aiX z(5b-8{|cGR-TMJjrttG-;|n!x!u9-p3g2qY~()KsJbt&U=*gb*nS?+OM{P zyJ@30_{Uvj2EiScZPK_aADt}X+M?;`1JdOb;6=W>>?jjca3mqGutyN_i>z$)=dj%@ z8layIQer*k4hcE=+LW#AlADsZqG}2Ys$t0?fU!RR0MS4ZRrk3>$R0*aKIAG}Z~*1T zT!l@{#Thwp4n4;W+q2#r$Aq3{+dH}&Z^1Q$k0a zWo_R81mlk}XAH}<(4pB|kbJ&rVM*Vzt~<9-Jyya79Z^JPw6ZE>SA=%RnF$z@GEKhN zc^wc6FI*kgG1wmJY3O6zGMM;(lL=N*O z;W;0zhI9cK`_*Gaj$!y-g-g_E^_~wLlV7a>p>?4@k}a{e;v8~?zq1vUeLjIxk;73o zwJ)h6!=eT!e$%bRZmNEsHR6R1&hfpyjz&bJp3mH;ufQOx4_<(|mM)DYG}~g50wvu} z46_F`O7zH0leq~Bco!zG8!%1L-Pb$j?byFFx&TeY=lGp zPpyw0gnIv{3&BC*lt`EzdYvL$zFpz=^Q+9p2B%pe>GG(r1CxdH$B-N!=Z{l<$+QM? zHPjjvoxsKD7?0bVK~m=hmw#2u8aUY-C)CG&3G@aroUcK{sD2$he*bWx=k;-w^|#}n zUv_K4Oq+#|RS5sscWtDKvMSZvTq_;p#+YB}mZ*URFlDC1mU{quEHC{=b{WJC*rYeP zwMko!VNm;-Y^KX2k~3UCsH-vU~y~<5VeTBlR(7u@CQI9u?~pEf!C9LCPU?+gFbfE z{?x{S*6iv5Nm5mZxB@bGextEMD@QdRaKC3?SW2 z$QK)~B)$r@yUctLC}J575D>G`x?e&RuQwiE)HO?nAINmCRj_cv%ednD-FwW>*JN-J zRSO88$haN6YdHsb?~>WhKjZ6tmV}2q%DNkC{<18gkgx+e%B;ntpvGMSNhBi2ql39%)G+N(ZMQ;q>4s#Lpg#=U-h9esdw0<6vf_1@ zjCMpSYkF3mK*J<$(4Im>csHvJ-gI%s|J7kLzk^R~th84rrdX#L zs5jdUTLcnY*-Z}J(rj0KHnL#%teAyzzEOsjs;!RjP_&M99KHEPd+$^$*YU|m+qNxh zE4rjmcMKBmCV0gMKpGd5Vf)j>!&I&*T}4KtAWzL7kC&PJ5sfpp7o9cbr50F@_*HL97p%bE8*3wJ*;*?TBoZDFK9>6ff0F*uLv0 zLhv1wDM$HdWF{62A}hB7%GsOd*+MB~F2iZyN6Txwa@OiUg7?7LW^$9Wp2RRHqM0@wl+Y=C4X1aVn@`uCN3blDewkXfmygsQMexzsWj$Ng>~A=A$j{&3ARLh2t#~xZ zr#)X!Dl0MHmuR*A=f$=Bev2?e;K#cH1Cjn(s~QfrrRe(e3bnA;IX_UX_e!B|TLC9N zLcb9ialdQTkn<+p-W%(5ZWpdUDBO|<7M!)bkA&0ZP!@b|>V1nVhN;|@_<2h1j_nBV z&cDyLlPk#B;d5h0y;_w%_Supid6=9#BlrlaBGT;la0Y!sXCK`@cI3dIb#Hm*GYI=UIC)!j`)?UgqnOVk(7k6osiEM8-e*|GpVam%RI~g+j_+2o{HPH_Tj$z8iCzPPMz^w~G3L-QpP#cIZk`ba z35gHHVzw*huGMb*GsVLZ5>~(|NoeUsbF(yZ1X{0KX%tv~$M}I*z!U4{>Qo)l9d|9iWdLd@ehOWkni3gB>75_HYS<-pk#>Yc&mojd?1 z%^q*|EB*K^O%%KA)9DM_0*rBYL^|R_UN#;Nl_dgE6M8Sp;|F8)UM*k*dhS@+@ZR^=KyUZ$-68rtpMXp$uS#iqo@y2e z3|QbwSDsZj&lwvPyk;+g!Sx2$ypZ^PAnL`|t$M@?SS{E5t9F7CafZLOjboW5jGe1L zaMR(JR{vV-lF|-8(HyzNJP07qfgLJ!aR`mk%GqknBUBG65xl?e5j4oIE4SKGTh=M8 zhzWtAw64N$$i&6!x4a5hNff*FbTfQG|QH|_>1ENWV z#zVkrcW&SOdtmN>>jj$%Juct%mr$gP<}sE%K1mf{;zK;9BH4>1`u4~WTz#`yO|aJ4 zv_3oal7gAFZm*o$LZ{b*cE==wycTWJmEUy^0+%OR8hFwIj@8*(~SEEZ%DocuCzg3x?OqO_DPmbaZ0ZK{PX;e(0{ z`!O3@uFUz_qsfG&vo@C}!!%yN-EYAfSjx;uVk%l}uP*o`)NQI6YiaSBB+h$#DsB#y zUP?8QZ;Le`?2LD8_$0mDV$Ad@@_3Ckj!dw$+l`5dZNmg(jes`M8r zETjL^+UFxT(6_h`&HyBU(1%G%uITxQy+rT1i1WUNPQCd)&(ucuR7z(jMGrI@3a~^k z^}aA(T9w^mw!M_65!+MWh%cYLHQ^cv0@o9|D-s);uG&PX~Kd(Tjwaw8%ApWWk3s)&4 z3lHYsPbG^Ju_UG@0>SXM+psnpw9Yl+a}Be;SeG!o?9UPTu}sZ5Gc3`IEf*U=4*bSJ zZGYJw^hV$-X%qg7ye{C+A(4nxzaG8t(?V49<28;5*jDy_1WZD6#2}{3yVm=xhHiDK zhZ$MKLISR@W?&v;6*>)aPO_n9PY5N*bX`5Yy|37_6X!(H`g2k2dZ|TcGh1RM-~DGU zP8930XUCM~e$1APT0S+3z?q0VRRSTGH%m8;)x}bxetFwGhqbwxrY@%VlH=$YVFmeOXjuy zF}c3m3S;CSxy=Pq7qZ$(tI;Z;K3EmG66}sE_59_;auxY`3cZ0C)ptwa$0EJMi_?Nt z+j~<>5V@{4(%z4$KMLvp5}-G#AZX_i`#$b{GQBT~uH@PU!A|w-U#TIwxQ7IW&RD-j zES=~=&jhKit0%TMg8hnGPE>`FXvi1awtHejw9IjU=H@SGFAx!V@_5o>ZC!>d3N^{ahNYiJ8U)6;W^!RhKgAxYrPSX{->w z?!x|9_t6d)nq^wx^?(e5U$LjsSdAX-RpcYXpzUAur~jCLT}%tu@!_V1&We3fUB*b{ z{W$>FCZbv*_Nv%MwDoTlBuIC-cKt3BR}){C{g@AmAHABDNT zRb_3w)?JQyZ6|lPTE$Q1h0w=x44O&Q6$rke2siM54j|6~XqhH-*?!rKCH4GN$6HIf zfp$y~It_w58oHZZiLa*Ht|l;Eyt1^s!a%$MZitUfT1l$RZ63|939{bn`i> zoH@!osi(*0bh>fBm0P4*66*@-S!)%nR%Ox{?s_o4*G~{Jc_A69WXX&p?0efqykRlu zJxKNdMowKR3)zqEFI0tL6%l>_uVqeW7z|R%)SVvX1p(2NB(`B&cwgJfkCq~tm-9jq z0L!$(6U{1vm;$VQQXnbUjj#|pxMz`nw%g|pp%3GTEBNC2AB?m^0e|hC-Voc+fJ{3q z6u|%McJxF2{}85z82s2RS;q4ElF}<*&aCbI@@PE$@TFgnyGMx+ijJx|eX~ z-XbkqCwdLD%s7+LuL(xWTJG?FaT&0M)(Yz7HUueg`<;(unFf4yS-v%ddF1EGm{THm z8>mE_a^>a+qX2ak37@yC_7|J3-=Fg#!IOWW{|Dc$p@6={L3iJ|O_UJzMN_BXmi?#m z_c8SM)7{}@<_u`_9<@0=x-i7h@u~qqQ+i%rzXz!#*3PC65^TDK-BJ^r8nWh7{%0GE z4~U)Z+g#uCyC1;OALk5iTl4c|q9u+k#GZnCpO5{oxA*9BX&T`_fANYL$j#XKBkYJl zZu^7X8tN{@nO)cr&G5hBHU3YcodEnwqPD!vP%AVL2PxA3fE4*ZM188iD3z=GqNw`+ z=j+v>fc%&1LlmFwD^g*7W{CUPfgJbiaBB<<){`Ijho`^p#DQ#QymL z`u9p>@@lI z&1{2!Yn7`&PPME~rbj88pbicR0rW~f&PS|X?=7^x`$uw-s zus(LE*>W_nbv#ifT)SC;GgOfysYf7Lo7S%iuiah%C?pxf^C41?cxW4;YK)CsS>Qn4 z)c7YiI|TG?VT|wZaEe=r$>zO~N!l z`Yr^+cEY_@R>*x;j47#XzbT+LQT#}RDiA_x2Iqb9sUF*oD4ZXE+1nJ5R=Ryv5_EM`o~q+|gt z;W193p>Y~wD$oN2WfbGq9yUCDXnOyn;y0q%yCq7&h95hdsT_bIjFP&F_k?mfXPPY< z=V}$rlEoeLpw1cmStBm}An6Xfxz|EX94`Rh_g((i)vL={q&;b9|NqVW7a=ALpdv2@ z;zASIB9f%a+Pj>i6`amV@+L_X8HnZ{P5^FHR0g8-r}+8%$z5K@j#3mW8Bz zC~U39%*ExLFU4ZT)^##ECr$#~b;%_%>Dfk)ODUgD!LHqwsREq;>*}x)JJWd35eDHI zfpK@B{w4(a5u1MWxHM!!>KUmV6O8a9;9tSuRq?Lg%gtEUVUK`NXmBm+MCK{KrTt5j z^6lb}IX}TZTwL3jofIxSP;2mBbG92P(iHF?@eSeq`Q_V@b3au)9TxuG2ogh1(4AN*jPtYx)%)y{DR`(QF2f&k}f045~e}HQEl!kvL3&<_JD{%tr z*{fBE`c$)dLM)4Dj45Rv3byZV#NQ3Lb>2Y(@qtu|+}NUOO{?7tnhTPt2&OsF#THVW zW`)`scb|7CWB9W^3ce}zm#sIWyrVwMnpKxD6^|p)%e!`$EyY4tNd)sS!c!fps-t?{ z7<`OLdtR)am#s02Yqcqv?lCl*bZ^-ER25(7GXJ^DWuEh_NJ@k|Xyyi4>8i#xb&7}b zzShk+2?g*#8kEI2=nKk!r<;EXYJ~nEl;kX8SH4Ag=CNA}pv;{m*ZXGj7le%$FGx*wGB<43>FGzj| zoMyigxp>ESS3GC$_PrL`qCik>c#84D6fV&2h1$Aau+e6eymrp&RE+L34PUKg z8FL5In?_Z8f2|)!>wG?O+4$9eIzHbfQ@&0_7In6^>wLt#-XEZrpbk5IZ3OmKo3DAB z5rD{f`4~iNre&-iP>VJUJ*CdWUJl=rc5?YYXB;WfC5UDzPOWJ+x7&T9dNJOKTGz{f z4%>kr-DM$BgyknMP?7h4Q^0Ayxz48@?T>o2tNKfxNowgL{DU|Kh#WiDs z@7nekmIf@LC`+)9&KqW?pB&7`KVIIbc0}-UBdJmDBWPX@N;qPRaTi}uy&UGbcOL(H zHT@B=x%}CDsmay#;4o9ON=`|lcR{r6t*Z7#ToCXeL>slca@>v8@e}5DD`4 z5SvOl5U7;!h#H834mWxxHKT%Z^(ULRHT(!wC0?7s;@L7|atCXYHK8G+lP>aylKia? zC9ZZv=^~8oE1rcHJU(P55^$a;oFyotJ3lEbKATFI7I5~czC?}&B%@jg{JWNiMwvYd z_ZR%6sK)Y-LU_ErSjOG;2p;;MKm89ql~@)_h&5UO-EVlt0+A+##$XI79yuTLr&e2L z-Lr^O9V(cV&CU2r+Oq=@-ytkQf>295Sc>zKVT6|bM)r`Ao7GDcCJ;vRFF9@%sJSw8 zM#RsW%vw>>w8MoR98>ao_+bl>ow}(hXeZ6exd1}3^_qRG1F}RX6~gpT$jc1NvBJF_ zxK;8{k#F3+|0q$z8C;pMuMoY^Sc$i$7Z61`qS&IO?AW>f;+nk*x_Ng^yWOe``U}II z)Q=J^0F0^jHZhpr})U8IqeCK zz-svcRDP>)wdYLkVCElPM4l0X>x?Ty|IsorFUIT+*m3TpZ~D6REV+LD{*;-R?4_3>xy6u)j(RT1M{YerpO{V!*yVX8z0IifJu8H}RINN1 zKel9`NlKvEc)vlScXeJ~Wxk*U!^)-^hpB~N+pyH;a{E|ob*X4Rw5V_vKU!$@K2V!c zuHf^yd$pj3+ehwK_#C}OH6Lcjn4*`iKd=<5myT3C z82*@6jZ(y?^tz_t3Wia}R z|8GGut}h>=>(xkLI09!_Hy}LNDgxL!rrRinlp(i-!Muz-$!Yt!Ibd%MxC9s>xyW=G zv4dz7$veftK{hTFYEtl~gvVI{T~5-lJd3u3QA8n+w%OXVl;t8COgg(hO>_EjkmlNy zHBA#`lWZld)}~ss8jduGS?WVtQa|kaLBeH3<=27Y8OwlA!I`?G70i7aBm&R(WW1j2VCySUPBqIfLyj zI@^(es3ge0@_O{f$QKkbI;=WuSj9$PD5kKcj|DbLElu3X8J_Zbh~LzTlI~ua7Y54E zD9Z$GYJ{gVq!M0;$<^?`5;dvMc9bo}m7c*}rYqxG?L?5z7Tan2t0Ji#n8EB$0iEuu_t>2B4V1U=J)SM@jv>&+s8-x}I zfUpt+9j1?>9^|)y-yf}6*6XZ8yQe@IDx$D}^Sb!VDA}Mk5182gm^@bb)x+&t&f}o_ zsVcHfg4cHFp^J*VpMovXo9a_2_(q2S6u7gCAl$4_X79;imgJtpl%4WANY|chF-een zRre6kHy~=?;y#Gd}dM+mk_%8mx7#A$cSy$Cv zhAuB=8V!Byi@`t_v4hGF#t}&RXsz@8X_an0(p+R=p6QYP7Q4Y+#RhSIKO8dT1X5c; zx)BbTz3mpCJ)H#0yQr*|S(pRR+!IQ7b#k0WrmPVCVOtOIVrroJ z2u;2%!@IiVpzmSpC(@oLbITl$D6>Hs*r1G;-G17;W_JTdU4`aPC~Mhs0>T`TSiO5T z-xNC|ea7R3qJcnmwt9@1*}83=zNH_zIB<9lhfP@-OWJm+t6LMIyrB&9mJGcPWLFJ< zL@9pA+kwDzC>8WEfBPcDBYq}JSR>N5Lf2MKj&D>O6^JIb|PvF9CLILZRdxg!VDW~ zSeii5RKaoQ^u_XBDP^aSN{0myBFJ{t@-7ghx>PEh#M;7VS^_r@8YF9M8sYr2vyo%A z$*bi8Xv9Y`_q0EgDI_?qWdcX@w&}z9;TR|egS0JK}x!kwyMWU7;e`>tAi|yKn-_T@Sm0<-zv@T@|elgSLiY2k2rXn}xMc^LuWd zTCoVcN1*J^8PDN9VwV{@nRTK426}sZ_Gp@;utH9-qQcWvNIDX}<73@!BP-=CZKicS zWHd}q8Tc0o7TvigqR&l3y{+l>mXN5?a(NqG@4=0|NFWA|v%JfQ+tYo}Vc%jKKe6g5 zie*7rKE#XFA23w)Ro7>p0ms+%eV(Ar&DCC1hcz=X2K?n!Mt7;HxijI(|6#4eR+&~0 zTVpOS^vibCV%G^+Eo4bj>e8cS$C7=Z%@}dzDyj*0yy@XA9O_U$hX+JG<`?9MPJ1}~u+|DD+)&PS<-jJiyX+@NWv*}FJ-1m?4 z-8$BS*T!WhKr*!+cq?We;{7)zIj&M2&iM#uMfBWbI+bVEr>r;__};LgFWm}3c3#WR zFobr2j*YlgF$Vs%y}EB9SXt~zZpy7DN0T*@LV_)4w!Zi>HjHWCK%+gYN{zFROH;DE zm)4J(BYF3$(Dwh2!e)D)U&Pm1?Ijt#Y&!?wC;EY)M`HQcTBOaD5P;M z8j~Wb#PK~q-HjhA>Fo$m;K))!0%XzEo?0X+TZDg)3s_Tq7-^A~z-%z+mz<&sjs0+2 zqyloh+B7rKx33#)?RuUEp(+*hwh~$%OeC$Kf1{Bg?s!K^wuDg0I(6w z)I~>Wiqqk%oxwNkJ!DE(EIrScrwiKB%j}uyManH3^bsv;aP+fbue~hvTfmUNK6UxA zk96@T`-v-2(!->K4^#i12&&YqoJLLI_A5+LBb-F)EY2Ouc)odsGaW1T4Y49|M~Ccp zxhG4QUIZSvtG4jPH=Pfvc@G016zXf~;JEL*Sckc4@*?R!eX8#?hGmYAzI&bOB{G#n zF=6@zLetQ%>mxS-6MD$?ld7>fu}G2(<<=6QvA_ba#12g~oUM=AeZ--?ux690Adr8y z&-V!cv#ApAyeP2S|5^F_C&QX%w?rN;!7joO!u3B0n;;Ljdsw_5fo zZ7lpo+b{|z0Jfw9EsgE_EM*vNGOJT^sDw$OsJPWeI`YIByL2+!g98N3WUOd+8^77P z!cDGt;L#=;7T=gL9RFXz^#hmv*~ttswh#0deDWFi?)$A~_dJKHb#)uw;vuj-z2G;m zrk?l2#&kIbyOW!^VtAiq<+)$m3mC<^T{*w+m;5Q@Xl?y{5hv6Ytn*+Gm+u>P&b z)u!e8Of~=7tO9|ilr4DJV5JJRCvloNO*Zl{+mM51o()aX)J8hRM$0no}M(G66?=Yq=X|3XL$Ehv`G8N^qNNAQ)45y!|7>3_DnQ zB+tJEXUO^5k!PWIPQ>3{6fp+P4u%6D3cVKiMr$s;Ab0t_SoeAu&dlB;73+|W%*vq3DT2C zqp)rjKbw(s-L*q+H%gSnsP(pPSIvSWs3Lci3#H4{8Ln(r>G~|RNDg71dq2X2ra7xk z$c**NFW49vK{3=M$tZy$1{o2d3^ z=bz#~J0K5|o>8&qkHx%hbs;_n+C2jNdb{n2?1l?Z0ZWcS3Dwr}A4PMWV! zvQ!T-XCH=2HQ1*;3=)K6P1az{ZZtI=jEUVZ=SV(gpFQBOubmdHE&a0F4wH@{%-F)s zmU&l-(WNAU0V`_J(5nR774|Hf)Eq1bu)`KU1!fXZ=V~G&TDq6p>7suFV3NDUGgawP z43u(xcZjWlTC&GM-s<#*3&8sH^BLDi+$AL<>&yUrCtG%qI9OL*!r<)6{dK%%H;@O2r3ddIw zP8+~|*;HT0TZQfIx^BCu6dcFf^^;RR4Ug-Ox`TSAQisgdZkmz8=NBePjksy1Sb0&> zhXb~=oPi08RP}pw*~I&JiM;kDkGlYiRJk^BT&?t8&N-)wqEQHxafhJn`@0$kBTCvJ zu0mQ^5KPR!_JD)He}oouw*4`1QG!Npnuj|0Q2++wCB^+s-U9tlwA-9DliZ~&h8gu$ zIW?@!Fqi;AKm)xOb2!vG)iGoJ=iHLT{py|3&}mvR{VQ|4m;dF6eIO`G)*cCdR5a;j zls_Od+qxDCnua_^;s@hwciMkz0q{-qqu3~0ZH5i*m&?5ZWafy2BYS7w50^=(O7gvi z>-B4y2G>$ia@cD(-H0zhaYB(O_JkABFoI)x=n=3Ctn>Xsm_g5}KTAt)7JZs(-JID5 z!8;d>XBO%f7xIo#ro+0ADQ&9Q_4Gl$r(J%R-3jdrV-l21=dt1L)rvbd3>aw#F(qb* z*p_u~=?PR;9rvqj95Y=&u-r688st1Ihuqw(fbtR~8LR;WLzv8ZwLr6pl7s1hO_Yb_ zI>q9vCt}P{0OfM^Qws)jqk4|HR6fOC2x{kkGpZuSm6@2+D{b%Sf^HlU(Qv7J|77CXK9FvpycQkO zC-{5Td>)yTL|`A{*3hjj{@R<-d|{u_UIcEjyf%-n(WX-Z572y3&S!u1U zvl$Ujrka+49Wpwaul0;v;(i)?<$7ROegbTnI0uY+oQQtc+yJ$XQeV5n0Ujhgr)Og~ z?u!4%v=0f@JF&KI%4a4rrwmp8CBJj;Fizcbt6inA+AfBSR=S>@<&Mw39k_M{9XA|% zgM`6$*7J}K7+N~z_E9NQ_PKh02h(|a7^jIB@nXse^5Wx`5n3&&B0=H6fcjf|0wJaJl@Y{SSI=m>THIv3NKmZ4~h)dI@V-1XC_K_XiF!+%BZG2o{pZ-N!6P#Xwf$T@c zu!J(ITfc!$>Bi!3QEmmxgB?vCsv(O{EnO(Le+NtJ*mA{4{QO%qCghyKi?vh1;8OuS zNX|GpmtA&JlRt-e=}sixI)xbtZv$OCjq6|D2}2^|_#cDC$7ZlxpUHdZIbz(KYbg3f>S$iY5E79=Zz-06uyF?HY}-N%gJXeUsq3Hif)1V7AS& zv(7XtmAiz-uW1MG;)_T29vZ{UBw`O7IEYoTPqpoGLOfOHgF(LEP*r1hS`tkfUe?Y^ z$|(Dm-sJ}GJ0GALc9s#*1B}!K>GsG6R2LD8fS_{FoJy&*gL}D%X~K#)LV9D6;@k-M z*oi2$n_odEnDIw{aDQDmPN}!-v+jcTm<{yrm6FEtf&h1*UHy_HXWJ3bkGb$LL^cA7 zEBZI%V$*@LO31CMoK)(MyWgt&e#t$7tASlY^rdYX>HT>m0fIMft@`79siP9R{9)BY zcA~W3`Gbb-+BXrHDbHu)J7A;S@g`HKNJf?{t-hll)zIl3pw9P zF7J6;_*@=OuID{A!INI8{IQahS?pqzn#_y%&{m!q;~U2GL5y36n>~nLl6dS{Mn-k05!a?dJ+B zj%C|y+og%kh`T)^(0VcH4vMDiKt<=twN5uyFu?$5e{u1y>H!j33)nq^k!r|0(-muH z0o~dx7s_Vdr4JH$mH!f`GGcUxY4f5XTRCedI^7siQlu$wV;w4Zu$Sa& zuU5=y($ClKebo2b3n6P~{d-_05vh6cnBa*u3K0eI0yQbQG&g0?25DX(r_`yL27M0l zg`K_)>_(DwY4SltFXTPY20GwJ`<-}^*2Uxn#3eJi$Pqd68fBW)$H3METwF*wqq{HT z)Vq?yMXjC8=rx-WJZ5z#acdB+T)*UkQCeLBp>=^dpVAiTZ;Eh|pnEy+gdB z6&qHFExBwC5J@|aZ7y9wnM}4Q`86>#0GYWQJqoEzZ-`D<7{ppLd-9(zUbax_N4$C{3 zYKxcMv{+K1)}q!3+O8DaG@nlO;7br{@Y0H4{h?4?pDV83mbt|uTx{NDf5f(6j}y#$ zR`S`Y(&{yGSE1lqZ`B`HiH?_UlpMC~f;DPSNVSq~kn4KB$?@G|F5LJ~rhCBJeb2F- zVIEt^bnDkvZY*PNyLhmFo0pFn&ZPX0-!5&Rv62=SYM(tWU(_*Op_=(Tp_(tNNgN<~ zktT4h=R&^~)IrjGmC=~Moqsyk_>P*jB2Uzi7$kr;kB!Oq5xL@==e57i(p*!5*^O}J zRjRMYEaQ6|VEJ?PLtJo%uq`iJ@D2(}T3CWkYAyggP7xpYqhQ;)Sw7A2ncSj+p&Q{7 zU~Jc||L(Nw6DvpYtRe~;g^$EwQVyX+E8&1M%18pg^6>`C*f7>A%IPRO)&6f-UH-U> zS`<6eOLEvIXBD}W^d7TcsHTvTQ_iBAt;GI=?`R#I2F*nMfPJ|BHDJZH%!4=CfiA(c zAbSw7$)puX3Rp`SZ;9f&Vq9IEkC_tnVsb9I{N&gGeJSFQCB4pIn82fzt3vOTW70>r z`FC6kv;I-}%JSgK^4||{cAl7s@s5>Uq&+8Xl`T~q%?eKfGA*|J=AK$Ak}O?p1f4Si z#N8IN5Lt!pZS&YxEgn|wcN2o}2K4+)4HwsserVe)=wt6MqZfSx*O2R)ywB7nSv(BC z3h8}9F)61njv{?i)zdGTE0IH|h)XJ>PIQV%5znUN4953xS*vbl`cN)UF&zoQmKd;q zWH@5gkzjgZCn&H@lx#i4WI=af)8;0f<-*La5Ja0_TPwbzD^{%`kE1y-$%L&*3>eC? zpchbyi?KsEv%3o(tLUH#1!I)Nq8^ODjU|yYD=-WNZxV*dpsQ<;Pm4heqS^gn&=}N)y^> zx&-t4Kdqd{G}|oM(<-27ihw)Od@%WktlB??MoE~|W2Q?6F~V>UJNt8+fySWpdJF4! z!88QUL+bqOzGL>jKbQLivga&`;=etm6Zfwe9fDLJ=b1$^yq~!`=Y8B?h^IYEXV( z((@Er5CCo$U`~3d=G`4bB&)d{^DmjM*wi7>P4ByF_=pwB)znY;zO+`CZukr!xXbXm z66qDkRaI}T@j$L!WLSGX&#Y~!dr`gk_F2zeW_P2@?AP9uB|C1_nQRq1&4WK_0{ zd}rtUPGPg(erXIek7tR>nsJ`Y?+W&u0&xWMzAXApPmOIhz3)rJ+%VA991K!fCda(wK=nyC=QF+`kuu zMXi$<7j}HF?d9V%qQ43O=S%rw!}>S%AKm#GrK!wN@TqFr7u?=E;3z*O@^R4ZYoTO5 zDBgyDfsq0L^#DN$TxTUl5EH}_W8+4QVBz@m)FT^nnF@AxQK*RhGPDD18D*nX6`V0W zAQc=ptRmobw)}@FMHLQ!o?Je?iEpJ5ug$~ri`IAEo19@ueJd3~7gtTDkXZ^{bJ9sW z;4h2Vm>o4*j0tDjJPIZG^G<6BeCb7Br1K0yt-+qz#Tu4Q&dXNaLcVy;m3z)8v^_7; z>SwwjuTDE8p7{pk`2eXh(Z=H@!r7LS5*lg2zS6GRCBb~GZ{`pRUH7O__QY^tZ=Py> zvY3`E)Nt|fE9DF7{gtY;nN9PBrA~O8iVV=ho2`lsYYEc14v}|3TbTAFA@VVlHHvI1RNO`x zs+&~wqQCCa=S5JnjD=LR7sUt~bb7y# zUk)W>@QD0%Q4R)Q+K=R;XJAK7Cqshw1@3}JYgET~Af+FIsxea)-RH*{j_$#(1k8LN(>(qf_KHXjEo0Uz>SCv zG7&%6@t!pteASV;(6xUU$=K!874-2YR~otA^&nc@U7(@}$$mFHf-j5&dM2aogC^TJ zx(`*M=7>CyPh7=hQEf;{&qNUQ*!`$mb(eV3ppOX~_Jvd5h=W4ylNHlWO%o9=d;wz{ zoV49%q_|ePSL2%8XZkzQ;5z;s0Su5fR&Zgh?twxQ&u~4`S^Xk9QxubO3ZMj?K#HYl>8yQ zQC1Y5j3kh?nQuVGhiM299D%ho(+*Sb4%tPnMb4Dfq^7N5sO5|ERwD8YIB0}+L&v)% zH?Bsf{qnut*H13i;v>3Dr`MePYA89SJVR)L-#s;6yzMIKgK(dY@fD=_qQlg{YABE7 zZ&VnF(zJhE`krO>QCVv?fxD3IU!&r4$@}<$IpMwiu}P!{mG&}9fe_LYw6=}d$0<*@ zL|``gI}_IL#|p?nX+=W1*Y*i%&Z_(j0Wn;t8KZ;_(Z99~v>&{}^{qMYh1Eu~cZL3p zG)y|mg|On>WSMn#qfgB#Ut#TG&4xsahNN_5;F91FC<}Twy{ubj$1Dg$v^k5G zM(p$bP{hA(Jelhgq_@&)x9_EQsu%Mwxw+&6+7i_ZVo+QSA;fk`{XOU8!dRQZTEaok z;R8Qn0(5e3P6z~Buq zeCz0taCKU$?gIUbg7ZH^{w8|Q|FPp*#J_C#N;SBoD_z{CF8^Zo@Vy`#TT)R&l^)&s z>jZR(9JT);Tg3&}z3j@ls#Su=oHe_Fi}AWB7k~Aa>)jQGFM%`Zqpwe6PhW$#GPJ?`L?E%jwc{J=;R9Sug3WLFL2EM z57{z&1_S99i*}c-6wUhg5ErohEX_8w5d_MJzopgx*Cr+Vryy>FEF+j!p?3e-dc*d+ zeHsrkOZjh}iBs2BQKeK`<(&hCcZC8~kWDvJrc6_?^!@EY{-X|3tQ8!_I?|5_eCyw8 zteB>rlI(!UNA_cc231hj+kN8A7TZ4wMDd0S@2Zda9C1{2#l~sK)c@EJ^Ai294O0># z_J=K5A!YPx7CeFgVPCx-DmrMk%`+-jsWJ#@eYK98mh+Abe7vdB!D3ktov6pN!P?<3 zhd^Nz0$rh)tRD*hhkEe{!{rH<@{Dfv?B0sDbs3;`d9L?@d;G(4^5T7Bf~fs&Ag*{9 zYVnZs5LK#f%r9B~^3l}OI@9E@ySS9t%u zSY!ZE7PLvC(){}W=l)I!)Grj(+42z1v9f*^WdG~G|1qg*$o*)FMrt&x<^TV@07LSV zk)TcvMpWNlt*`%czlq!@75OA@3?q=g&Z_^89NN%Mzz>KZC3&^z2?K?Ho=5@%38qjC z&SzzGj5om++T6Ik^cE7$JVC@ISh#`w-g!_?1TU4K?XO5v^F2d?Pki`6d&0J$Z) zW`*^MQTV@eJSh18H8+{_pk?;mrxHxaDm8FWMThAno~Gw0k6A6n<|OHuYzZFU!er&2 z`&F^T84bnuw#0ODft#`hfE!Sn&Xa}kzAl2vDtk-LQo5J9?&Z`l zR5Lz~%K%ef_D@_u<^tRb^U+y z^(&0FYIQcHl@?~6p#U=cf~!u;R4_(U6+uj6iK&^Y!q~w5rT4{Vefkf2COX=Jz(qn> zq-x&nF5UonLgba5$9WXSc!*g3WPx-W2RM5xyv!_E-$kYgTE-2`AqZ~x*>MTQT5Z0QR%Pz+qe?iVRPQf z>g~>k#1!LT61E14I9g9Ie5WhCRPEFJ|0#v}N>05Pa1l!Sxy!M9g>?nL_$e;((Qj1P zw<;l*o&!}1Vq2fSgYx;_%_I5E!LNfG29yO?Q1-6nyn;jdsHlb9eChi2?7~QFU+uC? z0Zx;Buc(bwOw?V{M(EztPJ7Us6z(O7D|0>uC6o&F;%v*tU~SPs9@Z^t%pz5H+G4_% zwtWqRS9$e1?c}w}j`-4A?rUqOw10>h@1QY~Kvk|Rn!7SI7cgi>N$)VOu#iDkK{l4( zUbc=Y@4$&gS^NoWS<_(hnI`xv_P<^kYnmRhv8#&9e*p92&q2~1Nm&d?9?$Z; zY$RzI@HPvoL*C$np&WnC+tC(lmB%zC0JU!zqVFxYar2>hHR$1~Z1vJ2$2zSV_p9sx zrH$u*zfBt3j|mFbkmgXGb=I8!E#eZlN#xLy3}Y+Eqg_p|m%)gUG|a#j|8XM_tCftp(Z5s#B^M&tX;c<>L6SarQ2eEbu^$q)QzHxv8=@z-kSOdYibXf{x=54| zEw#VFQBSn?3&&~3m96>t%rk^)CJ;rbEuHI9oxd!ti}gbiMast-s{7$`PaAM*#yXdr z7Qj`5E1?@^E|R9`cNZw?F7X>Owm+5o)K&t%QtpR#LF&FOkv!kIrog?O%Y+(Sa7(he z#wx=6CV6y|?P3Q^M|%>VK^OZ>H0 zBk@M&<6e%3(3w16U6uJ2@!uRI01EnosDEb1(NObs;@7QpV&QqjFXE;QR6Vq7;0n>( zV&s|S`!-c6+!S=b74Pk}cVj{gfG=yisH4gV@;?>sYj3@Fx+>-K`^scPbkZs)9-KF- z*f*QMiM=V38@>D&jpqIJfjZl}#tNW(dCH$sfEA32UbW4F$LepMLPq5%W4oB9;cZKW zN;^ntCZ0!gT9h~y{RtyTZ?`MM!43(+`65VqsgcHdl$KapC1b`E(C4zsVd~dL+^Ier zOVX;_a{Y`#kNVS?QNUNs`{PX zo$&Y9C0tG(PX2@;md`!&G?gc}`gyfHGpe)RyxVqVM06Te`^7JCX;YP zbxzJerhgWTIsxg-a>D6G2fL`A!e43rDS036ep%ej?Rh=rT<;@F4IXH3(f*z<&hc^W zUayFe*&;mu6FOnFTl1+uu^zJk94jN0BYKxGVO0q9ryDKm7Z1=7uWfwqekKU=DoTGc z_igLvM&IqLA~HZn)q{+>;!Z*yv( zq5}0%+3rbx`oUs+P;uI2s(D+mCOCr1rJky|W4qY`Ny&h+5%@wxo>dY4h(EaBV|!v= z#n%8U{S30x>a@;)33&iPISboxYHS?cIuU|&D9Lu0!}HbTn3K*AO8MLbsb1IXy0d8S zJgxGJ?+Q4Mnk497u&d71jfo|r54hV<;9}G;3wkl7sT!;F9c~z9{rStd=Np>8i3y;R zA^|w*l>z{=5=vj@YYa@ddTu6#Vh>#1B}k)WCK&<}6RMmUCzdETC>i^j(qQViw=}OE zI43n=Ehi^Vn-Z;!0+&2sBZL36;pI;?Lf!jwF_3 z+GcC`TTd{o_pac0-OFhIk1n`KBG1B<9kjOc;*@|)#vTyOZ`G%q=_h{*|38PB-NxVc zIxw6qztNYtk)*AOHujW$<*>U6kRO!D%MsGxT$Odq7%*{wL&3GyaAa^eC2D+z1hpg| z;37WX5BpEZZY^#Z1&Hf|fu`}>vorlnBK0>SshXpbP2Tw(s7OxM5(9Y+dSOQXQ9u4= z&IuyFRg|SU9An4$aU0RPW9bR^b`-<;d2+|gYW}lZnVsu z?rWd~-L1v(J=6z91h7>F%Q2S%LHA-|tdjq!6*%Jq11cag9{s0+qN~A;sQo}%V%&7& zs?;$5I=tgUI^h&=&C**ejFrRVY07oYoLZ>re33vXKKtic+u}k7tk&Yj zJ68uU!h5uKk_$NbUfE-N$Og#E#ai+{`N+EW4g$6$nr*AU(O~PUEy4JSc3i8;9ctsb z^EL2;?KkqzUw^HEyiNMvu#+Wi8wj~d@T2X4LzZZB;};#DWz{42q_v_OFj5H+IcHc? zdz&02n69-}(fK5&E}XyBQ|YH3SLBjjL%cP#v{@&#JN*zh?Dk=HmuoQ@JA}merlE)1 z-xc8IP)%j0yj~}MK4GDNkDe?m-_&<-VS)e&$3gNbb|us7ZaL}I1){lqXn}=4ufA~) ziqE6n+SqRVy5>N>zbFED!ziC!`)Yz`q)>j9(7zt|) zIP<8x@ZF2JtJ7voy^oVb;46@`{oC9OKTwRK5t$J#lgwX9Mn8jUyqMvW>A56%)f$zo z__vvkw?3yo69#o(!Xdo6=|4kePwJm~`PIGEv+GFBg!)(Id{8C%1W5{Z2$iE2pL{Dc zHs?TSxGMl-zI*XNC+hO09|nDYyMlQZGa+>-L9** z`AtiSO_S>0!d1h>pOD6KlL?~=27MmEH7Tva4y`N!&I76wC2B4N1${q@s4GZ_#cfG6 zU^dT{Hj!4MF>Dfy>oj@P=#CtU$)V2~==#_M0fSY@_-R2pGz-570T$F_Blr+H`1U_e zmLu7F)BPn9`xH8N>1o|<7ywIGNuaZ)f~=ulx-$l_oV4z++~ARUYo)5R38^q|bHB1Q zV%N^R>LZ)8g~w`-yuu0rF2bT2Q`si-B?%eh+L&_+vl|`^Uod+dgu>9s58dMqNHpZ| zZs%6a(;WNyqKIMcy*w@O%QD4jzPi~qg!qI(w#W*7W8$P5{2Bxy1H(d$>K3FKP12n! zAd0+TIeF-}0zZq7XWId}adD+n4h_Tj!vh%9lDmS`yG@o`G&hV5cfx-T8eWn(D`(zA_$^TLl|NH*st z2*=c`4;*t?y;;u6v2W>^bNCqDs+tyKRLF4AvgWa{Ib5<>S+BcF6dp+iEHxfQualSH zQ>(L-@oK;5&;o;)Xww?$qzr0l_5#?1(fvT3T{jM`HuEjatx`ad2&Dqq4y*Xl{D ztQVh&k zFY*Cqih@?l^^_seP4MLAH8GCF`W6vwI3^O2@bt!nA%=;QUM zi4;HiLlHS9YD~lv_Omf*tQ0TVQc^V0WI@!%@eVyH6P(;`kQpY=Ouv2jmOWycILl;p zRFd?V#r8tG!wyfG(k@%(my=|Tk5u)sia9ZQ?X1>9ows{bOe3zgw&ntMZ?u~fU?!(K zpy7tFe!0{j2LF^ab2~tW9r24PkK@@x2w24V%gGs^Jt@)3rrk%?n5_X1O=xk7v9_(A z=%dtsweQLw14ty8{V7~D&gXrI-$a8n!Xxv5o%q}BBebuTRo2J7dmc;k+s|&pRi~JP z@ij|7u3w$@VQYA8_ETl5na{qSbC4EiKaiX0ue%HJIL|AOoBlN?1;4mfAli`(vpCRAwe_$^#gxB405a-9Z8Chp;$v)b?YJ8ium zvgn6uT<@T$wMbUOzmu!2I}vOJT~v#9T+0sBG;?ER+RnMOZ!Y&pUnT1KgcS6wt_X@b zz(M6#^COpzMAN`?3dPdexSCcGTo%P1ywW&$92ArG;^!9vG*N2I8;&2Gf*U#vgVp@kvH!-O zmU&t@M8BOXs$V`4Qx8@mTdE%` z<3FENCf}ED^IwP*Sn1uyC1=iIOo7&JT#QqiK|^B0Q@=y8jNqFc-N%l&Q7UAK9=1!= zTR%yJ#o5YEOiH_m{A1HqtKK|C z>mApmN8<-Z4l|<@zgh7py_fo2&G!c;B7Dq8BG1?KTo{u|vF82usRhHB#;{YBwhMTu zaU$qBPlt@8r75v0U8rC^GKs-gLi*r3`85_id5%|lfe7iQi!I*j`vPI;4^ z?>ccCU9F0v+j^S97KxT;aXc1F2u#-$etIHx?%LKVrM4`Ri_d| zCKlLit4kYJrl(~e?^PXBZ@w+RdC{R!+UprnokkGukKfElGwjWO%kDoG;--xFCUYnR zgjEJ4GK)!LyOTcgYx%}TJw8qV4EPBBj#!-}Z9+_6*{EY#PgA1JJS3ZAA;gXP6)lWC zF|{Ic8pJz}kNK1pYH006DD&w&jZCr+YwU6Ikv zP*RKYd;wSw0f}=F#Oms}x&y^y$Y`Xw^cr}XttKMmZ(Unj57z-10s!oOB{=v)e38_B zUb{wHSK2E0X3K^#AbUH{2u#PCqR!*{x6RL!3*D)X-)I^7u8W0&aYU{zbI+-?Sq29= zV~j+OzuJkm#;g%8+Jw8IO3Bp}?)A~vkA1JVH(5iJTh&?EN!1BIS^g>3>F6b|)JaF# z;|Ci?0zeit3G*P#eShReJi)B1v2v`UDlf4TfGcZTSiL z@3THiwX}Xy>s@IK^d!-aR$!>|Vw}_*JEYI`VV(>Sz=Aqaa6(YjOApA!9AOPmS4Fg$ z0RM)E)KUbIuRR|iZr6YM$u%0zRe%EQx)Yls?b_!gqn(0A9s!Fpy}uUh(*AI8ew zbUi4SPE<}pC>u-1*?M|qz(&{(rA^kG+Ogrxk&tRAKm8b9Wf>=WZmO(+Jyt7uS<|IK zZ)6+Wje>qCNIB&mI|v5@oiVJx@iGxwi0U(f8e~pm9qE+I`r#EH^%l+~S3>>jb{M_9 zpaiZo+{sNEa}@Lr(vO=w;=_&6QLAWoH->Mp814wUMX*Ot^C;4ScOC{S@XI8v?gPvF z&$?~DoWC|7Wt&aj#`a$Z=$aa@WSdadFA5pJ!fJA?P-9_l1oCu__+W>y4ZZBvcyPbE z4)N0X2gyh%HW)woPr+F7Ym`)%%{7&b z9aU#CCE!iypNi?43J@iVq9((8jmERj=pWU?4;lNKEYQld^;9624gnVOYWT)yxxYkk z15Rcyhqp%(!G_lcG1k`hT_g}d&ENqxB>2|5F3`QqvznG1XK5U+K-3bc_#O9F_(B0n zD_QwwdLepN4=!h=PigK&EYhk2FNQFuXA*M}(=95=NFHsO8Cx@r zw%Uq5{|)xwkQX*dAQo%#JQ@d6%xX8}4}z}zp*IrEH91{PF5?ElpN%5S+P*Z2L|m@( zlTYa{I>`oa;S+Bn`BK5I>m>Q?gUH&j{@&-_FOgl3Wu2Xz8g!@ho>yr7};$Ie})vek`M{{mxQtP55_+-kS9OXDSpN7&Q^&-jc!SjA9( zdALUb^#{J^#T?U3AppOu=cw*|%YCts)qo!;Vb>wQS&hkgdL{Wk4@anm&Vb_>^ZJcS z1kjn!Pp+K_|H}8SKdKPwx!%l#jWE(~iyoyPu<^X9wVC4cZZOSAu+>9ELfgEcQ< zGiGIOY23M2E?u+2uS6Zv7PV7LsKMJi1amZ``et-~2XdEcVqr|4AN6vWu z)9F$I%$r$v z?0DcK>I1xG_X41blooOn$MF-5kk0?Ox%wQae$?S%q>vR^?dBm#d4Puk9 z82&}!ALphXYwTGuXNT~N+ypw>jpf+_L(AFEBw59*iK4H=9>LJ%V5`0WXD`bPFRYEV z*Q1qdai1mEgr)YREbrB?TL6V~v4EdivS}PXF|}(57aOsaTwCoV zcAqai45{ij+8{92dkbPmNZP>$`fc~8lSd>D8R_Ir;HQffrm$Sr8EUGFv728uIH2Y< z+oo1<6s_+I63OEO4c6j zM?(2@;o{ZIAt$ef-1&zu3~)3ODI70kT9yg}Wl>095m@RU>cHxqmD6oov#DVVNW^|2}9@wk5uUVtLA7)Df|m%@q{!V%R4eTy@ciZJ?$ z-jj0ZG-W9-{{;|_W>5foi^p{+gdg556jz0392lul7h&r6EV0J+=Owytnt#*BB<^Fd(2IaAM~dw%iBZGa@9;9(wLNx*HsOf z)!qvYE}A_VE)zNhzRQM$51%ng%DhrG>b2c_bto|0mxe~l5=iOj9)CQYOXB@uK?KM~ zNp)og8yjaH-L?z3&Sd30dj(Sq0U}6o5g~Ta%CcBWDY?}0Ny`lgqn>#nN@jEgs*Z2+ zfnQW_76?h2wn6(SjyBI0HP#{RSwAzH7+IwxQvuLLmeaSXb)joa{ki{!ny!4fI+0M z3TksFr!IiYU&rPlav7r_?Kd0NS`69!l@tfS2#Q6_Xj7Hf_Uv@Z=7Z}%?OPzF^dl7kEcgFM!&X>FNDL0ee z!ZPe9xa%Z~)tSI7Y?{?~#_%84$h;g_;SKL-W>@Py-e`P4#D`Nvt=vB~9kIQ<^n*$y zy$W1QP@nYV8dET30qd|f8dsD?J)FUbR3zk@^|;orH<3g&-#yYu4| z{Z5>Lit&Ga57~`aD;0c6c-rJ7W zOld(#RupV;6EsQ&20x$w*7+MesFbDLD|8)Wir15WR3;wnv;5j*UIV2uA>z1tubk$bA-D$AfWmek=-=t;G8jH9Mw= zYc%9SDn;ksod989QD)nzkHW2ou}a=2PDlI!5rP=S^Y5O^&b-ISp;w6*;N1zy4o^8* zK6tl6Y7)v$TrPVn<&t|7+AX+zW+a*J-@Z#iXOeILvJ-IzBh`+b{~WqUh@_SehioSh zzKw7hV7?klcJwf9S2ta}2IZ?ok9W~d5+}7p%68WLv>XIHbgB~F^;rx*`LoxOqMB}l zJU;y5Il)z>WRBnGT(OX#DCA#1RWllICjFQ*{`1+s1NT$wb&fBG1B~@&PR{NJaG`@2noY_RFQHJUx zYh?re^%0^JBQ%{gF3b->wlr(4Uko2EzFiSEKremNjmMf_spGGBxll440;} zt{!*l)+KH$yCXG%aK=$p)VmYBk1JIt7r(`laRY+n=b^8c-!p7-UiJ(!G}ku~$Q4O; z*Tk|Ov1+aSK$-Kva$Q93ek;brbw?;V3R?p!dR>U^Ds`p!;VOc7`fj9zW7&GGeEb!QeMAf&;~cn3Gxj^Xn>!n+~an>PR1+VH%&v-cd2A%F^Ws!7-6EZ)DZ>KrDp zbWw(K-@t(cn(Dl;2I9;W3aFyx_T@gi)+wwInt&yttAU~ZPKhUZd@Kxh(G+wxIdlC~ z+OeNM%Y5MW`L6n>P5bF2oq_DNUqJ@RHla`pai)(x`Ku=@&d^axmUDesd{90Qo(SM%rSeXhh6c5q z&c}URT}pY7{7F~}`3#vyr%{(ZWsi$gX#znizXCf{w$3$;g$gu~_dA&*s~V(`j8nh0 z8AqioRtD|hhg)zCmRqv~p$WGxHX@OAIM|Qeq6-UusP^@FWkKgshIly_xz>RhlNuo8 z8}LC&){w-BWtv+x>==z!4G*FbRO*LJXZ%bL?hNoVU2*OBp&fi{v-V*8on5m)fN`AS zZNoWet!Z`JiU;pu4_U2g4el)#Rk5cmwC9B+J}YIH$B(Fz%ha@TG;x%3A>fb%TwYO4 zTzR;`#7ABW=ZVGWh1$b{mf&)22>0Q%=0Xt#8rBR7@JuZO<@E*1!c^bFsuPPLBh1%i zQm&LOFX$vzru6)^q9?h@1InV2#)=AOtx!O1aiJ7#EQm5fZn+B4xX=~z0mp@tyn2{mF3nrsD!+I$JC`MclZuv%lG!Q zrCYuE?PGZ#f0wO3Q%r*c2~KME!FE6R7h>AcU%c8tRRLqEe}63ytN* zreCHpc+9rmZJSUehAF~a<|H|+q46%+Fu*TTXZ7GsF>v$uUTQ^F&kW( z;F^!aQX_#aW-+|qGlCFDB65P#dTZJR&iY^1^jPXXRtiAO4}ixh?1=1oDHajT{T8>D zR8w9=G#OTjz#MfyG#yrON?I)La|nL&x!CK*d!gQ6ueIfLE6fj32&Mnso*(n*w67iL zR80b0C|U~ctt+5Jnxm%O;J3-o;rS&b+;%+~w2j=XOkhztNshUg=B0C#(WRhv!`HF}mm^YD z9X?@8g;8V>=VGUM4S{18Fr_h~6w+aUB=qf$3d+0HE!(6@o0>u=!lV6A(#hXQ2Bi&_ z2p2a}jd!N)#H|jk2T^J!3l`R6>ATE|lX}Eg?^&BUvrFU!j5fE{5p-x7s;3QMo0a#i z9z=%?+0kMROz&9L>Q2mF>Ay3->LtOXe*~#I*L&d=rR=!+uA{utRoB$2|NR}?ZD(u& zPfv>5g%{1abMHO7WeV;=@=V{$R1fSaH#E{~aK1;7rn7r$+9U_Tv{>`9RKy)+rHoPU z@8Q&>tJOFzs2rMxG=#vA14fCeVbj1(%St+!k2kLw4(xs7Nj=vj;<#Fj(F@4xd2IYgHR$lH=j+9RDF$l2bsI$f>n(Vm6LKMqYF%%Xy0TJ(h{ue zy96UF(Pc(h&Lgi~qH*14LRbNAIMokdR*_{V#lj^fW5WER`otx`$=CM+zLDz?#v?T~ zvyc{ZwVIGku0mS~`(RVKT8YEs{eanq-NzJ0oXGy!=(i}d`D ziB_k>Lh51;>pUhPacQGM`I&^Boa+pJzL;%ULHePAUV67gtDE{3bw+4?`#pj8WESB1 z)@{z+Vv4OeBn|?_8_J32nw0)fIrYvJAFp4{vM?|CcyoR^48L@1iuF-S^1YTa#*m2f zga=!{Sg?N$t9blqx5k)f01pwM#;nEo6NVqZgfwUY`xD3d<#Ah!uzcXW9Rf!96;MwJ zvNqhPF1u^w^6hQ+Bvq>Bn!nh6lIouOviy&xtZmQNbN8yylThwndiAQ?8R77- z#Hh6>T-4BaoivAj8GH3R;qK3eG>x9RpS$`TTWN`srysgMEB*8%>)=c;nLo?*>wLGl z`ng$ajdV*5+K)F_4hisQ8}qDj&gM;?;eqZ@}pJwSfCWfOXN%Z+?+Qa?K2h@3EAx|}5Hw(i;DNW< z@N~^hH06{s=!F_=LG-VH#5o?2+lf90@Dqo*mqQjXr)g8ki1Fk|4} z3*V1$>#-f`Pk1ka_Oxu05pfGP=ec=Y1a_+xUWj^S93u+8xEodJxHuxy6`d#BZafF5{nyLvxt z%mDv0f7o2O=~Hrg8`{%iv98gsv8?|1t{m&-e6=I%L%p0AZLk*46Sgn7Aclw#2FeMh zw4D_p4g(pOzj>_r9!fSU?(uh{cYbWA`jX==vwnc4-AffmuFrL=?MS}I(ckP6 zhU&2%w&gBc?b0EoCm}Vl@Fg3QUv=tU9Eer<*P0Ss*?FBkQJJu<#>>fx`>y`xP+a;YhFd}f5Y=n8H;9U;%^|U%gXg0huohE@1Z2exs zCSydo>C!hIj1H_O5hp{$88u$1z4SP~=u2U1WDLyLpsTS?(5SB@C;c8Aq=5yDbVTMG z86ete262Le&&kUt_I`M91~D2jf5<}5BwqC!IrJ!{Fs~Mv2=ZdO-@bLnnvtP7+i3I= zP>nBH9>b#AT?%<`Qw{xU19nVme2U#Br|d&pR|03vqK?*oXt&A=y_I)3;%2Xd;q=O! zBT8qdjcBD^FD|elH){Xu_MqV)t{)c;p*w(?Wo_3u+m%)R)UO5~nXAckdQkOrduZ@% zxW(VGiMj8K_r#;*Q(nBZ2TUi4dkkN^S^cyjLN~qurfTpJq%~$|XmxiA{kKeTv{*Q6 zRFw#_wJ1KiAcI&z_eFhvWuz$L{FEL4RhG;!UI&vZ4x`FpjvfOv3Pdu1OazsO2!hKl zEdc{-J;Qcs!l7GRsW9sHS4jt2ET5ckL;%CYhyTYyf@!L3DTGXp-_f%fj>1_q3&z}0 zoSnA!3}r?6Q=-`}C@C6TV2wgLy}*6TV_MZZvzVE^y1_I$x|So-==4%AO+Vg6X;;** zPKEOFFLyq1edDKtKhKf{qem7slzU#!-HLmLZ!cFf$*r-oGd5bdxaT7M1zBm0K0WMz z;7XlWsZpxihM)!L_ z@(55KE);jn@nHJBd(a&K)fg7% zey=Y6xZ0*pTJIw3J?Oi=c#71PP`=b**Yf!=(+f!yQj3WHb)2}zR+7DM*h+iuEfk`L zpX}F#fP{g8;Fd8InnzS~hmsK8cNBAn^A`t>m0XH-UAC4lyre7T@3ehj2jvcZ!aUlL zPznOqp-goX(3jT>+Dv5_lEXgygJRyG@f?-ODnn??<21)Lhq5u+iC{r>D=MXO$bG3&qdjf_DAM(XAP^dcp1&C(`3&biQo z6^#aCzGYrK?3lff$WSTMo&dY$lS14NS7DNC$*D19YJKtvh>NdKH>Vq#vWF0@OTq&SA!uS5OA(uGwrNe z>?Z~CgJLii#_pp+FjA(2umeDTd>A1ddBDH$Yf!^7GPh7?|=JY{NqqDqc_w{x_qOG3YZPZTUB4XRa4}~Mnp*QIBVYMh|L?tgn z3v?Lj`w3tbETe|b#E@{yLXw$SN@Ckx?7=}-6s9f;fPw-sC(3@|sss>Ntg3Pj%i^jp zB!CzoG?nbr3-O)G>H0GkrnMlAueNmLHR;z?Ij6C{dm-wfH(3OR z-_!Bxqb#7?ZDBL44NiDBUksHPX$jZAYLJQw+*PSd?Y%j6nIoQ}To$o9+J1?Z_iUkq z<}G3?&vo6*2fO_*G3rP-VzE# zoeE%W?inV-)PeTE^4`X&KG1{=?n>|kBjhMU%0Pj{K&zD9b>f%;SiRQT$D+RJ?mPx{ zJgnZ0{s3=%4urmKMhLs^_obf@FXBQ%+v53gm?6HC?`d7_)aYd1KbnZmh8MH$)r7VZb6Hz)hY)9eYeY7nkS$+nF1gS}x`l}jS2 zP#Qwy0Xxc3n5v}&rlOrc?~&kNZ5OMr-f2Z_hj#?89P}s5zH0&pglYP~!Jhs|q7PH{{S9r8HzadN>1HPLi6~Km`xMXT`D0#Rzn-kBN*ER${`c;o0fBZOq|?$=F1lUpukO&?yLl|W8Zic@ zNfCD&aZTz3lGig?-3mTaHjM(d>#}JLd?~m8F%gTrY23TFqF{(7o!3Mi8XR`X8RUXu zZ1(n>%3C&LgIsqq)#3H|Q-zW4${N}@y3fjIwW@!-*^4Yw$B3)7XBvzTMUWnKqx4=9 z#o!N^(3irk?9qzq4^P=oExIA16&WrGGBsl&g-nCvS2eb)s*~EP5oIov zv#a#Zy(n>MjR-j-l%USB8@KF!X$5=Y-%r-*Jp&qRcix`1bTYlSiXgA8X512EFk-*- z`yK_$wY==+s$bDhtux2_uOc{m`)t`f3c?IM!iG)xeDTp$S=xRao#?3-_2zXGNDSB{`XX*>d8K@A&KQHz$ z)Oc?8-sx|dWBFO;)t!au@GSjkY3q93#9S1ba=DSQkqjm~5oM-) zYFnY};N>h^(XI3p%xBUYLIeywiQoDf3RwEqvfAx#IlJhwi&O zpjukeF^*C4j?(MDoC(Z8=Tvw&ySfq*bhPSZu@7qf8@ZUJZ(Mz!6&eosyN}?}KjArH z-NJp5XH>ED?-klVKAx`X=cNl;Uoq_1R+p^j~olDg2Qe6)o7eDwQfklmFR;rD- z+GE)zBNZhyQ6_IwTOH`GFIE30x`4NcH`|0Kcw`^shnWP7LHx=s_%&FOmU4^435+^w zm(7Ts_Tm)*j5(QoexX*x{|5V^dMerS?g*`)ZoBPex_!fH5kEH~c66@p;(4fO2jNB= zG>JZv7UDA_Lh~wz?*faGic+*~Y;ZM}Fuz-6_Z-4NhPn_;<*FI#TWd9g{1=j9Y}gDm>?l_;|s zLKk|B)=3ar@@70yZFS_jVSCUw<&++MB2%k}`dWZm?jXoc38Pk6Y?b*Nf6nMH0+jG9 z!e&J6U8#pqSY~s&;S^gj8S+`qRg!G=Juaq)Vv)thAOxE{hI<9w21()6gA!xj(RI@Z z(%9Sl0}8H*uIYO72VEmu$rN_e>?8gi`9{}Ey{8fEI7cY77|1+|ID3aP79ygT!5l68 z%s~8C>#r$fyrDELR|JQ*zhG$SuMp6TGSR}LGVYr*taFy)Pk?ma${a&ICgC!X-tbrT zXz8!DL7{hV?O-bD#eg27wJ_FgIRFA^Nguc3Myet46ySJ%{>j+HzH7W(CqxyIx@ z-f+kAY5)!O1i%6%0@-Pb4JG2Tn#lWf5XI3$=g?~wR8&Y5HN+G$o0 zcGuQdLj~q}-E~OzWrMiWq4B<4Bu2lKXTw#jqxfG4!3cB?6=5?_UJ#(fTRpag?5`mD zv#(nVcpW^ROl?EO6BCSj?jPsw&B%$5$dk`pxEQ|hoC&`_aPvty3{!E8;qO6ulDn*= zh2?J7fBgJ}CKs+j*a2L~7TyX-Z0lZ4t$%G=29K~wb!r2lU%>m zMm+~T;W3uuLQUN3qM{uQk+uBY9u;x&F}+5s`bK>*Ek00UX#lilen>(vk28=CSp}@# z>OhCb#n|C|x7)-Yu2AjL-(Oo@_^xkQjxXPTfmya?UHEN9X=RSeP~8bm#V!7o;1OS3 z^HuSk&|a#Oobi`Q>s^@P?rlpxMt(){cATpt!X}j4g@)wk!mERWP3Y@4o;w=6k$X(4 zvic@1m9NA6^9L?Rx?FdHzsZv4?(RwizI~n(3ql8eC4B=<1LLnlLv-9{(Sd0Dw;UEE zy4ut$G){0;2({00QPaldiu-f4w7Ghr{^|6vvD$(DgNwojPVn6h;ygW;;ld}*^TF8L z71pta&m9hnr-SDCTeQWn*Y(k>qi<)j@jp#8e%|g82R?aYV;1h}3_Kkq8jJXVv2^O= zaa(p+OgyHMc3!RtJS;%hZ8@i?a%zo_ds#!CMch2H0q{)HErUHUDW{I>R^~f@Q#M_> z*oowIpjwK~x1Xw5>PA5(9wGl@C9pzF%QBVEc1qJ)C;TX>OzZ!oJy+`cgw@Ne|5US7 zqrlF%5DAN%>mQSg?Zb>h?SnR}f!ybavo9?v{i+VZOoOz+?_Q(geuTKU(aqfm6r{Zi z>+64jLzX{;tJGq&)nOgB^qgVOe!!v1Ozci$b#HwI2a*p0560f=ZIp!E!>zDzQw-U6T+laWW8Ycn!2UfLM)RqD^XHFMCq4Ka`Q4Q}7|*MM zvirv~YNe90+y?p2JWmKRKlawkKmau}y$u|?75;M?rHU96lo1hwR2&&V!5{E8_FAK-U(ynsMn@R)DPzqy$5&_uB~#xT?4tUmC6BZXi;$GK(K* z?yzYTf@VnKMFgwu^>C?SVeYGpO{|^q{%a}%vw4Fws@ZAa+JRhyyaON~h_Q^a>pL$B;kdnoPkU_8?hhrY=B1CoIahC6!?s+U z4-EBWUm=*4ZYKEEmi&-p8h);Jc&_C0`BG6{`m2&=x;N>! zb4Ni%jNl*lbaQNGFfRchmoUwfaWkmdhvmNsF;bmyXdRm#t)srQ=mTetGw}_txM2my zEinVvkmkCvq7v`Ok#`}?@qAX8hm~k-j=YeC&=6+i0>X6Hji0~9^OeON1gT`9^+ zb$5cd2tPj@;9brKB%&UA<;a*jg>CZSMu~+siBRiR;}Edn_csiQT>Sq@~|Acs=As?+x>(J~fLyF5mW&CgPUqjs_!b zw#O>c?`CN_zoXpa&GyHYcmwO2-+S3w&O(s9_!Q~cYLsOxWv(`sKh6mrl7kp%j&)1m zvHfo52FeZmw@j6|HCSN}C5I=a>--__H+n-y>ryLb-yMo;BA3w1RU5HD~<6Zwq49E z(j&dKJ=kx_7(klE)5Fe)Xz*mrt$cnkKF6@gX6}wEdmn4#pB%^>ayZ43m9bzR)tC8B zAbJsTjbryD6^W_k#uO#?J>S8rbo*$^rW#KRH$rc`Ig)esTxMXhbt?nJRP3~;M%-(C zDz-Cz0y$Mmy;klo7wHbNFF$67{m%10c4c9{`5zJI;2^{HNsH{SN^J{zuOl{~iz#8$ z&-!jCbcp>574bm|oV_-TWrpk!60rzcH;M_eLO)LVcxn4wdDeL+ejBws8SKMnD6a^r zeE$XRJ|J-A3MqQ!^BuCO4W7Mbr+w$=M&xiWKR&9P?i}BU!zayY!%Fc(GMNsBVAOFleZ^B7G)Atr>_vXNV=7?*)(_^Ht`Ew63Uu1JWl8L}fzP7mz3wuQ<^UH+4a zj;bVazaAn3j|*YsUY1mAh>O0ux;`p>Qv6vUC$CjAX*xKB-UtzB2u5EFt~AZ>c0Ji$ z9xN&;cwOkG`6{nYGST#N;y2jc=(P?oeY*PA0h5)#1Jd-pfg}$wr z)T^RlvZSj%JX*Ix5xXK+s5@iBx8h6GWl4C3yEc2h62?XRF4~C)63fU-e$y2KoP^b~ zmaKfMY0Oq@x(ZMA(Vj5v)u4R_C|tb)J4!Lg&r1yO?jp^Gsnqv=_=jnE?f3 ztrQ|*0nI=90}vAJwMZcuU49qmq$U_J(0SonAPFHXkRljQ@b&5FXGEZYSP&w^N9+;7 zZfEIMuoK7~tbM7d#*DJCDLMti-t&K9xjsb zp<4CV+{Z6I)e208EQi;a;}EKjR{t{KsUkW@Z=c@AGxojN>1=(Y4n?aSMB5(fT-0#v z%VyDVl!^7BojMQ$3n%J&5Ip?Zz?tk8d}G3cfVwIv;H%USuQwH=_u^TM01RHo)Y^!2 zLNUB|t5_Tr)t#bv)(>{QX4e4K=@?Co_a&37jwczh8t+&shBRj&^9?Qx2%&}Jpq~dz zoo>zsG=fL~1I%SjWonGoc#kzVJ|JxFWF+dlNUchjD#%P&XfMMmHMpjRcH_?{Hqw`&Xxq=ccYdH zs?v$TTq3qW%EOU$v4bccZ$75+c2T)boD%Vf_mK0A5hQu=J3~Bn4vjE>4EE5=Zr4XW zJFd)+^C+en`0yrCA)K-w-D;(kWpq(}{4kBytCog#dgn)s|KtJ?-Oa)0BNe~3RZsewb5o|mJ?c$&UKBr0B}IjU z+n^kQTncyJ_%s>3E^?3FyJav(<$$%;Go>s(3VXFx{!*X8F4AI3_ruIN%YWe(r|Isp z&nJ~5n|}5p`Y}$ytJp6wl*e2slb!QCk&+3r4 z0?C?0pF@}Q{Wve%*jxi07ZSSq>oZH61mg8fE#F`t9JXT@N!`>o8&VkwV4fQx&)s@Uq{w2HUr}FsMbz-T(?rl5yB?V0bq+gf_RdBB z(X+Qj4Q^cC>W&GicP84)&gN4x@iVH-(fh(JuvO>X(+=&-$+GKOHLmwoEVsE>U_ z;?QMZ2klf%PptrKjaTfN#WHtZpyrSX=2JLLdSV{wPr_s3x5)W7{K0bH?H1B`MKkD( zoB^<6>UZ*^Vm`=wz{-22!IF@Zrfjlkk*h0TCyxmO-a?MXa03%ni0Zk9CNO$_)maOyBJiC-H zXZ{Y`mg}~4RSkIB9fEU}<{jHsu^;tpXOncQBhdE-H-b{qXH+tSr{*GhFLYWg55N$~ z&QN@vOD(Kc$LZg@a9KTJ(>BT0A$2Y%s`UYv(TH(uZTvZI@F5q)rs&ER1vT!uE6A*| z*fJzSB zhV#ft3+-3?+>X$?U1#2(A}?6(S4-!@(qfqx_5O2u!a(}{{4gkJk44vnG#GAXy4oA; zlbVR(E>%I<+v3*=Z9QtJzW0E~5qRNJ`QWIr{Mk(gb9ae7kfH*my&t6&{tQxbwR0v2!^kA=75dxy z;8zZI%9LM~Lu~BW<+SOugB}_ap%e~MH9Hxt*NhLx{4ml6*nz2JCH=OGx05{Tj6eFx zi;L)1YrT&H8ucbz?IXhAG#XkgxbLta(`8kCGstA;HJPvF{0#YJI60*&{&)UE+RIc36U>|W&|5(B}dMG4hT{bX# z{XO&&y;s71)&I^bAhPkg8#*ZZ0NJiQcnj@kf0_r1$c$7C?vS*12HK7(E___%ID`MI zrf0BeAhZ!qme43bf2BU?1Jt{k3!K<0rvaH5h#|&T_WD^LzpuTi^kUF$^K7j`o6?Tx z8a0Z2Wcj6O>s%a0Xq8#O?E&=AZ!}fX)XXt?vBpVKrt5$JTIFn+7r)vxJZFHfQnxN} zpQzoSlD&Q?<}`(ZLd1vf3ZxlneM`2A+XeakZ85xFw0RFsxQg_?d;(5-cgs<3_DLS2)y@@ZMX? z6w#1G-m_t?rM*|GUa?yVh=H=aAt?JtK{$fIp3W0}g}&`#?2+dD8SPv8i5W&Lx-d0v zQy7C8L+C4yE? zNU;r4f-Z82Kj1iYQ0NXPMdNB}eqlW~Cg?rJ=!+gU ztc&l9?$$ynJ>tQf+!1(cx4}yT5NA+E@Tu`h;XDkd`wXqM0S6yj>x74N;`zW;38KW6 z{>DiFSv_oEhwSwa>bTdJ`jsB919Fs`Q~1kgBXxLF8);!o<4QIsDV`p;Vq>3^Z1Gk9 zpjknc+A&&*3dS69V0Y$B+8iNa7>b@p*oO>8plS7##It~`$()Z^Oo+3nXtc+@{I{C0 z))IO?TbaBxj0K)8$a~$(U?M5_q6cYj9KB?s>K{FKJQ5N~VxHc9bNQ z(S6c|6Xtm@>q;2v=ZD0e;guHOQU$dx42AU6to*ucIY_#2%oZG_2(>OlX6g4thr~sK zat7E2?;cyCKw5t|wElGj45VLjf4?!!g7cI4W@9eKYO8%;_EBSgw|Rh&=;*T1rhTPg zx7ho_+^c2VEZq$W4J>wXqVFkuRQhq}c&F4{`LGAgWP^Lg*sxyDB`2zAOdm|`#aCotc2KX`94^$`F zV(^Bduwe`ARmWZDj^>Abx65w}XSrhbrIaiz<-?UU-MN+7xJqj5mQBr>yxK9_-i+d8gk9)eXImxdf+33;;{Q(4^H9H*O#qbxIYBsf7-ENwP`LL~qh%A)#>gl-_;9fv zS`}Wp51%<*LacwZ@QIW&bd&W&AnBQsnP!*k$|q<(@bDlA{iQoMTp?5BR#wb zE^Bh&RT-(1u!Z9NV0Wy9MGL)r$8^#+d;WBcU!;k$CyhV;`-SV}j#R39ax&U*i>r0D@)7p|QA%$%~3 zrB0@+hUp9i*(e7BHA0u07a8Fifr4)ZFLKZ|@agw?XKSsyPVHpr4l9SvaqK~UmkLjF z5Hj2GKv(!E2af4g>LO_VY(@Ogf`DoZVEjSFTj(OUEgD;VEh#Xa}x;={hV@;B<^KOf4TGCaT4$N4zPFcyYGFuaJR$nB8Zhru6yrq zTi>O-nwQ^V`uH4bR{jAJi7rnDKkip7(VFA%76JCScfS|}tH=@h)cFj@y=~CZgZi0DO^%IK8gW&hsAK0Fk!~kMqQ!^<5rm1{y8J;T}2f;Ec^*(*Cpt5fFC;@6*_Ry{OC`v zDcXCLo#6ZO`P3i>$+_YGRCUGq@pM`6`vJ3GeF4VE zINUu+J{*KSz(~o(l?tGn};hweG#nVP3xWgZr~kz3RP* zI=`xA*&(=wv?*6iBKpBzo8DY3|LXP=9q9PBbGcIaw(55vKdUDA;qO9Uv z9_p?+no}P#mT}NOZGzOG$2v8L6K)vbw@S4CY_Oxp{=z&vy10DRdi4c?Xnzco&5!L9 z`s1*8(!kU`Wrn=m-pX8;HRmBAZ$}a(B0h8p4a*Z15p)vS^1g((ylt2J93uQe>TAB<1T(Haz+C0~B$p z6`<`C3L)6UG}U!el>uDDU+v!>avx_{-~CGW#pm38`ZPO zPbk0S($>NR*L3vqXje1K6%nTTY;l^8B3wV`-nId@M{%d#HVf6-PwyL5iDw?9$f7$y z4*E3+v7af6im=s)frI`88Y(iVq!XMiZZWngSpvuh?D70pLa^7hjLACxm<(dHFS}cU zFTipVAV$&RbRRE6j`5C55`c#V)@Dr!9{NXi>)Po-QbsGh^*6SGJ*l?3&o7d^L1Md} z$%{E$dI-PL<+WueuE0*&YOQezzqdmI5((W(-6h!)P{Do?YWu5w8iinq=`v}&*V8mO?5YgSYP`JU-Y( zhSht{Eg4Jn*FCwUWW&p-mU8J5QR>V4dXc*Ej#f3^RA~~|EPB#@UgzzltEwE`l79#H zF#=a%Q0zp9nSzC6{;B#W6K540G{7uDa@IhqhSVdg^&`B_#vsi{FRzD9B$W=C8mJj)^F zztR%OD%l|9%=U*@$t;4<3SfSQp}^ROgp!O#>ZX(CLk!i&VorU7LgNj^EiLVxW8pXE zkv>BFJCs@k&{ptXWZ@H~S}b)xp0ULN{VNPz4=Pldj2}>#PWzw4VFap)26&0)!kslM z|J@)11}ci`&{<;W!o2gbZ7l_n1M+-OXzX(zX&Iw&={aVeA;Bxpk>Yba4^0$I`P+Nq z8y$hnAd9r9pM>x#yslU1N6DYAVNh9KbJK<1nT@g^br*cl2w_AK0-yH(xMIsGl$LYv z$k^)l;>dbm*^BnY4Vpc<$Y;)64ZS*5s@`kYg0;Sw6@Fd8P-M%!(E;~ZrlXMl8@v{Wo;u{0-v{PX`RZS-ZxMplKZVTUU^!I zxFa(YP529s^uxp}1hQ#UIrS$Zafuyad|35&T&p!^@D}k~EVhnZjM^Apumpv%VG{nb zqd7%XbM?UCx+VTo0sp7~9vD5nW!VTUW5$1T^Y7;GHRQWwBlUbE?nM5bDh?mDOJVP2 zOm>kA!DQcUV3czOIFGFR|L=K}2>2buCwXN(_fI!dWV>Bfo3~IVRVt>^`zQkjl9K&_ zPwRskb(dZ!LC86%aeym*_gPji0h^~L5uZepKNjbC$2sUaqq;%}m|*Fjj%#rtmhWVc z>_jueRH~K`iovfR%Rd)5)v_(aRd&SRTj`yp2JhDrmynzyJ+DVNDw8eCh-j(*leRxa zu|@bO8_>Ti_0(X#yc~rpx$|5A3<11pfy&D&NxJ7kyX# zIEPiR1GwN{4J!g*mIcyYQ!D+R5F~d&VpjQyVJAS(MHo8t|KL<|cU|%iqV&65rFqN# zbJqXgi}f*mxZ{v538JQ%(l6Qn9}oZMyZm5_@HXy~NBIA%$NycNrUSF2z{a=_;;Vua zdoKFlkM>8eM7j%V*D8x+)c&{ch@fhT{*kp#V2Hv0557cDd9Y!WnaiD3V!y)tuNk8O z1N|3gTR}xH|E&Bg7+?s|K!OR!VUC#wgkY4nY~r5yg!7Jz_APfohZvMM#Wy1@McNde zxMZy%CXD##8^M*P_1aT6=rR7def0Ejv=W#cb_gu5$b#rAJxhq1dH#%94bN%1>?m>6 z_8%x(N4biYNX`m-l9}C5Cx{`LGcqEeN=d-9*=vTJBWhdWNN+ry$#<7kUHy`=t0FaUkr00?oroI& zP+_U!ouHhUg79Ym6ym^8gCNbfM1B^(@AtBUR=y{ai=j2vUjMA{MuqNtxxAAVV+!zs zqcF4D*SxiQba?-)MBot9GEC*EsZGuxjF^G{s0S+;Lb$3Yml~aIXBs-Y(00!yqMe#gVDmA;PG6lub{Ot-7W*u1qc}+D#6>)Y-sq9PPwA`zR3t2&}VH!>(;% zqeRAo2|3w?N-s`)JJEaGt`d*s1k)+Nb!?29;9X`nN`X!{wq0FIx`iJOg9kPH2kwKH z0Jw9;O4c`J&v<-LK6;^VA+)~#y>x;@XlP7N2Y&eq@qbKvo#;O!CiU(3b{Q<)qal-s zl+%Zz%dO*I+}^leJY%C~{e)5n;9+Ij+;i%AQjfk&A{YBObj&D~uK>nLmE`{e2Rsun zIaPYf>|ISu)i?va@C?=0*$q4 zqn*8Jz<>`CRF8cX0wistbDo_2e`7#7c1En0$ZTpl@7r zfw-xl)16qe%B?XG`xCFsobs>#**ywSrkQ9kHQQ#IcYL9pa;%17VuMF~&#(>AikkBM zN@AEpATuOD;sn?9O9se%RPTV8dvp{4e%%sYLu3{dgyJ)r5(zufB073>UA{L1b`8pw z2^K>_dCjArPgW{IRBexw_XW8r7ToKS14XO8?vVYXPwuwRZH~=Q97bZ4 zA>;NepY#%MgmNiHKX>RT zB1=W@N=7mUfsRg?Bgz=FO-uwQT19&eA1IfA#{Yp_kuMNQ*FjlpxiuEU-%(66!me{i z^SENB4^jD6hgSZU@riDoUg%Fe;@7k2sconYbrU$%R*%QQPXZ@z0gvDeeJ$~j*rr6^ zBy9){Gvj>{l;N}-v6S_=LrXDYH=ri~AWPaI7VbJCcDqrq)a!b{TkLFiZ1P9W5&x&0 zYZ;2#en*|W`n?lYW_74PkmyXj@m%1MrkOYsW5S?PL!+@GN8}6;TvJS+k;WjMA02&f zx0^fef+19!PB*Uez5gX`GxhAK2GHU+>{OJdWf-dkiAt=_+XWIz413uY>2ZLnIqTI=9+j!8rT?|>@rv)R&p=`}ER3g9Oz-*L2-MXOQt}7-@ZEAT}{L4C`0W=q0PC0c5G@286>_P*hj)od!9Y7RbKq)x+Qm z$r$zFa2E*NyW>90lMwsEZhm;To?#bs^Q>$gC&rqT{zLdeKnUU)5wD}+M0-xPRNok- z61&rQ)WpSq;brYam+st0rh#Iy;&am&DfwtpphNpwXom;ryslQ;E^%r?HBl>rR%b*( zH(W6lWqCE%`#%juUx-#FBYR6>ld|H2|A&!xiVn0})^KCnPC7})wr$(CZQHhO+crA3 zZQIG|f31zR$2eo0bM@WKt8Z4-theweXzvX$4jd#qw zFGiMOtCMD}^O-M!@5qgp1{p-0d=mJ(G&%h{pJ#nN<@MC=-d4NE`c+8R`ZHhA&bcI@ z(dYk)2A_6F?xoT5JukY*|Fu%%+qF%XZEkl zkL}>DK9*z@^FV;GGmdkp5cC}WUqe^s&9P92dLXpkkxqO2vOb4TiGlC9s<~D|@0t#H zW8_&d>rF}IKv_nr+ga4>sra3i4zsWQOQg|ZJaS3X)Ii9xUip(3_x+xqOoK)SQ+U%v z7>PU{dkeTRAFnHJF|a1dY*`MY&WIQSa|c+0et#psihk0TAs@k9y1PT;zITuP*h$l< z-wh2ha&@G7ezc%cBM!Ndq&<$s-8^x=)pC6bDLB z6kOjKM!H(_HH_1j5t`3Hza%_7qg`LR{5vAYj1@4RS&k9y(+SOS4d`x`K2A#og=!v4 zW`Q?$SdfJD^w4iv@u>*c?~(975ieN_cpIY&G|m}P+Cq|>sN$E&VjThTT`2P5r|kbn zD*gUv_<GE0_Cd+R_VLW+Z7nwES@@{{f1L{qn zYt8{iyx@LT%5zH+*6Zq+k}7?%o2;rQFW2=(lEXiF?P6~i^8n*MiG|d9&E{p}8=@7# zhc6&0dyD^0yIqE^0AzJqb7m?ab3C3Hx}m%L|tK_>zJuPG)Y~HZ!@>exfVXAhi0=O+$V}UCRNUyw4GY4s}q4t3z^w)W2{4 zoNiT6D02tv{=I5uI;A#Dpj`*f$3OZv!o8}2HMTD4-^CDGgAj7nJt35_4L!U-hdb*0 zYva@ZTr8x?u#lsM6PQRt&^ciROeZ0^akwg741YwK(0@#FqWCuk{%ahqj?Niepi|yd znpe59A5E!R#qxQe{FfE~l1Pm%wi;Pthd7X+mrPt^0GCl800mp0kLX0|La>C)F=N1t z+8|vnc+h&~l8OE`L9<9TSUI!BAWYRps!({Mf(89ehG7gg9S>wMU6T>n*_;l zXMw54)JR!KWNoWn`>`Fg1q>Hk&kn9GX{3Ng1q69(*2yJ3htnycB2}H*jJ;J2Sz*BL zYITk|+?2^xpMFQxl(+&6IFJLB5mMjAU#Cs5qm)1qmvg?|qfDaI=ukiuegR-F0n7=2 z(R}hkD^MB{;%>qW>Wey~%ZrQD`X?7Xfw5|+;<$`!rMh3&B3c`l1l%?ugRVnWhHZ&^ zXeg~izO`=(dGe@UrpxQicw)3%l|EKLa_-NUil0JL1WGlqHhQ_TUol&>to}xd#N@oR zFbYHF*MDX^vqm-G#gtOJJxS~=>+k|<;Gw{K*g$`vo3Y`s9xpnmwd)w4Ro~(pAUMs@r{d|fV8pfVL!9HR%=2H|0x&u~l1o7dVs4V-B z_5Mv&p01RQ3eNdk%?{h`oghmoH)dTd*>C@ju%tT11pZDLJy75esKly6A)~y~|08o_ zMEv@op`@uEr(8lmHj#Dp)rr>yXB;=Y9aT*1Rup(&wmzPEyhpW*m%7gWrHA0QfBnu; zt;+wJP%hN*Jl}!2uwLYL62NepL+72NDT^)C^(Syny`d=YQgHv8VH1cx$W6pRg6c-u z5w<#xvEZ+T0o|N3z8BD33r22G6ezRbU^leXL=6kMj3_?Si+pDodV?0l~1t8*x)#S0bBbRstqD)Q+n-(xe^(fxRWd0z6XO|jNyXQhqO13 z3PO8Fmc2q>#9ptS_B^uP_EHBDwWA!O5HjShpZSQ17}362M?Lp>Ot__q>+jezBUi^` zG-KwYEe$%S`2miM;bHLSG|rjodR3;vHEB$3Qg}j_i*rZf_Hr=+amT*5iH3_zNb^N} z#YNo<`ryTzp^mFrJF1eaNJQCW(Rr2I%^X!-@Oa>BG*w09qaK>Y$*ILtN?5@rP|hSC z)U}7gxb^D?^}=>hy~# zMTGG^CjDoG59wnc0|j-7G#pEJnAPfpMBaQ71usct=_f?<5s96pp-q;Qd~51!ggeMc znZsa>QbB*NFOVfXLq~@_>0}DojSZ_%qvTmuXo4Ac*iV^ zR!pK1fuR_9Y{M=UC9KxF>T7KGWy;+;;0QEw}j6)yaoh@Q9~h!dJ*m;KUL zQ65C~ew4d#?q*}^ZpyD5{J6Lt{!?CC;h`cXG1z!dF})GPh)<+xyy73ThKhe2{b;>@ z3djyXfjKaifO%z?&{DbW3M^qRW5#Okm}5*?fAvU#tR+!G@8QMtgn4Y%vxK!6E>-)b zJVEc022ZQ>(wCDej!vAD%!cp5c(|rwYxN{`I8No7!Cg)8dyxgw@}T*NBdQI&X4Dmo zgtra{-v9V@Hx!^V4F*_84o1RX-K(YwmAU-96=#{cTzO^x7m2lqqCL+*Fg4jQ*2S$$ za}d!$KtALtVHQZF_wO2j`0qX*qqk}V+ns?M2dYX{$tt>`9u;UFGJRf1_DYb`{XRTl zMgQq@G!)YYDeRL5hqi42pw_blvyvjEN{Z>n5?UKKJ(^8=(u+ReLRBgmyNzy8TK`uC zKWy;B{Gj$RE>&X!V)kH!%A)UBM!x)>iwWGqX`-B$%$&Kiw(Wk7)U`o@@VKCF(nx0h zB?=0nFpXq!O%7FKg-T|iOjx;0t4<9AFWQak!NY62#hAduUxWRSVjOjFUE(2);4qU# zvr3hYOH58)@uu8~xGwKG$wz8wyVdyK$ccovCq%R35IdeZBm4|jnVHQVwB&>s=anv| zyP&@->)NZSLsME14`kyW1hYEq^nYIEzQ3B@Z=bqlpM@CYaRl&6*e&g4I~N7P2Kv!Z z@Shz(&$N3wOQTdY0?L(lA&WqF`;YNs%zu_FWRm9JJd^FC_jH6Hhl=2+mNza*d4aBtc&g&| zj8TJ%%Z`V>?NcwfRDFR_%QLVYLKi za(~cvuwZ5TRX0rL_aO5;)Y&!n69$?i<9 zKSt)i)*+c*?6pi7`*$FhhWp;oo)elZUX;D46}!(8N|t?&PI}~P;ZJmMp}&gcP&5$~ znNH}wSc!~QA{o~_zyPkXDsdVIj`3~CWMC$r)s5@f}In(9`{?+_^4nm5J~)KIhi5$`_~nkl%yB`>-X3{`)t7-CygUq2+q zF2^&#H)u+mo_qRHS{2oO1AvHn%1K7ywHrt{r5}|RThwDU3Wj#ApK%jmfHq4NjXyi< z>XSfDA`UR-O7Y`2N*fL%VxSck&8!gDAd~_Tb&I3VdooPR3w@cQ0-w6ho*9uyyjO#v)R~nAE9AsEm?szg2 zM7FB(YQ!nq4(rI1NF%pZG`k(-W?=PdZCq)#3|ji`dc6b!&}YN$giSEHxt51MxaLxc+@LDN{!b3gkrhR0iHV zu2L)XqCN0mMn&)j2$TQDl_UdkDEQCl|CpFwrQPX2@LISgJCD-^;6Kt9BVDNhZ* zi*>0{nR!HHL3VrxeklFhK15EEVK$JylG+_+8Dp%=FGwH4}S%>bPcEnHas0FrJ zqz5Dq7Ml*q(buvFctoJ8C9GI^7XC-=;5Z={jfUz`;5OKbesEn7q=c|BFl!)6A;H%b zXLu|=iVpwy!#}#jD*~LiR4f|j9M~!pGeayk_ z7_>e6(!P_O-K-hh#uX-fZX}13&5W7YMQPOkb)cSYEMSm;z2FhiXC;?}XqbniJttSY z2I+!0oF{F&Kkv$gy>PFfFVW$=9m$G1kjHAw546W#`T;_>8@Jg5eT;=m((iCFmj@H4 z&mBeq$2c;X%9WtgrjheWFuUG+wLwTmA&>IGn{|G2Drdmg_3|oI<=M6)$D9??GEt~- zpvR-!cPvtU+f^h4bUHgyhhAS7R`Ukho~;lg>*e8!(eV~FSW|Xha|c9^!1K38&>f7_*~qQ1`z9rZsJOk!Zb{UCcIv1J?7DR1pk{PB;0i>_pl&*+ z>ps4GcdC|lL4K(L*3en~&NOKxPsPS>8~g_=qKAng7b$W&JrR*~z;0tBIa!yJV2Gk) zSb;`_GH+!e3uPGnHW*Kd1G8fY{}3lY(A+=mF#+#p!2LR6!Q%{gS#?$KO}%)nesIV3 zq+jH3K)@uc=U_2lxRa_?|K6k9+gm}|iq@gE@!Ckry8z)BH4ge}rUobAh*BxI(DP2} zTYgsPS%JPch~>cF-}+Li>tM+oguO5T2!PP?Z$|z&8+G9$+ivBIdx_rW%sUp-m7++=k1%8c3{TojsJze`wsf!gbEj$f^zQq5 z8To~`?ILzkocrFF+1LAUOqF|w6GLA$?ZGiza3A@TeMJaC&(0a(F3_RzRgPbN>W-Jv zSY_fE9Ex%sJ$e3t=N_sdzNPWaNnZaN`4ZX8@lRoxbA+sz{p(3XLsi=Tvtf;iKy|a= zub06s`_g{h8wv+^Iu=}vll>3n6FG)lsd*S-MERN)D*2MF4B-=!NZ66;zn@ZrHMZ5ELYs-fZ#J)Xm z6Gv(#t-?~4nGQLoqT9{=YwdvA!LdGSc~#>{DdLIySXCeh8q8l zy>2qfN#^G=*E%;{lFHK+?_DiHo&JqgW_PabB#XuWbCOggeB%PhLo}dPIBU@S-&Bav zt^oTa6jCr!>ahRlK@);Z^V5`7--kX|)IBGnDW&^YG1suOaBvqwlSB0(!Sk57-I@g0 zD$$Xd*rgyFmEPNgkQWC(=AR%>Ul8n$8RkG+UboD&IY2^4-eMUA8Mxsq_K}Qx%ImqA z{PA${HBu)Uy<^Kw6jBc4!i?+ji$Biykvr7~$We30d_@chhWTVh;}&qQZ+?9o&4fwlPS_R>o|J zh7j$37-FqiQ!Z<~-nx5&LCby7q;R>}vpJFvbKH}?4EmH6PAl~W1D35;m715DA{dF( zE}02xIoKrbz>D;GC?`s9ZtT>lzK4GQ?4@Z|6~9=X5mILVMyDC`@tw4POUAg5ZOo6| z%>qcrzAA{-*#w+Q;=JxFaFi+-oeP>hQNEGWY>!K9*M(urH=s0s)JTtY*_b565)O7O zX_?NVdEE)x!mLnZ=0%aHR1XT~(#XCm%VZvol+Ku1v)oU!4n}=EBwD<7yKJzNE%MhP z4pdBjkOlHgt-qsV1m$=D$c+jVCOFay9}k`hP>-}$PhLp`s<;s;m7AF}R-s3?P9<}I zRhT?SNqTv+-jC^S#{v($hX%Q>4Eh9n`s8g5C)4sBKqf+SJn}ukCV*7fnZhq)*+YUe z7>HZ}DX7h+dR5rKyjTa)5N0b@Ff)Ym`zXkdFJ{WXdu~^N_OjzG8unmvwbEBoNaB-A zV8VW`Q5@H(;q{?+y*D4zOR<(bf2jI`EJLHyBDz-`tH=5^0|@|d$JqLR^}GC7noh8o zo8+p&t{s6XbN4=zZeDAQu}0eF&ZJ(bM`qPU8WKx{7c;P85a0Q>=qXV(B_;gW11hVy!Tc{RWwT6-R1i^qfQ0D&-H?wj3mD&q#6t|uP zJ?0TPb4mvZ7?yrnBuW+9=}r-3wL&JK9~rLVb;#yHOB1MtfY5lI%^;$WZqLRHZpk=d^5X3sqG-7{huo^woTBH)O8YLzC|Riu?N@Ag()aw!TzIL+H4o z#r_W{S+eWF5G|wp{{xg<4Om2!1v?L{__>cyB>l6ET?x$mNaTOp$V+2~WQ0Dc^4mXf zuKg~sQWdLt=j|w=L>1qCw;%;6IUQda)_z9-egYa#Y`b2=l6C}$dqcW4*l}2A9e7?|2rqSZM!c=<^LZi z*_!>oILYPN#D0ffvTzk+Id7!YOC&X;z?vVjwV&GFRoZR8vO{Fl4N>6h+mH?AnMaiz zp(ZJoSAC9R*PGW*tdY5%kDm}8{Y`{@??PhFHSV^d+y?P8LyvS6#HiY3L|7AE6d@I| zrTFlX#^O41FUNa?Ba9R_r9SXe-hiyG-EBO{4QVdN3rt08zQ0B^tyJ3OM!@i~W3)X# zV2xb5q-x}$IA=mLGBCPRdEg$-cfLhNPxs;~Jn=vn=eZ@hu2&5H{25fQm!YG~34`=L zAZ9A%wvl@&!Y`y5>qSH7dJ|KjV}ZmJjF7IKzxdCtGc(AvElt_1MM+)?fpn0@I7H25 ztf?hO1IfEcojU z{F`=#I{KvKJ!DWwnsZ5@@A)qYC$Q3Xy9RyNA8}7_kpDp>#`jwl&0lz-8Y>2*^nw=t z1DSokW-n$B^=>DD5l~d8oKBw7>?N$MFm_lizhJgx9}*}ysoR|ufWviBA%0AS#YeQ}XT1V2NA#GO#Ku}m&qE{UG6~QK6VO9mh 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

  • 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 literal 0 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∈i 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 literal 0 Hc$@63B7j^ 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

    4%)u9t;#d zXtZ%sT-!J6&IFBs(;oU&9WYi_0IMkr5MHT=ZBzVFJaufbXR5DIoSLj6d+gYKz+v5# z@NoA0Q0D_~zH-3R1uO50FmM~S@ToT`(196X7$~&>3_vw;Qnh?=eZT3On3y<{Zy~^J z$Ql@=hi$IE^I?#k>aY7C>pN35@#d1)M-zn5_|Z|cdncho+^^c$-~Xg_s>Yccl&AxU zk06(Jf=HSAvqIg%!s2eca!ikrPZC`K#r_;tcu0`*e7&EtWd_oxU-d%M8G|?l=6Mc> zBQF)mkr(hQU+JjZ1gQm^EezbHa#qk&03fPufaF~PGQ~cAehe^6e{-1*6-MIP&9hb+ zDq|EuZu10M!!*m-zrq<{hC`*QPGN^|2!k(m`kwjsC;fhq0jp#Rb`&= z_vzdkD|IlZ5c56c-#fzI$Pu9906#)cQ9fdu)4K(Gl6)OhvkmGhkr zO|*x{i*Qd-!MGg}w~lfeX`kg6J9%HNcI|TSgg39#2)oD^b12-`vJWQvz#mo=~ ztFcQ?=6P^Q{Yg$ZXKGt5Z?yeZG#bWgWnE@OP&fYEGB{ z-VU7Xim13V0ZA9Jx#mWbZB04?gjg2l o$bVee{MS{_{~!Jj58wXS8y*x>^KLZoE)2XZ%xp}H&bmJM3!Ir~L;wH) literal 0 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 Fe*x%3>r(&# diff --git a/docs/source/parallel/vision_cpu_load.png b/docs/source/parallel/vision_cpu_load.png deleted file mode 100644 index 7d3148549c5f35fc17902d0f226a1128794f5ff5..0000000000000000000000000000000000000000 GIT binary patch literal 0 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 Qe+S4tRF*E1eD3@I0aS)fzW@LL diff --git a/docs/source/parallel/visionhpc.txt b/docs/source/parallel/visionhpc.txt deleted file mode 100644 index 7a6b15f..0000000 --- a/docs/source/parallel/visionhpc.txt +++ /dev/null @@ -1,250 +0,0 @@ -================================== - IPython/Vision Beam Pattern Demo -================================== - -.. note:: - - This page has not been updated to reflect the recent work on ipcluster. - This work makes it much easier to use IPython on a cluster. - -Installing and testing IPython at OSC systems -============================================= - -All components were installed from source and I have my environment set up to -include ~/usr/local in my various necessary paths ($PATH, $PYTHONPATH, etc). -Other than a slow filesystem for unpacking tarballs, the install went without a -hitch. For each needed component, I just downloaded the source tarball, -unpacked it via:: - - tar xzf (or xjf if it's bz2) filename.tar.{gz,bz2} - -and then installed them (including IPython itself) with:: - - cd dirname/ # path to unpacked tarball - python setup.py install --prefix=~/usr/local/ - -The components I installed are listed below. For each one I give the main -project link as well as a direct one to the file I actually dowloaded and used. - -- nose, used for testing: -http://somethingaboutorange.com/mrl/projects/nose/ -http://somethingaboutorange.com/mrl/projects/nose/nose-0.10.3.tar.gz - -- Zope interface, used to declare interfaces in twisted and ipython. Note: -you must get this from the page linked below and not fro the defaul -one(http://www.zope.org/Products/ZopeInterface) because the latter has an -older version, it hasn't been updated in a long time. This pypi link has -the current release (3.4.1 as of this writing): -http://pypi.python.org/pypi/zope.interface -http://pypi.python.org/packages/source/z/zope.interface/zope.interface-3.4.1.tar.gz - -- pyopenssl, security layer used by foolscap. Note: version 0.7 *must* be -used: -http://sourceforge.net/projects/pyopenssl/ -http://downloads.sourceforge.net/pyopenssl/pyOpenSSL-0.6.tar.gz?modtime=1212595285&big_mirror=0 - - -- Twisted, used for all networking: -http://twistedmatrix.com/trac/wiki/Downloads -http://tmrc.mit.edu/mirror/twisted/Twisted/8.1/Twisted-8.1.0.tar.bz2 - -- Foolscap, used for managing connections securely: -http://foolscap.lothar.com/trac -http://foolscap.lothar.com/releases/foolscap-0.3.1.tar.gz - - -- IPython itself: -http://ipython.scipy.org/ -http://ipython.scipy.org/dist/ipython-0.9.1.tar.gz - - -I then ran the ipython test suite via:: - - iptest -vv - -and it passed with only:: - - ====================================================================== - ERROR: testGetResult_2 - ---------------------------------------------------------------------- - DirtyReactorAggregateError: Reactor was unclean. - Selectables: - - - ---------------------------------------------------------------------- - Ran 419 tests in 33.971s - - FAILED (SKIP=4, errors=1) - -In three more runs of the test suite I was able to reproduce this error -sometimes but not always; for now I think we can move on but we need to -investigate further. Especially if we start seeing problems in real use (the -test suite stresses the networking layer in particular ways that aren't -necessarily typical of normal use). - -Next, I started an 8-engine cluster via:: - - perez@opt-login01[~]> ipcluster -n 8 - Starting controller: Controller PID: 30845 - ^X Starting engines: Engines PIDs: [30846, 30847, 30848, 30849, - 30850, 30851, 30852, 30853] - Log files: /home/perez/.ipython/log/ipcluster-30845-* - - Your cluster is up and running. - - [... etc] - -and in a separate ipython session checked that the cluster is running and I can -access all the engines:: - - In [1]: from IPython.kernel import client - - In [2]: mec = client.MultiEngineClient() - - In [3]: mec.get_ids() - Out[3]: [0, 1, 2, 3, 4, 5, 6, 7] - -and run trivial code in them (after importing the ``random`` module in all -engines):: - - In [11]: mec.execute("x=random.randint(0,10)") - Out[11]: - - [0] In [3]: x=random.randint(0,10) - [1] In [3]: x=random.randint(0,10) - [2] In [3]: x=random.randint(0,10) - [3] In [3]: x=random.randint(0,10) - [4] In [3]: x=random.randint(0,10) - [5] In [3]: x=random.randint(0,10) - [6] In [3]: x=random.randint(0,10) - [7] In [3]: x=random.randint(0,10) - - In [12]: mec.pull('x') - Out[12]: [10, 0, 8, 10, 2, 9, 10, 7] - - -We'll continue conducting more complex tests later, including instaling Vision -locally and running the beam demo. - - -Michel's original instructions -============================== - -I got a Vision network that reproduces the beam pattern demo working: - -.. image:: vision_beam_pattern.png - :width: 400 - :target: vision_beam_pattern.png - :align: center - - -I created a package called beamPattern that provides the function run() in its -__init__.py file. - -A subpackage beamPattern/VisionInterface provides Vision nodes for: - -- computing Elevation and Azimuth from a 3D vector - -- Reading .mat files - -- taking the results gathered from the engines and creating the output that a - single engine would have had produced - -The Mec node connect to a controller. In my network it was local but an furl -can be specified to connect to a remote controller. - -The PRun Func node is from the IPython library of nodes. the import statement -is used to get the run function from the beamPattern package and bu puting -"run" in the function entry of this node we push this function to the engines. -In addition to the node will create input ports for all arguments of the -function being pushed (i.e. the run function) - -The second input port on PRun Fun take an integer specifying the rank of the -argument we want to scatter. All other arguments will be pushed to the engines. - -The ElevAzim node has a 3D vector widget and computes the El And Az values -which are passed into the PRun Fun node through the ports created -automatically. The Mat node allows to select the .mat file, reads it and passed -the data to the locdata port created automatically on PRun Func - -The calculation is executed in parallel, and the results are gathered and -output. Instead of having a list of 3 vectors we nd up with a list of n*3 -vectors where n is the number of engines. unpackDectorResults will turn it into -a list of 3. We then plot x, y, and 10*log10(z) - - -Installation ------------- - -- inflate beamPattern into the site-packages directory for the MGL tools. - -- place the appended IPythonNodes.py and StandardNodes.py into the Vision - package of the MGL tools. - -- place the appended items.py in the NetworkEditor package of the MGL tools - -- run vision for the network beamPat5_net.py:: - - vision beamPat5_net.py - -Once the network is running, you can: - -- double click on the MEC node and either use an emptty string for the furl to - connect to a local engine or cut and paste the furl to the engine you want to - use - -- click on the yellow lighting bold to run the network. - -- Try modifying the MAT file or change the Vector used top compute elevation - and Azimut. - - -Fernando's notes -================ - -- I had to install IPython and all its dependencies for the python used by the - MGL tools. - -- Then I had to install scipy 0.6.0 for it, since the nodes needed Scipy. To - do this I sourced the mglenv.sh script and then ran:: - - python setup.py install --prefix=~/usr/opt/mgl - - -Using PBS -========= - -The following PBS script can be used to start the engines:: - - #PBS -N bgranger-ipython - #PBS -j oe - #PBS -l walltime=00:10:00 - #PBS -l nodes=4:ppn=4 - - cd $PBS_O_WORKDIR - export PATH=$HOME/usr/local/bin - export PYTHONPATH=$HOME/usr/local/lib/python2.4/site-packages - /usr/local/bin/mpiexec -n 16 ipengine - - -If this file is called ``ipython_pbs.sh``, then the in one login windows -(i.e. on the head-node -- ``opt-login01.osc.edu``), run ``ipcontroller``. In -another login window on the same node, run the above script:: - - qsub ipython_pbs.sh - -If you look at the first window, you will see some diagnostic output -from ipcontroller. You can then get the furl from your own -``~/.ipython/security`` directory and then connect to it remotely. - -You might need to set up an SSH tunnel, however; if this doesn't work as -advertised:: - - ssh -L 10115:localhost:10105 bic - - -Links to other resources -======================== - -- http://www.osc.edu/~unpingco/glenn_NewLynx2_Demo.avi - diff --git a/docs/source/parallel/winhpc_index.txt b/docs/source/parallel/winhpc_index.txt new file mode 100644 index 0000000..d52da22 --- /dev/null +++ b/docs/source/parallel/winhpc_index.txt @@ -0,0 +1,14 @@ +======================================== +Using IPython on Windows HPC Server 2008 +======================================== + + +Contents +======== + +.. toctree:: + :maxdepth: 1 + + parallel_winhpc.txt + parallel_demos.txt + diff --git a/docs/source/whatsnew/development.txt b/docs/source/whatsnew/development.txt new file mode 100644 index 0000000..8a7c4a0 --- /dev/null +++ b/docs/source/whatsnew/development.txt @@ -0,0 +1,231 @@ +================================================ +Development version +================================================ + +Main `ipython` branch +===================== + +As of the 0.11 version of IPython, a signifiant portion of the core has been +refactored. This refactoring is founded on a number of new abstractions. +The main new classes that implement these abstractions are: + +* :class:`IPython.utils.traitlets.HasTraitlets`. +* :class:`IPython.core.component.Component`. +* :class:`IPython.core.application.Application`. +* :class:`IPython.config.loader.ConfigLoader`. +* :class:`IPython.config.loader.Config` + +We are still in the process of writing developer focused documentation about +these classes, but for now our :ref:`configuration documentation +` contains a high level overview of the concepts that these +classes express. + +The changes listed here are a brief summary of the recent work on IPython. +For more details, please consult the actual source. + +New features +------------ + +* The :mod:`IPython.extensions.pretty` extension has been moved out of + quarantine and fully updated to the new extension API. + +* New magics for loading/unloading/reloading extensions have been added: + ``%load_ext``, ``%unload_ext`` and ``%reload_ext``. + +* The configuration system and configuration files are brand new. See the + configuration system :ref:`documentation ` for more details. + +* The :class:`~IPython.core.iplib.InteractiveShell` class is now a + :class:`~IPython.core.component.Component` subclass and has traitlets that + determine the defaults and runtime environment. The ``__init__`` method has + also been refactored so this class can be instantiated and run without the + old :mod:`ipmaker` module. + +* The methods of :class:`~IPython.core.iplib.InteractiveShell` have + been organized into sections to make it easier to turn more sections + of functionality into componenets. + +* The embedded shell has been refactored into a truly standalone subclass of + :class:`InteractiveShell` called :class:`InteractiveShellEmbed`. All + embedding logic has been taken out of the base class and put into the + embedded subclass. + +* I have created methods of :class:`~IPython.core.iplib.InteractiveShell` to + help it cleanup after itself. The :meth:`cleanup` method controls this. We + couldn't do this in :meth:`__del__` because we have cycles in our object + graph that prevent it from being called. + +* Created a new module :mod:`IPython.utils.importstring` for resolving + strings like ``foo.bar.Bar`` to the actual class. + +* Completely refactored the :mod:`IPython.core.prefilter` module into + :class:`~IPython.core.component.Component` subclasses. Added a new layer + into the prefilter system, called "transformations" that all new prefilter + logic should use (rather than the older "checker/handler" approach). + +* Aliases are now components (:mod:`IPython.core.alias`). + +* We are now using an internally shipped version of + :mod:`~IPython.external.argparse` to parse command line options for + :command:`ipython`. + +* New top level :func:`~IPython.core.embed.embed` function that can be called + to embed IPython at any place in user's code. One the first call it will + create an :class:`~IPython.core.embed.InteractiveShellEmbed` instance and + call it. In later calls, it just calls the previously created + :class:`~IPython.core.embed.InteractiveShellEmbed`. + +* Created a component system (:mod:`IPython.core.component`) that is based on + :mod:`IPython.utils.traitlets`. Components are arranged into a runtime + containment tree (not inheritance) that i) automatically propagates + configuration information and ii) allows components to discover each other + in a loosely coupled manner. In the future all parts of IPython will be + subclasses of :class:`~IPython.core.component.Component`. All IPython + developers should become familiar with the component system. + +* Created a new :class:`~IPython.config.loader.Config` for holding + configuration information. This is a dict like class with a few extras: i) + it supports attribute style access, ii) it has a merge function that merges + two :class:`~IPython.config.loader.Config` instances recursively and iii) it + will automatically create sub-:class:`~IPython.config.loader.Config` + instances for attributes that start with an uppercase character. + +* Created new configuration loaders in :mod:`IPython.config.loader`. These + loaders provide a unified loading interface for all configuration + information including command line arguments and configuration files. We + have two default implementations based on :mod:`argparse` and plain python + files. These are used to implement the new configuration system. + +* Created a top-level :class:`Application` class in + :mod:`IPython.core.application` that is designed to encapsulate the starting + of any IPython process. An application loads and merges all the + configuration objects, constructs the main application :class:`Component` + instances and then starts the application running. The default + :class:`Application` class has built-in logic for handling the IPython + directory as well as profiles. + +* The :class:`Type` and :class:`Instance` traitlets now handle classes given + as strings, like ``foo.bar.Bar``. This is needed for forward declarations. + But, this was implemented in a careful way so that string to class + resolution is done at a single point, when the parent + :class:`~IPython.utils.traitlets.HasTraitlets` is instantiated. + +* :mod:`IPython.utils.ipstruct` has been refactored to be a subclass of + dict. It also now has full docstrings and doctests. +* Created a Trait's like implementation in :mod:`IPython.utils.traitlets`. + This is a pure Python, lightweight version of a library that is similar to + :mod:`enthought.traits`. We are using this for validation, defaults and + notification in our new component system. Although it is not API compatible + with :mod:`enthought.traits`, we plan on moving in this direction so that + eventually our implementation could be replaced by a (yet to exist) pure + Python version of :mod:`enthought.traits`. + +* Added a new module :mod:`IPython.lib.inputhook` to manage the integration + with GUI event loops using `PyOS_InputHook`. See the docstrings in this + module or the main IPython docs for details. + +* For users, GUI event loop integration is now handled through the new + :command:`%gui` magic command. Type ``%gui?`` at an IPython prompt for + documentation. + +* The command line options ``-wthread``, ``-qthread`` and + ``-gthread`` just call the appropriate :mod:`IPython.lib.inputhook` + functions. + +* For developers :mod:`IPython.lib.inputhook` provides a simple interface + for managing the event loops in their interactive GUI applications. + Examples can be found in our :file:`docs/examples/lib` directory. + +Bug fixes +--------- + +* Previously, the latex Sphinx docs were in a single chapter. This has been + fixed by adding a sixth argument of True to the ``latex_documents`` + attribute of :file:`conf.py`. + +* The ``psum`` example in the MPI documentation has been updated to mpi4py + version 1.1.0. Thanks to J. Thomas for this fix. + +* The top-level, zero-install :file:`ipython.py` script has been updated to + the new application launching API. + +* Keyboard interrupts now work with GUI support enabled across all platforms + and all GUI toolkits reliably. + +Backwards incompatible changes +------------------------------ + +* The extension loading functions have been renamed to + :func:`load_ipython_extension` and :func:`unload_ipython_extension`. + +* :class:`~IPython.core.iplib.InteractiveShell` no longer takes an + ``embedded`` argument. Instead just use the + :class:`~IPython.core.iplib.InteractiveShellEmbed` class. + +* ``__IPYTHON__`` is no longer injected into ``__builtin__``. + +* :meth:`Struct.__init__` no longer takes `None` as its first argument. It + must be a :class:`dict` or :class:`Struct`. + +* :meth:`~IPython.core.iplib.InteractiveShell.ipmagic` has been renamed + :meth:`~IPython.core.iplib.InteractiveShell.magic.` + +* The functions :func:`ipmagic` and :func:`ipalias` have been removed from + :mod:`__builtins__`. + +* The references to the global :class:`~IPython.core.iplib.InteractiveShell` + instance (``_ip``, and ``__IP``) have been removed from the user's + namespace. They are replaced by a new function called :func:`get_ipython` + that returns the current :class:`~IPython.core.iplib.InteractiveShell` + instance. This function is injected into the user's namespace and is now the + main way of accessing IPython's API. + +* Old style configuration files :file:`ipythonrc` and :file:`ipy_user_conf.py` + are no longer supported. Users should migrate there configuration files to + the new format described :ref:`here ` and :ref:`here + `. + +* The old IPython extension API that relied on :func:`ipapi` has been + completely removed. The new extension API is described :ref:`here + `. + +* Support for ``qt3`` has been dropped. User's who need this should use + previous versions of IPython. + +* Removed :mod:`shellglobals` as it was obsolete. + +* Removed all the threaded shells in :mod:`IPython.core.shell`. These are no + longer needed because of the new capabilities in + :mod:`IPython.lib.inputhook`. + +* The ``-pylab`` command line flag has been disabled until matplotlib adds + support for the new :mod:`IPython.lib.inputhook` approach. The new stuff + does work with matplotlib, but you have to set everything up by hand. + +* New top-level sub-packages have been created: :mod:`IPython.core`, + :mod:`IPython.lib`, :mod:`IPython.utils`, :mod:`IPython.deathrow`, + :mod:`IPython.quarantine`. All existing top-level modules have been + moved to appropriate sub-packages. All internal import statements + have been updated and tests have been added. The build system (setup.py + and friends) have been updated. See :ref:`this section ` of the + documentation for descriptions of these new sub-packages. + +* Compatability modules have been created for :mod:`IPython.Shell`, + :mod:`IPython.ipapi` and :mod:`IPython.iplib` that display warnings + and then load the actual implementation from :mod:`IPython.core`. + +* :mod:`Extensions` has been moved to :mod:`extensions` and all existing + extensions have been moved to either :mod:`IPython.quarantine` or + :mod:`IPython.deathrow`. :mod:`IPython.quarantine` contains modules that we + plan on keeping but that need to be updated. :mod:`IPython.deathrow` + contains modules that are either dead or that should be maintained as third + party libraries. More details about this can be found :ref:`here + `. + +* The IPython GUIs in :mod:`IPython.frontend` and :mod:`IPython.gui` are likely + broken because of the refactoring in the core. With proper updates, these + should still work. We probably want to get these so they are not using + :mod:`IPython.kernel.core` (which is being phased out). + + + diff --git a/docs/source/whatsnew/index.txt b/docs/source/whatsnew/index.txt new file mode 100644 index 0000000..ed606f6 --- /dev/null +++ b/docs/source/whatsnew/index.txt @@ -0,0 +1,28 @@ +.. Developers should add in this file, during each release cycle, information +.. about important changes they've made, in a summary format that's meant for +.. end users. For each release we normally have three sections: features, bug +.. fixes and api breakage. +.. Please remember to credit the authors of the contributions by name, +.. especially when they are new users or developers who do not regularly +.. participate in IPython's development. + +.. _whatsnew_index: + +===================== +What's new in IPython +===================== + +This section documents the changes that have been made in various versions of +IPython. Users should consult these pages to learn about new features, bug +fixes and backwards incompatibilities. Developers should summarize the +development work they do here in a user friendly format. + +.. toctree:: + :maxdepth: 1 + + development + version0.10 + version0.9 + version0.8 + + diff --git a/docs/source/whatsnew/version0.10.txt b/docs/source/whatsnew/version0.10.txt new file mode 100644 index 0000000..d23f813 --- /dev/null +++ b/docs/source/whatsnew/version0.10.txt @@ -0,0 +1,223 @@ +======================================== +0.10 series +======================================== + +Release 0.10 +============ + +This release brings months of slow but steady development, and will be the last +before a major restructuring and cleanup of IPython's internals that is already +under way. For this reason, we hope that 0.10 will be a stable and robust +release so that while users adapt to some of the API changes that will come +with the refactoring that will become IPython 0.11, they can safely use 0.10 in +all existing projects with minimal changes (if any). + +IPython 0.10 is now a medium-sized project, with roughly (as reported by David +Wheeler's :command:`sloccount` utility) 40750 lines of Python code, and a diff +between 0.9.1 and this release that contains almost 28000 lines of code and +documentation. Our documentation, in PDF format, is a 495-page long PDF +document (also available in HTML format, both generated from the same sources). + +Many users and developers contributed code, features, bug reports and ideas to +this release. Please do not hesitate in contacting us if we've failed to +acknowledge your contribution here. In particular, for this release we have +contribution from the following people, a mix of new and regular names (in +alphabetical order by first name): + +* Alexander Clausen: fix #341726. +* Brian Granger: lots of work everywhere (features, bug fixes, etc). +* Daniel Ashbrook: bug report on MemoryError during compilation, now fixed. +* Darren Dale: improvements to documentation build system, feedback, design + ideas. +* Fernando Perez: various places. +* Gaël Varoquaux: core code, ipythonx GUI, design discussions, etc. Lots... +* John Hunter: suggestions, bug fixes, feedback. +* Jorgen Stenarson: work on many fronts, tests, fixes, win32 support, etc. +* Laurent Dufréchou: many improvements to ipython-wx standalone app. +* Lukasz Pankowski: prefilter, `%edit`, demo improvements. +* Matt Foster: TextMate support in `%edit`. +* Nathaniel Smith: fix #237073. +* Pauli Virtanen: fixes and improvements to extensions, documentation. +* Prabhu Ramachandran: improvements to `%timeit`. +* Robert Kern: several extensions. +* Sameer D'Costa: help on critical bug #269966. +* Stephan Peijnik: feedback on Debian compliance and many man pages. +* Steven Bethard: we are now shipping his :mod:`argparse` module. +* Tom Fetherston: many improvements to :mod:`IPython.demo` module. +* Ville Vainio: lots of work everywhere (features, bug fixes, etc). +* Vishal Vasta: ssh support in ipcluster. +* Walter Doerwald: work on the :mod:`IPython.ipipe` system. + +Below we give an overview of new features, bug fixes and backwards-incompatible +changes. For a detailed account of every change made, feel free to view the +project log with :command:`bzr log`. + +New features +------------ + +* New `%paste` magic automatically extracts current contents of clipboard and + pastes it directly, while correctly handling code that is indented or + prepended with `>>>` or `...` python prompt markers. A very useful new + feature contributed by Robert Kern. + +* IPython 'demos', created with the :mod:`IPython.demo` module, can now be + created from files on disk or strings in memory. Other fixes and + improvements to the demo system, by Tom Fetherston. + +* Added :func:`find_cmd` function to :mod:`IPython.platutils` module, to find + commands in a cross-platform manner. + +* Many improvements and fixes to Gaël Varoquaux's :command:`ipythonx`, a + WX-based lightweight IPython instance that can be easily embedded in other WX + applications. These improvements have made it possible to now have an + embedded IPython in Mayavi and other tools. + +* :class:`MultiengineClient` objects now have a :meth:`benchmark` method. + +* The manual now includes a full set of auto-generated API documents from the + code sources, using Sphinx and some of our own support code. We are now + using the `Numpy Documentation Standard`_ for all docstrings, and we have + tried to update as many existing ones as possible to this format. + +* The new :mod:`IPython.Extensions.ipy_pretty` extension by Robert Kern + provides configurable pretty-printing. + +* Many improvements to the :command:`ipython-wx` standalone WX-based IPython + application by Laurent Dufréchou. It can optionally run in a thread, and + this can be toggled at runtime (allowing the loading of Matplotlib in a + running session without ill effects). + +* IPython includes a copy of Steven Bethard's argparse_ in the + :mod:`IPython.external` package, so we can use it internally and it is also + available to any IPython user. By installing it in this manner, we ensure + zero conflicts with any system-wide installation you may already have while + minimizing external dependencies for new users. In IPython 0.10, We ship + argparse version 1.0. + +* An improved and much more robust test suite, that runs groups of tests in + separate subprocesses using either Nose or Twisted's :command:`trial` runner + to ensure proper management of Twisted-using code. The test suite degrades + gracefully if optional dependencies are not available, so that the + :command:`iptest` command can be run with only Nose installed and nothing + else. We also have more and cleaner test decorators to better select tests + depending on runtime conditions, do setup/teardown, etc. + +* The new ipcluster now has a fully working ssh mode that should work on + Linux, Unix and OS X. Thanks to Vishal Vatsa for implementing this! + +* The wonderful TextMate editor can now be used with %edit on OS X. Thanks + to Matt Foster for this patch. + +* The documentation regarding parallel uses of IPython, including MPI and PBS, + has been significantly updated and improved. + +* The developer guidelines in the documentation have been updated to explain + our workflow using :command:`bzr` and Launchpad. + +* Fully refactored :command:`ipcluster` command line program for starting + IPython clusters. This new version is a complete rewrite and 1) is fully + cross platform (we now use Twisted's process management), 2) has much + improved performance, 3) uses subcommands for different types of clusters, 4) + uses argparse for parsing command line options, 5) has better support for + starting clusters using :command:`mpirun`, 6) has experimental support for + starting engines using PBS. It can also reuse FURL files, by appropriately + passing options to its subcommands. However, this new version of ipcluster + should be considered a technology preview. We plan on changing the API in + significant ways before it is final. + +* Full description of the security model added to the docs. + +* cd completer: show bookmarks if no other completions are available. + +* sh profile: easy way to give 'title' to prompt: assign to variable + '_prompt_title'. It looks like this:: + + [~]|1> _prompt_title = 'sudo!' + sudo![~]|2> + +* %edit: If you do '%edit pasted_block', pasted_block variable gets updated + with new data (so repeated editing makes sense) + +.. _Numpy Documentation Standard: http://projects.scipy.org/numpy/wiki/CodingStyleGuidelines#docstring-standard + +.. _argparse: http://code.google.com/p/argparse/ + +Bug fixes +--------- + +* Fix #368719, removed top-level debian/ directory to make the job of Debian + packagers easier. + +* Fix #291143 by including man pages contributed by Stephan Peijnik from the + Debian project. + +* Fix #358202, effectively a race condition, by properly synchronizing file + creation at cluster startup time. + +* `%timeit` now handles correctly functions that take a long time to execute + even the first time, by not repeating them. + +* Fix #239054, releasing of references after exiting. + +* Fix #341726, thanks to Alexander Clausen. + +* Fix #269966. This long-standing and very difficult bug (which is actually a + problem in Python itself) meant long-running sessions would inevitably grow + in memory size, often with catastrophic consequences if users had large + objects in their scripts. Now, using `%run` repeatedly should not cause any + memory leaks. Special thanks to John Hunter and Sameer D'Costa for their + help with this bug. + +* Fix #295371, bug in `%history`. + +* Improved support for py2exe. + +* Fix #270856: IPython hangs with PyGTK + +* Fix #270998: A magic with no docstring breaks the '%magic magic' + +* fix #271684: -c startup commands screw up raw vs. native history + +* Numerous bugs on Windows with the new ipcluster have been fixed. + +* The ipengine and ipcontroller scripts now handle missing furl files + more gracefully by giving better error messages. + +* %rehashx: Aliases no longer contain dots. python3.0 binary + will create alias python30. Fixes: + #259716 "commands with dots in them don't work" + +* %cpaste: %cpaste -r repeats the last pasted block. + The block is assigned to pasted_block even if code + raises exception. + +* Bug #274067 'The code in get_home_dir is broken for py2exe' was + fixed. + +* Many other small bug fixes not listed here by number (see the bzr log for + more info). + +Backwards incompatible changes +------------------------------ + +* `ipykit` and related files were unmaintained and have been removed. + +* The :func:`IPython.genutils.doctest_reload` does not actually call + `reload(doctest)` anymore, as this was causing many problems with the test + suite. It still resets `doctest.master` to None. + +* While we have not deliberately broken Python 2.4 compatibility, only minor + testing was done with Python 2.4, while 2.5 and 2.6 were fully tested. But + if you encounter problems with 2.4, please do report them as bugs. + +* The :command:`ipcluster` now requires a mode argument; for example to start a + cluster on the local machine with 4 engines, you must now type:: + + $ ipcluster local -n 4 + +* The controller now has a ``-r`` flag that needs to be used if you want to + reuse existing furl files. Otherwise they are deleted (the default). + +* Remove ipy_leo.py. You can use :command:`easy_install ipython-extension` to + get it. (done to decouple it from ipython release cycle) + diff --git a/docs/source/whatsnew/version0.8.txt b/docs/source/whatsnew/version0.8.txt new file mode 100644 index 0000000..38d5ed0 --- /dev/null +++ b/docs/source/whatsnew/version0.8.txt @@ -0,0 +1,34 @@ +======================================== +0.8 series +======================================== + +Release 0.8.4 +============= + +This was a quick release to fix an unfortunate bug that slipped into the 0.8.3 +release. The ``--twisted`` option was disabled, as it turned out to be broken +across several platforms. + + +Release 0.8.3 +============= + +* pydb is now disabled by default (due to %run -d problems). You can enable + it by passing -pydb command line argument to IPython. Note that setting + it in config file won't work. + + +Release 0.8.2 +============= + +* %pushd/%popd behave differently; now "pushd /foo" pushes CURRENT directory + and jumps to /foo. The current behaviour is closer to the documented + behaviour, and should not trip anyone. + + +Older releases +============== + +Changes in earlier releases of IPython are described in the older file +``ChangeLog``. Please refer to this document for details. + diff --git a/docs/source/changes.txt b/docs/source/whatsnew/version0.9.txt similarity index 57% rename from docs/source/changes.txt rename to docs/source/whatsnew/version0.9.txt index c0e146e..5887c5e 100644 --- a/docs/source/changes.txt +++ b/docs/source/whatsnew/version0.9.txt @@ -1,93 +1,6 @@ -.. _changes: - -========== -What's new -========== - -.. contents:: -.. - 1 Release 0.9.1 - 2 Release 0.9 - 2.1 New features - 2.2 Bug fixes - 2.3 Backwards incompatible changes - 2.4 Changes merged in from IPython1 - 2.4.1 New features - 2.4.2 Bug fixes - 2.4.3 Backwards incompatible changes - 3 Release 0.8.4 - 4 Release 0.8.3 - 5 Release 0.8.2 - 6 Older releases -.. - -Release dev -=========== - -New features ------------- - -* The new ipcluster now has a fully working ssh mode that should work on - Linux, Unix and OS X. Thanks to Vishal Vatsa for implementing this! - -* The wonderful TextMate editor can now be used with %edit on OS X. Thanks - to Matt Foster for this patch. - -* Fully refactored :command:`ipcluster` command line program for starting - IPython clusters. This new version is a complete rewrite and 1) is fully - cross platform (we now use Twisted's process management), 2) has much - improved performance, 3) uses subcommands for different types of clusters, - 4) uses argparse for parsing command line options, 5) has better support - for starting clusters using :command:`mpirun`, 6) has experimental support - for starting engines using PBS. However, this new version of ipcluster - should be considered a technology preview. We plan on changing the API - in significant ways before it is final. - -* The :mod:`argparse` module has been added to :mod:`IPython.external`. - -* Fully description of the security model added to the docs. - -* cd completer: show bookmarks if no other completions are available. - -* sh profile: easy way to give 'title' to prompt: assign to variable - '_prompt_title'. It looks like this:: - - [~]|1> _prompt_title = 'sudo!' - sudo![~]|2> - -* %edit: If you do '%edit pasted_block', pasted_block - variable gets updated with new data (so repeated - editing makes sense) - -Bug fixes ---------- - -* Numerous bugs on Windows with the new ipcluster have been fixed. - -* The ipengine and ipcontroller scripts now handle missing furl files - more gracefully by giving better error messages. - -* %rehashx: Aliases no longer contain dots. python3.0 binary - will create alias python30. Fixes: - #259716 "commands with dots in them don't work" - -* %cpaste: %cpaste -r repeats the last pasted block. - The block is assigned to pasted_block even if code - raises exception. - -* Bug #274067 'The code in get_home_dir is broken for py2exe' was - fixed. - -Backwards incompatible changes ------------------------------- - -* The controller now has a ``-r`` flag that needs to be used if you want to - reuse existing furl files. Otherwise they are deleted (the default). - -* Remove ipy_leo.py. "easy_install ipython-extension" to get it. - (done to decouple it from ipython release cycle) - - +======================================== +0.9 series +======================================== Release 0.9.1 ============= @@ -368,34 +281,3 @@ Backwards incompatible changes * Changed the ``setupNS`` and ``resultNames`` in the ``Task`` class to ``push`` and ``pull``. - -Release 0.8.4 -============= - -This was a quick release to fix an unfortunate bug that slipped into the 0.8.3 -release. The ``--twisted`` option was disabled, as it turned out to be broken -across several platforms. - - -Release 0.8.3 -============= - -* pydb is now disabled by default (due to %run -d problems). You can enable - it by passing -pydb command line argument to IPython. Note that setting - it in config file won't work. - - -Release 0.8.2 -============= - -* %pushd/%popd behave differently; now "pushd /foo" pushes CURRENT directory - and jumps to /foo. The current behaviour is closer to the documented - behaviour, and should not trip anyone. - - -Older releases -============== - -Changes in earlier releases of IPython are described in the older file -``ChangeLog``. Please refer to this document for details. - diff --git a/docs/sphinxext/apigen.py b/docs/sphinxext/apigen.py index afce9da..1237409 100644 --- a/docs/sphinxext/apigen.py +++ b/docs/sphinxext/apigen.py @@ -251,6 +251,7 @@ class ApiDocWriter(object): ad += ' :members:\n' \ ' :undoc-members:\n' \ ' :show-inheritance:\n' \ + ' :inherited-members:\n' \ '\n' \ ' .. automethod:: __init__\n' if multi_fx: diff --git a/docs/sphinxext/ipython_console_highlighting.py b/docs/sphinxext/ipython_console_highlighting.py index 00f9abd..217b779 100644 --- a/docs/sphinxext/ipython_console_highlighting.py +++ b/docs/sphinxext/ipython_console_highlighting.py @@ -1,4 +1,8 @@ """reST directive for syntax-highlighting ipython interactive sessions. + +XXX - See what improvements can be made based on the new (as of Sept 2009) +'pycon' lexer for the python console. At the very least it will give better +highlighted tracebacks. """ #----------------------------------------------------------------------------- @@ -15,7 +19,6 @@ from pygments.token import Comment, Generic from sphinx import highlighting - #----------------------------------------------------------------------------- # Global constants line_re = re.compile('.*?\n') @@ -77,8 +80,11 @@ class IPythonConsoleLexer(Lexer): [(0, Generic.Prompt, continue_prompt.group())])) curcode += line[continue_prompt.end():] elif output_prompt is not None: + # Use the 'error' token for output. We should probably make + # our own token, but error is typicaly in a bright color like + # red, so it works fine for our output prompts. insertions.append((len(curcode), - [(0, Generic.Output, output_prompt.group())])) + [(0, Generic.Error, output_prompt.group())])) curcode += line[output_prompt.end():] else: if curcode: @@ -93,6 +99,16 @@ class IPythonConsoleLexer(Lexer): pylexer.get_tokens_unprocessed(curcode)): yield item + +def setup(app): + """Setup as a sphinx extension.""" + + # This is only a lexer, so adding it below to pygments appears sufficient. + # But if somebody knows that the right API usage should be to do that via + # sphinx, by all means fix it here. At least having this setup.py + # suppresses the sphinx warning we'd get without it. + pass + #----------------------------------------------------------------------------- # Register the extension as a valid pygments lexer highlighting.lexers['ipython'] = IPythonConsoleLexer() diff --git a/docs/sphinxext/only_directives.py b/docs/sphinxext/only_directives.py deleted file mode 100644 index 57d70a4..0000000 --- a/docs/sphinxext/only_directives.py +++ /dev/null @@ -1,93 +0,0 @@ -# -# A pair of directives for inserting content that will only appear in -# either html or latex. -# - -from docutils.nodes import Body, Element -from docutils.writers.html4css1 import HTMLTranslator -from docutils.parsers.rst import directives - -# The sphinx API has changed, so we try both the old and new import forms -try: - from sphinx.latexwriter import LaTeXTranslator -except ImportError: - from sphinx.writers.latex import LaTeXTranslator - - -class html_only(Body, Element): - pass - -class latex_only(Body, Element): - pass - -def run(content, node_class, state, content_offset): - text = '\n'.join(content) - node = node_class(text) - state.nested_parse(content, content_offset, node) - return [node] - -try: - from docutils.parsers.rst import Directive -except ImportError: - from docutils.parsers.rst.directives import _directives - - def html_only_directive(name, arguments, options, content, lineno, - content_offset, block_text, state, state_machine): - return run(content, html_only, state, content_offset) - - def latex_only_directive(name, arguments, options, content, lineno, - content_offset, block_text, state, state_machine): - return run(content, latex_only, state, content_offset) - - for func in (html_only_directive, latex_only_directive): - func.content = 1 - func.options = {} - func.arguments = None - - _directives['htmlonly'] = html_only_directive - _directives['latexonly'] = latex_only_directive -else: - class OnlyDirective(Directive): - has_content = True - required_arguments = 0 - optional_arguments = 0 - final_argument_whitespace = True - option_spec = {} - - def run(self): - self.assert_has_content() - return run(self.content, self.node_class, - self.state, self.content_offset) - - class HtmlOnlyDirective(OnlyDirective): - node_class = html_only - - class LatexOnlyDirective(OnlyDirective): - node_class = latex_only - - directives.register_directive('htmlonly', HtmlOnlyDirective) - directives.register_directive('latexonly', LatexOnlyDirective) - -def setup(app): - app.add_node(html_only) - app.add_node(latex_only) - - # Add visit/depart methods to HTML-Translator: - def visit_perform(self, node): - pass - def depart_perform(self, node): - pass - def visit_ignore(self, node): - node.children = [] - def depart_ignore(self, node): - node.children = [] - - HTMLTranslator.visit_html_only = visit_perform - HTMLTranslator.depart_html_only = depart_perform - HTMLTranslator.visit_latex_only = visit_ignore - HTMLTranslator.depart_latex_only = depart_ignore - - LaTeXTranslator.visit_html_only = visit_ignore - LaTeXTranslator.depart_html_only = depart_ignore - LaTeXTranslator.visit_latex_only = visit_perform - LaTeXTranslator.depart_latex_only = depart_perform diff --git a/docs/sphinxext/plot_directive.py b/docs/sphinxext/plot_directive.py deleted file mode 100644 index a1a0621..0000000 --- a/docs/sphinxext/plot_directive.py +++ /dev/null @@ -1,155 +0,0 @@ -"""A special directive for including a matplotlib plot. - -Given a path to a .py file, it includes the source code inline, then: - -- On HTML, will include a .png with a link to a high-res .png. - -- On LaTeX, will include a .pdf - -This directive supports all of the options of the `image` directive, -except for `target` (since plot will add its own target). - -Additionally, if the :include-source: option is provided, the literal -source will be included inline, as well as a link to the source. -""" - -import sys, os, glob, shutil -from docutils.parsers.rst import directives - -try: - # docutils 0.4 - from docutils.parsers.rst.directives.images import align -except ImportError: - # docutils 0.5 - from docutils.parsers.rst.directives.images import Image - align = Image.align - - -import matplotlib -import IPython.Shell -matplotlib.use('Agg') -import matplotlib.pyplot as plt - -mplshell = IPython.Shell.MatplotlibShell('mpl') - -options = {'alt': directives.unchanged, - 'height': directives.length_or_unitless, - 'width': directives.length_or_percentage_or_unitless, - 'scale': directives.nonnegative_int, - 'align': align, - 'class': directives.class_option, - 'include-source': directives.flag } - -template = """ -.. htmlonly:: - - [`source code <../%(srcdir)s/%(basename)s.py>`__, - `png <../%(srcdir)s/%(basename)s.hires.png>`__, - `pdf <../%(srcdir)s/%(basename)s.pdf>`__] - - .. image:: ../%(srcdir)s/%(basename)s.png -%(options)s - -.. latexonly:: - .. image:: ../%(srcdir)s/%(basename)s.pdf -%(options)s - -""" - -def makefig(fullpath, outdir): - """ - run a pyplot script and save the low and high res PNGs and a PDF in _static - """ - - fullpath = str(fullpath) # todo, why is unicode breaking this - formats = [('png', 100), - ('hires.png', 200), - ('pdf', 72), - ] - - basedir, fname = os.path.split(fullpath) - basename, ext = os.path.splitext(fname) - all_exists = True - - if basedir != outdir: - shutil.copyfile(fullpath, os.path.join(outdir, fname)) - - for format, dpi in formats: - outname = os.path.join(outdir, '%s.%s' % (basename, format)) - if not os.path.exists(outname): - all_exists = False - break - - if all_exists: - print ' already have %s'%fullpath - return - - print ' building %s'%fullpath - plt.close('all') # we need to clear between runs - matplotlib.rcdefaults() - - mplshell.magic_run(fullpath) - for format, dpi in formats: - outname = os.path.join(outdir, '%s.%s' % (basename, format)) - if os.path.exists(outname): continue - plt.savefig(outname, dpi=dpi) - -def run(arguments, options, state_machine, lineno): - reference = directives.uri(arguments[0]) - basedir, fname = os.path.split(reference) - basename, ext = os.path.splitext(fname) - - # todo - should we be using the _static dir for the outdir, I am - # not sure we want to corrupt that dir with autogenerated files - # since it also has permanent files in it which makes it difficult - # to clean (save an rm -rf followed by and svn up) - srcdir = 'pyplots' - - makefig(os.path.join(srcdir, reference), srcdir) - - # todo: it is not great design to assume the makefile is putting - # the figs into the right place, so we may want to do that here instead. - - if options.has_key('include-source'): - lines = ['.. literalinclude:: ../pyplots/%(reference)s' % locals()] - del options['include-source'] - else: - lines = [] - - options = [' :%s: %s' % (key, val) for key, val in - options.items()] - options = "\n".join(options) - - lines.extend((template % locals()).split('\n')) - - state_machine.insert_input( - lines, state_machine.input_lines.source(0)) - return [] - - -try: - from docutils.parsers.rst import Directive -except ImportError: - from docutils.parsers.rst.directives import _directives - - def plot_directive(name, arguments, options, content, lineno, - content_offset, block_text, state, state_machine): - return run(arguments, options, state_machine, lineno) - plot_directive.__doc__ = __doc__ - plot_directive.arguments = (1, 0, 1) - plot_directive.options = options - - _directives['plot'] = plot_directive -else: - class plot_directive(Directive): - required_arguments = 1 - optional_arguments = 0 - final_argument_whitespace = True - option_spec = options - def run(self): - return run(self.arguments, self.options, - self.state_machine, self.lineno) - plot_directive.__doc__ = __doc__ - - directives.register_directive('plot', plot_directive) - diff --git a/ipython.py b/ipython.py index 5be0994..0185eeb 100755 --- a/ipython.py +++ b/ipython.py @@ -7,5 +7,6 @@ in './scripts' directory. This file is here (ipython source root directory) to facilitate non-root 'zero-installation' (just copy the source tree somewhere and run ipython.py) and development. """ -import IPython.Shell -IPython.Shell.start().mainloop() +from IPython.core.ipapp import launch_new_instance + +launch_new_instance() diff --git a/sandbox/asynparallel.py b/sandbox/asynparallel.py deleted file mode 100644 index 35b815b..0000000 --- a/sandbox/asynparallel.py +++ /dev/null @@ -1,172 +0,0 @@ -#!/usr/bin/env python -"""A parallel tasking tool that uses asynchronous programming. This uses -blocking client to get taskid, but returns a Deferred as the result of -run(). Users should attach their callbacks on these Deferreds. - -Only returning of results is asynchronous. Submitting tasks and getting task -ids are done synchronously. - -Yichun Wei 03/2008 -""" - -import inspect -import itertools -import numpy as N - -from twisted.python import log -from ipython1.kernel import client -from ipython1.kernel.client import Task - -""" After http://trac.pocoo.org/repos/pocoo/trunk/pocoo/utils/decorators.py -""" -class submit_job(object): - """ a decorator factory: takes a MultiEngineClient a TaskClient, returns a - decorator, that makes a call to the decorated func as a task in ipython1 - and submit it to IPython1 controller: - """ - def __init__(self, rc, tc): - self.rc = rc - self.tc = tc - - def __call__(self, func): - return self._decorate(func) - - def _getinfo(self, func): - assert inspect.ismethod(func) or inspect.isfunction(func) - regargs, varargs, varkwargs, defaults = inspect.getargspec(func) - argnames = list(regargs) - if varargs: - argnames.append(varargs) - if varkwargs: - argnames.append(varkwargs) - counter = itertools.count() - fullsign = inspect.formatargspec( - regargs, varargs, varkwargs, defaults, - formatvalue=lambda value: '=defarg[%i]' % counter.next())[1:-1] - shortsign = inspect.formatargspec( - regargs, varargs, varkwargs, defaults, - formatvalue=lambda value: '')[1:-1] - dic = dict(('arg%s' % n, name) for n, name in enumerate(argnames)) - dic.update(name=func.__name__, argnames=argnames, shortsign=shortsign, - fullsign = fullsign, defarg = func.func_defaults or ()) - return dic - - def _decorate(self, func): - """ - Takes a function and a remote controller and returns a function - decorated that is going to submit the job with the controller. - The decorated function is obtained by evaluating a lambda - function with the correct signature. - - the TaskController setupNS doesn't cope with functions, but we - can use RemoteController to push functions/modules into engines. - - Changes: - 200803. In new ipython1, we use push_function for functions. - """ - rc, tc = self.rc, self.tc - infodict = self._getinfo(func) - if 'rc' in infodict['argnames']: - raise NameError, "You cannot use rc as argument names!" - - # we assume the engines' namepace has been prepared. - # ns[func.__name__] is already the decorated closure function. - # we need to change it back to the original function: - ns = {} - ns[func.__name__] = func - - # push func and all its environment/prerequesites to engines - rc.push_function(ns, block=True) # note it is nonblock by default, not know if it causes problems - - def do_submit_func(*args, **kwds): - jobns = {} - - # Initialize job namespace with args that have default args - # now we support calls that uses default args - for n in infodict['fullsign'].split(','): - try: - vname, var = n.split('=') - vname, var = vname.strip(), var.strip() - except: # no defarg, one of vname, var is None - pass - else: - jobns.setdefault(vname, eval(var, infodict)) - - # push args and kwds, overwritting default args if needed. - nokwds = dict((n,v) for n,v in zip(infodict['argnames'], args)) # truncated - jobns.update(nokwds) - jobns.update(kwds) - - task = Task('a_very_long_and_rare_name = %(name)s(%(shortsign)s)' % infodict, - pull=['a_very_long_and_rare_name'], push=jobns,) - jobid = tc.run(task) - # res is a deferred, one can attach callbacks on it - res = tc.task_controller.get_task_result(jobid, block=True) - res.addCallback(lambda x: x.ns['a_very_long_and_rare_name']) - res.addErrback(log.err) - return res - - do_submit_func.rc = rc - do_submit_func.tc = tc - return do_submit_func - - -def parallelized(rc, tc, initstrlist=[]): - """ rc - remote controller - tc - taks controller - strlist - a list of str that's being executed on engines. - """ - for cmd in initstrlist: - rc.execute(cmd, block=True) - return submit_job(rc, tc) - - -from twisted.internet import defer -from numpy import array, nan - -def pmap(func, parr, **kwds): - """Run func on every element of parr (array), using the elements - as the only one parameter (so you can usually use a dict that - wraps many parameters). -> a result array of Deferreds with the - same shape. func.tc will be used as the taskclient. - - **kwds are passed on to func, not changed. - """ - assert func.tc - tc = func.tc - - def run(p, **kwds): - if p: - return func(p, **kwds) - else: - return defer.succeed(nan) - - reslist = [run(p, **kwds).addErrback(log.err) for p in parr.flat] - resarr = array(reslist) - resarr.shape = parr.shape - return resarr - - -if __name__=='__main__': - - rc = client.MultiEngineClient(client.default_address) - tc = client.TaskClient(client.default_task_address) - - # if commenting out the decorator you get a local running version - # instantly - @parallelized(rc, tc) - def f(a, b=1): - #from time import sleep - #sleep(1) - print "a,b=", a,b - return a+b - - def showres(x): - print 'ans:',x - - res = f(11,5) - res.addCallback(showres) - - # this is not necessary in Twisted 8.0 - from twisted.internet import reactor - reactor.run() diff --git a/sandbox/config.py b/sandbox/config.py deleted file mode 100644 index e88fb8a..0000000 --- a/sandbox/config.py +++ /dev/null @@ -1,179 +0,0 @@ -"""Load and store configuration objects. - -Test this module using - -nosetests -v --with-doctest --doctest-tests IPython.config - -""" - -from __future__ import with_statement -from contextlib import contextmanager - -import inspect -import types -from IPython.config import traitlets -from traitlets import Traitlet -from IPython.external.configobj import ConfigObj - -def debug(s): - import sys - sys.stderr.write(str(s) + '\n') - -@contextmanager -def raw(config): - """Context manager for accessing traitlets directly. - - """ - config.__getattribute__('',raw_access=True) - yield config - config.__getattribute__('',raw_access=False) - -class Config(object): - """ - Implementation Notes - ==================== - All instances of the same Config class share properties. Therefore, - - >>> class Sample(Config): - ... my_float = traitlets.Float(3) - - >>> s0 = Sample() - >>> s1 = Sample() - >>> s0.my_float = 5 - >>> s0.my_float == s1.my_float - True - - """ - def __init__(self): - # Instantiate subconfigs - with raw(self): - subconfigs = [(n,v) for n,v in - inspect.getmembers(self, inspect.isclass) - if not n.startswith('__')] - - for n,v in subconfigs: - setattr(self, n, v()) - - def __getattribute__(self,attr,raw_access=None, - _ns={'raw_access':False}): - if raw_access is not None: - _ns['raw_access'] = raw_access - return - - obj = object.__getattribute__(self,attr) - if isinstance(obj,Traitlet) and not _ns['raw_access']: - return obj.__call__() - else: - return obj - - def __setattr__(self,attr,value): - obj = object.__getattribute__(self,attr) - if isinstance(obj,Traitlet): - obj(value) - else: - self.__dict__[attr] = value - - def __str__(self,level=1,only_modified=True): - ci = ConfigInspector(self) - out = '' - spacer = ' '*(level-1) - - # Add traitlet representations - for p,v in ci.properties: - if (v.modified and only_modified) or not only_modified: - out += spacer + '%s = %s\n' % (p,v) - - # Add subconfig representations - for (n,v) in ci.subconfigs: - sub_str = v.__str__(level=level+1,only_modified=only_modified) - if sub_str: - out += '\n' + spacer + '[' * level + ('%s' % n) \ - + ']'*level + '\n' - out += sub_str - - return out - - def __iadd__(self,source): - """Load configuration from filename, and update self. - - """ - if not isinstance(source,dict): - source = ConfigObj(source, unrepr=True) - update_from_dict(self,source) - return self - - -class ConfigInspector(object): - """Allow the inspection of Config objects. - - """ - def __init__(self,config): - self._config = config - - @property - def properties(self): - "Return all traitlet names." - with raw(self._config): - return inspect.getmembers(self._config, - lambda obj: isinstance(obj, Traitlet)) - - @property - def subconfigs(self): - "Return all subconfig names and values." - with raw(self._config): - return [(n,v) for n,v in - inspect.getmembers(self._config, - lambda obj: isinstance(obj,Config)) - if not n.startswith('__')] - - def reset(self): - for (p,v) in self.properties: - v.reset() - - for (s,v) in self.subconfigs: - ConfigInspector(v).reset() - -def update_from_dict(config,d): - """Propagate the values of the dictionary to the given configuration. - - Useful to load configobj instances. - - """ - for k,v in d.items(): - try: - prop_or_subconfig = getattr(config, k) - except AttributeError: - print "Invalid section/property in config file: %s" % k - else: - if isinstance(v,dict): - update_from_dict(prop_or_subconfig,v) - else: - setattr(config, k, v) - -def dict_from_config(config,only_modified=True): - """Create a dictionary from a Config object.""" - ci = ConfigInspector(config) - out = {} - - for p,v in ci.properties: - if (v.modified and only_modified) or not only_modified: - out[p] = v - - for s,v in ci.subconfigs: - d = dict_from_config(v,only_modified) - if d != {}: - out[s] = d - - return out - -def write(config, target): - """Write a configuration to file. - - """ - if isinstance(target, str): - target = open(target, 'w+') - target.flush() - target.seek(0) - - confobj = ConfigObj(dict_from_config(config), unrepr=True) - confobj.write(target) diff --git a/sandbox/minitraits.py b/sandbox/minitraits.py deleted file mode 100644 index 7442554..0000000 --- a/sandbox/minitraits.py +++ /dev/null @@ -1,119 +0,0 @@ -import types - -class AttributeBase(object): - - def __get__(self, inst, cls=None): - if inst is None: - return self - try: - return inst._attributes[self.name] - except KeyError: - raise AttributeError("object has no attribute %r" % self.name) - - def __set__(self, inst, value): - actualValue = self.validate(inst, self.name, value) - inst._attributes[self.name] = actualValue - - def validate(self, inst, name, value): - raise NotImplementedError("validate must be implemented by a subclass") - -class NameFinder(type): - - def __new__(cls, name, bases, classdict): - attributeList = [] - for k,v in classdict.iteritems(): - if isinstance(v, AttributeBase): - v.name = k - attributeList.append(k) - classdict['_attributeList'] = attributeList - return type.__new__(cls, name, bases, classdict) - -class HasAttributes(object): - __metaclass__ = NameFinder - - def __init__(self): - self._attributes = {} - - def getAttributeNames(self): - return self._attributeList - - def getAttributesOfType(self, t, default=None): - result = {} - for a in self._attributeList: - if self.__class__.__dict__[a].__class__ == t: - try: - value = getattr(self, a) - except AttributeError: - value = None - result[a] = value - return result - -class TypedAttribute(AttributeBase): - - def validate(self, inst, name, value): - if type(value) != self._type: - raise TypeError("attribute %s must be of type %s" % (name, self._type)) - else: - return value - -# class Option(TypedAttribute): -# -# _type = types.IntType -# -# class Param(TypedAttribute): -# -# _type = types.FloatType -# -# class String(TypedAttribute): -# -# _type = types.StringType - -class TypedSequenceAttribute(AttributeBase): - - def validate(self, inst, name, value): - if type(value) != types.TupleType and type(value) != types.ListType: - raise TypeError("attribute %s must be a list or tuple" % (name)) - else: - for item in value: - if type(item) != self._subtype: - raise TypeError("attribute %s must be a list or tuple of items with type %s" % (name, self._subtype)) - return value - -# class Instance(AttributeBase): -# -# def __init__(self, cls): -# self.cls = cls -# -# def validate(self, inst, name, value): -# if not isinstance(value, self.cls): -# raise TypeError("attribute %s must be an instance of class %s" % (name, self.cls)) -# else: -# return value - - -# class OptVec(TypedSequenceAttribute): -# -# _subtype = types.IntType -# -# class PrmVec(TypedSequenceAttribute): -# -# _subtype = types.FloatType -# -# class StrVec(TypedSequenceAttribute): -# -# _subtype = types.StringType -# -# -# class Bar(HasAttributes): -# -# a = Option() -# -# class Foo(HasAttributes): -# -# a = Option() -# b = Param() -# c = String() -# d = OptVec() -# e = PrmVec() -# f = StrVec() -# h = Instance(Bar) \ No newline at end of file diff --git a/sandbox/sample_config.py b/sandbox/sample_config.py deleted file mode 100644 index 0efddfe..0000000 --- a/sandbox/sample_config.py +++ /dev/null @@ -1,19 +0,0 @@ -from IPython.config.api import * - -class SubSubSample(Config): - my_int = Int(3) - - -class Sample(Config): - my_float = Float(3) - my_choice = Enum('a','b','c') - - class MiddleSection(Config): - left_alone = Enum('1','2','c') - unknown_mod = Module('asd') - - class SubSample(Config): - subsample_uri = URI('http://localhost:8080') - - # Example of how to include external config - SubSubSample = SubSubSample() diff --git a/sandbox/test_config.py b/sandbox/test_config.py deleted file mode 100644 index 27ab192..0000000 --- a/sandbox/test_config.py +++ /dev/null @@ -1,135 +0,0 @@ -""" -# Test utilities - ->>> import os - ->>> def dict_as_sorted_list(d): -... for k in d: -... if isinstance(d[k],dict): -... d[k] = dict_as_sorted_list(d[k]) -... return sorted(d.items()) - ->>> def pprint(d,level=0): -... if isinstance(d,dict): -... d = dict_as_sorted_list(d) -... for item,value in d: -... if isinstance(value,list): -... print "%s%s" % (' '*level, item) -... pprint(value,level+2) -... else: -... print "%s%s: %s" % (' '*level, item, value) - - -# Tests - ->>> from IPython.config.api import * ->>> from sample_config import * - ->>> s = Sample() ->>> print s.my_float -3.0 ->>> s.my_float = 4 ->>> print s.my_float -4.0 ->>> print type(s.my_float) - ->>> s.SubSample.SubSubSample.my_int = 5.0 ->>> print s.SubSample.SubSubSample.my_int -5 - ->>> i = ConfigInspector(s) ->>> print i.properties -[('my_choice', 'a'), ('my_float', 4.0)] ->>> print tuple(s for s,v in i.subconfigs) -('MiddleSection', 'SubSample') - ->>> print s -my_float = 4.0 - -[SubSample] - - [[SubSubSample]] - my_int = 5 - - ->>> import tempfile ->>> fn = tempfile.mktemp() ->>> f = open(fn,'w') ->>> f.write(str(s)) ->>> f.close() - ->>> s += fn - ->>> from IPython.external.configobj import ConfigObj ->>> c = ConfigObj(fn) ->>> c['SubSample']['subsample_uri'] = 'http://ipython.scipy.org' - ->>> s += c ->>> print s -my_float = 4.0 - -[SubSample] - subsample_uri = 'http://ipython.scipy.org' - - [[SubSubSample]] - my_int = 5 - - ->>> pprint(dict_from_config(s,only_modified=False)) -MiddleSection - left_alone: '1' - unknown_mod: 'asd' -SubSample - SubSubSample - my_int: 5 - subsample_uri: 'http://ipython.scipy.org' -my_choice: 'a' -my_float: 4.0 - ->>> pprint(dict_from_config(s)) -SubSample - SubSubSample - my_int: 5 - subsample_uri: 'http://ipython.scipy.org' -my_float: 4.0 - -Test roundtripping: - ->>> fn = tempfile.mktemp() ->>> f = open(fn, 'w') ->>> f.write(''' -... [MiddleSection] -... # some comment here -... left_alone = 'c' -... ''') ->>> f.close() - ->>> s += fn - ->>> pprint(dict_from_config(s)) -MiddleSection - left_alone: 'c' -SubSample - SubSubSample - my_int: 5 - subsample_uri: 'http://ipython.scipy.org' -my_float: 4.0 - ->>> write(s, fn) ->>> f = file(fn,'r') ->>> ConfigInspector(s).reset() ->>> pprint(dict_from_config(s)) - ->>> s += fn ->>> os.unlink(fn) ->>> pprint(dict_from_config(s)) -MiddleSection - left_alone: 'c' -SubSample - SubSubSample - my_int: 5 - subsample_uri: 'http://ipython.scipy.org' -my_float: 4.0 - - -""" diff --git a/sandbox/traitlets.py b/sandbox/traitlets.py deleted file mode 100644 index f1720c5..0000000 --- a/sandbox/traitlets.py +++ /dev/null @@ -1,322 +0,0 @@ -"""Traitlets -- a light-weight meta-class free stand-in for Traits. - -Traitlet behaviour -================== -- Automatic casting, equivalent to traits.C* classes, e.g. CFloat, CBool etc. - -- By default, validation is done by attempting to cast a given value - to the underlying type, e.g. bool for Bool, float for Float etc. - -- To set or get a Traitlet value, use the ()-operator. E.g. - - >>> b = Bool(False) - >>> b(True) - >>> print b # returns a string representation of the Traitlet - True - >>> print b() # returns the underlying bool object - True - - This makes it possible to change values "in-place", unlike an assigment - of the form - - >>> c = Bool(False) - >>> c = True - - which results in - - >>> print type(b), type(c) - - -- Each Traitlet keeps track of its modification state, e.g. - - >>> c = Bool(False) - >>> print c.modified - False - >>> c(False) - >>> print c.modified - False - >>> c(True) - >>> print c.modified - True - -How to customize Traitlets -========================== - -The easiest way to create a new Traitlet is by wrapping an underlying -Python type. This is done by setting the "_type" class attribute. For -example, creating an int-like Traitlet is done as follows: - ->>> class MyInt(Traitlet): -... _type = int - ->>> i = MyInt(3) ->>> i(4) ->>> print i -4 - ->>> try: -... i('a') -... except ValidationError: -... pass # this is expected -... else: -... "This should not be reached." - -Furthermore, the following methods are provided for finer grained -control of validation and assignment: - - - validate(self,value) - Ensure that "value" is valid. If not, raise an exception of any kind - with a suitable error message, which is reported to the user. - - - prepare_value(self) - When using the ()-operator to query the underlying Traitlet value, - that value is first passed through prepare_value. For example: - - >>> class Eval(Traitlet): - ... _type = str - ... - ... def prepare_value(self): - ... return eval(self._value) - - >>> x = Eval('1+1') - >>> print x - '1+1' - >>> print x() - 2 - - - __repr__(self) - By default, repr(self._value) is returned. This can be customised - to, for example, first call prepare_value and return the repr of - the resulting object. - -""" - -import re -import types - -class ValidationError(Exception): - pass - -class Traitlet(object): - """Traitlet which knows its modification state. - - """ - def __init__(self, value): - "Validate and store the default value. State is 'unmodified'." - self._type = getattr(self,'_type',None) - value = self._parse_validation(value) - self._default_value = value - self.reset() - - def reset(self): - self._value = self._default_value - self._changed = False - - def validate(self, value): - "Validate the given value." - if self._type is not None: - self._type(value) - - def _parse_validation(self, value): - """Run validation and return a descriptive error if needed. - - """ - try: - self.validate(value) - except Exception, e: - err_message = 'Cannot convert "%s" to %s' % \ - (value, self.__class__.__name__.lower()) - if e.message: - err_message += ': %s' % e.message - raise ValidationError(err_message) - else: - # Cast to appropriate type before storing - if self._type is not None: - value = self._type(value) - return value - - def prepare_value(self): - """Run on value before it is ever returned to the user. - - """ - return self._value - - def __call__(self,value=None): - """Query or set value depending on whether `value` is specified. - - """ - if value is None: - return self.prepare_value() - - self._value = self._parse_validation(value) - self._changed = (self._value != self._default_value) - - @property - def modified(self): - "Whether value has changed from original definition." - return self._changed - - def __repr__(self): - """This class is represented by the underlying repr. Used when - dumping value to file. - - """ - return repr(self._value) - -class Float(Traitlet): - """ - >>> f = Float(0) - >>> print f.modified - False - - >>> f(3) - >>> print f() - 3.0 - >>> print f.modified - True - - >>> f(0) - >>> print f() - 0.0 - >>> print f.modified - False - - >>> try: - ... f('a') - ... except ValidationError: - ... pass - - """ - _type = float - -class Enum(Traitlet): - """ - >>> c = Enum('a','b','c') - >>> print c() - a - - >>> try: - ... c('unknown') - ... except ValidationError: - ... pass - - >>> print c.modified - False - - >>> c('b') - >>> print c() - b - - """ - def __init__(self, *options): - self._options = options - super(Enum,self).__init__(options[0]) - - def validate(self, value): - if not value in self._options: - raise ValueError('must be one of %s' % str(self._options)) - -class Module(Traitlet): - """ - >>> m = Module('some.unknown.module') - >>> print m - 'some.unknown.module' - - >>> m = Module('re') - >>> assert type(m()) is types.ModuleType - - """ - _type = str - - def prepare_value(self): - try: - module = eval(self._value) - except: - module = None - - if type(module) is not types.ModuleType: - raise ValueError("Invalid module name: %s" % self._value) - else: - return module - - -class URI(Traitlet): - """ - >>> u = URI('http://') - - >>> try: - ... u = URI('something.else') - ... except ValidationError: - ... pass - - >>> u = URI('http://ipython.scipy.org/') - >>> print u - 'http://ipython.scipy.org/' - - """ - _regexp = re.compile(r'^[a-zA-Z]+:\/\/') - _type = str - - def validate(self, uri): - if not self._regexp.match(uri): - raise ValueError() - -class Int(Traitlet): - """ - >>> i = Int(3.5) - >>> print i - 3 - >>> print i() - 3 - - >>> i = Int('4') - >>> print i - 4 - - >>> try: - ... i = Int('a') - ... except ValidationError: - ... pass - ... else: - ... raise "Should fail" - - """ - _type = int - -class Bool(Traitlet): - """ - >>> b = Bool(2) - >>> print b - True - >>> print b() - True - - >>> b = Bool('True') - >>> print b - True - >>> b(True) - >>> print b.modified - False - - >>> print Bool(0) - False - - """ - _type = bool - -class Unicode(Traitlet): - """ - >>> u = Unicode(123) - >>> print u - u'123' - - >>> u = Unicode('123') - >>> print u.modified - False - - >>> u('hello world') - >>> print u - u'hello world' - - """ - _type = unicode diff --git a/scripts/ipython b/scripts/ipython deleted file mode 100755 index 1f9744d..0000000 --- a/scripts/ipython +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -"""IPython -- An enhanced Interactive Python - -This is just the startup wrapper script, kept deliberately to a minimum. - -The shell's mainloop() takes an optional argument, sys_exit (default=0). If -set to 1, it calls sys.exit() at exit time. You can use the following code in -your PYTHONSTARTUP file: - -import IPython -IPython.Shell.IPShell().mainloop(sys_exit=1) - -[or simply IPython.Shell.IPShell().mainloop(1) ] - -and IPython will be your working environment when you start python. The final -sys.exit() call will make python exit transparently when IPython finishes, so -you don't have an extra prompt to get out of. - -This is probably useful to developers who manage multiple Python versions and -don't want to have correspondingly multiple IPython versions. Note that in -this mode, there is no way to pass IPython any command-line options, as those -are trapped first by Python itself. -""" - -import IPython.Shell - -IPython.Shell.start().mainloop() diff --git a/scripts/ipython_win_post_install.py b/scripts/ipython_win_post_install.py index fb1c2bc..a965ee8 100755 --- a/scripts/ipython_win_post_install.py +++ b/scripts/ipython_win_post_install.py @@ -13,7 +13,7 @@ def mkshortcut(target,description,link_file,*args,**kw): def install(): """Routine to be run by the win32 installer with the -install switch.""" - from IPython.Release import version + from IPython.core.release import version # Get some system constants prefix = sys.prefix @@ -62,18 +62,10 @@ def install(): cmd = '"%s" -p sh' % ipybase mkshortcut(python,'IPython (command prompt mode)',link,cmd) - link = pjoin(ip_start_menu, 'pylab.lnk') - cmd = '"%s" -pylab' % ipybase - mkshortcut(python,'IPython (PyLab mode)',link,cmd) - link = pjoin(ip_start_menu, 'scipy.lnk') - cmd = '"%s" -pylab -p scipy' % ipybase + cmd = '"%s" -p scipy' % ipybase mkshortcut(python,'IPython (scipy profile)',link,cmd) - - link = pjoin(ip_start_menu, 'IPython test suite.lnk') - cmd = '"%s" -vv' % pjoin(scripts, 'iptest') - mkshortcut(python,'Run the IPython test suite',link,cmd) - + link = pjoin(ip_start_menu, 'ipcontroller.lnk') cmd = '"%s" -xy' % pjoin(scripts, 'ipcontroller') mkshortcut(python,'IPython controller',link,cmd) diff --git a/setup.py b/setup.py index 27d2db5..e0179ef 100755 --- a/setup.py +++ b/setup.py @@ -29,8 +29,7 @@ if os.path.exists('MANIFEST'): os.remove('MANIFEST') from distutils.core import setup -# Local imports -from IPython.genutils import target_update +from IPython.utils.genutils import target_update from setupbase import ( setup_args, @@ -42,6 +41,7 @@ from setupbase import ( ) isfile = os.path.isfile +pjoin = os.path.join #------------------------------------------------------------------------------- # Handle OS specific things @@ -123,7 +123,7 @@ if len(sys.argv) >= 2 and sys.argv[1] in ('sdist','bdist_rpm'): # First, compute all the dependencies that can force us to rebuild the # docs. Start with the main release file that contains metadata - docdeps = ['IPython/Release.py'] + docdeps = ['IPython/core/release.py'] # Inculde all the reST sources pjoin = os.path.join for dirpath,dirnames,filenames in os.walk('docs/source'): @@ -167,13 +167,14 @@ if 'setuptools' in sys.modules: setuptools_extra_args['zip_safe'] = False setuptools_extra_args['entry_points'] = { 'console_scripts': [ - 'ipython = IPython.ipapi:launch_new_instance', - 'pycolor = IPython.PyColorize:main', - 'ipcontroller = IPython.kernel.scripts.ipcontroller:main', - 'ipengine = IPython.kernel.scripts.ipengine:main', - 'ipcluster = IPython.kernel.scripts.ipcluster:main', + 'ipython = IPython.core.ipapp:launch_new_instance', + 'pycolor = IPython.utils.PyColorize:main', + 'ipcontroller = IPython.kernel.ipcontrollerapp:launch_new_instance', + 'ipengine = IPython.kernel.ipengineapp:launch_new_instance', + 'ipcluster = IPython.kernel.ipclusterapp:launch_new_instance', 'ipythonx = IPython.frontend.wx.ipythonx:main', 'iptest = IPython.testing.iptest:main', + 'irunner = IPython.lib.irunner:main' ] } setup_args['extras_require'] = dict( @@ -189,10 +190,6 @@ if 'setuptools' in sys.modules: # Allow setuptools to handle the scripts scripts = [] else: - # package_data of setuptools was introduced to distutils in 2.4 - cfgfiles = filter(isfile, glob('IPython/UserConfig/*')) - if sys.version_info < (2,4): - data_files.append(('lib', 'IPython/UserConfig', cfgfiles)) # If we are running without setuptools, call this function which will # check for dependencies an inform the user what is needed. This is # just to make life easy for users. diff --git a/setup_bdist_egg.py b/setup_bdist_egg.py deleted file mode 100755 index 6736e39..0000000 --- a/setup_bdist_egg.py +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/env python -"""Wrapper to build IPython as an egg (setuptools format).""" - -import os -import sys - -# Add my local path to sys.path -home = os.environ['HOME'] -sys.path.insert(0,'%s/usr/local/lib/python%s/site-packages' % - (home,sys.version[:3])) - -# now, import setuptools and build the actual egg -import setuptools - -sys.argv=['','bdist_egg'] -execfile('setup.py') - -# clean up the junk left around by setuptools -os.system('rm -rf ipython.egg-info') diff --git a/setupbase.py b/setupbase.py index 94a1ca7..2572635 100644 --- a/setupbase.py +++ b/setupbase.py @@ -57,8 +57,8 @@ def file_doesnt_endwith(test,endings): # Basic project information #--------------------------------------------------------------------------- -# Release.py contains version, authors, license, url, keywords, etc. -execfile(pjoin('IPython','Release.py')) +# release.py contains version, authors, license, url, keywords, etc. +execfile(pjoin('IPython','core','release.py')) # Create a dict with the basic information # This dict is eventually passed to setup after additional keys are added. @@ -103,22 +103,30 @@ def find_packages(): Find all of IPython's packages. """ packages = ['IPython'] - add_package(packages, 'config', tests=True) - add_package(packages , 'Extensions') + add_package(packages, 'config', tests=True, others=['default','profile']) + add_package(packages, 'core', tests=True) + add_package(packages, 'deathrow', tests=True) + add_package(packages , 'extensions') add_package(packages, 'external') - add_package(packages, 'gui') - add_package(packages, 'gui.wx') add_package(packages, 'frontend', tests=True) + # Don't include the cocoa frontend for now as it is not stable + if sys.platform == 'darwin' and False: + add_package(packages, 'frontend.cocoa', tests=True, others=['plugin']) + add_package(packages, 'frontend.cocoa.examples') + add_package(packages, 'frontend.cocoa.examples.IPython1Sandbox') + add_package(packages, 'frontend.cocoa.examples.IPython1Sandbox.English.lproj') add_package(packages, 'frontend.process') add_package(packages, 'frontend.wx') - add_package(packages, 'frontend.cocoa', tests=True) - add_package(packages, 'kernel', config=True, tests=True, scripts=True) - add_package(packages, 'kernel.core', config=True, tests=True) + add_package(packages, 'gui') + add_package(packages, 'gui.wx') + add_package(packages, 'kernel', config=False, tests=True, scripts=True) + add_package(packages, 'kernel.core', config=False, tests=True) + add_package(packages, 'lib', tests=True) + add_package(packages, 'quarantine', tests=True) + add_package(packages, 'scripts') add_package(packages, 'testing', tests=True) - add_package(packages, 'tests') add_package(packages, 'testing.plugin', tests=False) - add_package(packages, 'tools', tests=True) - add_package(packages, 'UserConfig') + add_package(packages, 'utils', tests=True) return packages #--------------------------------------------------------------------------- @@ -132,8 +140,7 @@ def find_package_data(): # This is not enough for these things to appear in an sdist. # We need to muck with the MANIFEST to get this to work package_data = { - 'IPython.UserConfig' : ['*'], - 'IPython.tools.tests' : ['*.txt'], + 'IPython.config.userconfig' : ['*'], 'IPython.testing' : ['*.txt'] } return package_data @@ -187,17 +194,24 @@ def find_data_files(): Most of these are docs. """ - docdirbase = 'share/doc/ipython' - manpagebase = 'share/man/man1' - + docdirbase = pjoin('share', 'doc', 'ipython') + manpagebase = pjoin('share', 'man', 'man1') + # Simple file lists can be made by hand - manpages = filter(isfile, glob('docs/man/*.1.gz')) - igridhelpfiles = filter(isfile, glob('IPython/Extensions/igrid_help.*')) + manpages = filter(isfile, glob(pjoin('docs','man','*.1.gz'))) + igridhelpfiles = filter(isfile, glob(pjoin('IPython','extensions','igrid_help.*'))) # For nested structures, use the utility above - example_files = make_dir_struct('data','docs/examples', - pjoin(docdirbase,'examples')) - manual_files = make_dir_struct('data','docs/dist',pjoin(docdirbase,'manual')) + example_files = make_dir_struct( + 'data', + pjoin('docs','examples'), + pjoin(docdirbase,'examples') + ) + manual_files = make_dir_struct( + 'data', + pjoin('docs','dist'), + pjoin(docdirbase,'manual') + ) # And assemble the entire output list data_files = [ ('data',manpagebase, manpages), @@ -245,16 +259,18 @@ def find_scripts(): """ Find IPython's scripts. """ - scripts = ['IPython/kernel/scripts/ipengine', - 'IPython/kernel/scripts/ipcontroller', - 'IPython/kernel/scripts/ipcluster', - 'scripts/ipython', - 'scripts/ipythonx', - 'scripts/ipython-wx', - 'scripts/pycolor', - 'scripts/irunner', - 'scripts/iptest', - ] + kernel_scripts = pjoin('IPython','kernel','scripts') + main_scripts = pjoin('IPython','scripts') + scripts = [pjoin(kernel_scripts, 'ipengine'), + pjoin(kernel_scripts, 'ipcontroller'), + pjoin(kernel_scripts, 'ipcluster'), + pjoin(main_scripts, 'ipython'), + pjoin(main_scripts, 'ipythonx'), + pjoin(main_scripts, 'ipython-wx'), + pjoin(main_scripts, 'pycolor'), + pjoin(main_scripts, 'irunner'), + pjoin(main_scripts, 'iptest') + ] # Script to be run by the windows binary installer after the default setup # routine, to add shortcuts and similar windows-only things. Windows @@ -264,7 +280,7 @@ def find_scripts(): if len(sys.argv) > 2 and ('sdist' in sys.argv or 'bdist_rpm' in sys.argv): print >> sys.stderr,"ERROR: bdist_wininst must be run alone. Exiting." sys.exit(1) - scripts.append('scripts/ipython_win_post_install.py') + scripts.append(pjoin('scripts','ipython_win_post_install.py')) return scripts diff --git a/setupegg.py b/setupegg.py index 0924b19..8ed1c35 100755 --- a/setupegg.py +++ b/setupegg.py @@ -5,8 +5,6 @@ import sys # now, import setuptools and call the actual setup import setuptools -# print sys.argv -#sys.argv=['','bdist_egg'] execfile('setup.py') # clean up the junk left around by setuptools diff --git a/setupexe.py b/setupexe.py deleted file mode 100644 index ef38539..0000000 --- a/setupexe.py +++ /dev/null @@ -1,106 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -r"""Setup script for exe distribution of IPython (does not require python). - -- Requires py2exe - -- install pyreadline *package dir* in ipython root directory by running: - -svn co http://ipython.scipy.org/svn/ipython/pyreadline/branches/maintenance_1.3/pyreadline/ -wget http://ipython.scipy.org/svn/ipython/pyreadline/branches/maintenance_1.3/readline.py - -OR (if you want the latest trunk): - -svn co http://ipython.scipy.org/svn/ipython/pyreadline/trunk/pyreadline - -- Create the distribution in 'dist' by running "python exesetup.py py2exe" - -- Run ipython.exe to go. - -""" - -#***************************************************************************** -# Copyright (C) 2001-2005 Fernando Perez -# -# Distributed under the terms of the BSD License. The full license is in -# the file COPYING, distributed as part of this software. -#***************************************************************************** - -# Stdlib imports -import os -import sys - -from glob import glob - - -# A few handy globals -isfile = os.path.isfile -pjoin = os.path.join - -from distutils.core import setup -from distutils import dir_util -import py2exe - -# update the manuals when building a source dist -# Release.py contains version, authors, license, url, keywords, etc. -execfile(pjoin('IPython','Release.py')) - -# A little utility we'll need below, since glob() does NOT allow you to do -# exclusion on multiple endings! -def file_doesnt_endwith(test,endings): - """Return true if test is a file and its name does NOT end with any - of the strings listed in endings.""" - if not isfile(test): - return False - for e in endings: - if test.endswith(e): - return False - return True - - -egg_extra_kwds = {} - -# Call the setup() routine which does most of the work -setup(name = name, - options = { - 'py2exe': { - 'packages' : ['IPython', 'IPython.Extensions', 'IPython.external', - 'pyreadline'], - 'excludes' : ["Tkconstants","Tkinter","tcl",'IPython.igrid','wx', - 'wxPython','igrid', 'PyQt4', 'zope', 'Zope', 'Zope2', - '_curses','enthought.traits','gtk','qt', 'pydb','idlelib', - ] - - } - }, - version = version, - description = description, - long_description = long_description, - author = authors['Fernando'][0], - author_email = authors['Fernando'][1], - url = url, - download_url = download_url, - license = license, - platforms = platforms, - keywords = keywords, - console = ['ipykit.py'], - - # extra params needed for eggs - **egg_extra_kwds - ) - -minimal_conf = """ -import IPython.ipapi -ip = IPython.ipapi.get() - -ip.load('ipy_kitcfg') -import ipy_profile_sh -""" - -if not os.path.isdir("dist/_ipython"): - print "Creating simple _ipython dir" - os.mkdir("dist/_ipython") - open("dist/_ipython/ipythonrc.ini","w").write("# intentionally blank\n") - open("dist/_ipython/ipy_user_conf.py","w").write(minimal_conf) - if os.path.isdir('bin'): - dir_util.copy_tree('bin','dist/bin') diff --git a/test/test_shell_options.py b/test/test_shell_options.py deleted file mode 100755 index fd00112..0000000 --- a/test/test_shell_options.py +++ /dev/null @@ -1,94 +0,0 @@ -#!/usr/bin/env python -"""A few unit tests for the Shell module. -""" - -from unittest import TestCase, main - -from IPython import Shell - -try: - import matplotlib - has_matplotlib = True -except ImportError: - has_matplotlib = False - -class ShellTestBase(TestCase): - def _test(self,argv,ans): - shell = Shell._select_shell(argv) - err = 'Got %s != %s' % (shell,ans) - self.failUnlessEqual(shell,ans,err) - -class ArgsTestCase(ShellTestBase): - def test_plain(self): - self._test([],Shell.IPShell) - - def test_tkthread(self): - self._test(['-tkthread'],Shell.IPShell) - - def test_gthread(self): - self._test(['-gthread'],Shell.IPShellGTK) - - def test_qthread(self): - self._test(['-qthread'],Shell.IPShellQt) - - def test_q4thread(self): - self._test(['-q4thread'],Shell.IPShellQt4) - - def test_wthread(self): - self._test(['-wthread'],Shell.IPShellWX) - -if has_matplotlib: - class MplArgsTestCase(ShellTestBase): - def setUp(self): - self.backend = matplotlib.rcParams['backend'] - - def tearDown(self): - matplotlib.rcParams['backend'] = self.backend - - def _test(self,argv,ans): - shell = Shell._select_shell(argv) - err = 'Got %s != %s' % (shell,ans) - self.failUnlessEqual(shell,ans,err) - - def test_tk(self): - matplotlib.rcParams['backend'] = 'TkAgg' - self._test(['-pylab'],Shell.IPShellMatplotlib) - - def test_ps(self): - matplotlib.rcParams['backend'] = 'PS' - self._test(['-pylab'],Shell.IPShellMatplotlib) - - def test_gtk(self): - matplotlib.rcParams['backend'] = 'GTKAgg' - self._test(['-pylab'],Shell.IPShellMatplotlibGTK) - - def test_gtk_2(self): - self._test(['-gthread','-pylab'],Shell.IPShellMatplotlibGTK) - self.failUnlessEqual(matplotlib.rcParams['backend'],'GTKAgg') - - def test_qt(self): - matplotlib.rcParams['backend'] = 'QtAgg' - self._test(['-pylab'],Shell.IPShellMatplotlibQt) - - def test_qt_2(self): - self._test(['-qthread','-pylab'],Shell.IPShellMatplotlibQt) - self.failUnlessEqual(matplotlib.rcParams['backend'],'QtAgg') - - def test_qt4(self): - matplotlib.rcParams['backend'] = 'Qt4Agg' - self._test(['-pylab'],Shell.IPShellMatplotlibQt4) - - def test_qt4_2(self): - self._test(['-q4thread','-pylab'],Shell.IPShellMatplotlibQt4) - self.failUnlessEqual(matplotlib.rcParams['backend'],'Qt4Agg') - - def test_wx(self): - matplotlib.rcParams['backend'] = 'WxAgg' - self._test(['-pylab'],Shell.IPShellMatplotlibWX) - - def test_wx_2(self): - self._test(['-pylab','-wthread'],Shell.IPShellMatplotlibWX) - self.failUnlessEqual(matplotlib.rcParams['backend'],'WXAgg') - - -main() diff --git a/tools/build_release b/tools/build_release new file mode 100755 index 0000000..45d6c4c --- /dev/null +++ b/tools/build_release @@ -0,0 +1,42 @@ +#!/usr/bin/env python +"""IPython release build script. +""" +from toollib import * + +# Get main ipython dir, this will raise if it doesn't pass some checks +ipdir = get_ipdir() +cd(ipdir) + +# Load release info +execfile(pjoin('IPython','core','release.py')) + +# Check that everything compiles +compile_tree() + +# Cleanup +for d in ['build','dist',pjoin('docs','build'),pjoin('docs','dist')]: + if os.path.isdir(d): + remove_tree(d) + +# Build source and binary distros +c('./setup.py sdist --formats=gztar') + +# Build version-specific RPMs, where we must use the --python option to ensure +# that the resulting RPM is really built with the requested python version (so +# things go to lib/python2.X/...) +c("python2.5 ./setup.py bdist_rpm --binary-only --release=py25 " + "--python=/usr/bin/python2.5") +c("python2.6 ./setup.py bdist_rpm --binary-only --release=py26 " + "--python=/usr/bin/python2.6") + +# Build eggs +c('python2.5 ./setupegg.py bdist_egg') +c('python2.6 ./setupegg.py bdist_egg') + +# Call the windows build separately, so that the extra Windows scripts don't +# get pulled into Unix builds (setup.py has code which checks for +# bdist_wininst) +c("python setup.py bdist_wininst --install-script=ipython_win_post_install.py") + +# Change name so retarded Vista runs the installer correctly +c("rename 's/linux-i686/win32-setup/' dist/*.exe") diff --git a/tools/check_sources.py b/tools/check_sources.py index 9d90f3c..5b64c6e 100755 --- a/tools/check_sources.py +++ b/tools/check_sources.py @@ -1,15 +1,54 @@ +#!/usr/bin/env python +"""Utility to look for hard tabs and \r characters in all sources. + +Usage: + +./check_sources.py + +It prints summaries and if chosen, line-by-line info of where \\t or \\r +characters can be found in our source tree. +""" + +# Config +# If true, all lines that have tabs are printed, with line number +full_report_tabs = True +# If true, all lines that have tabs are printed, with line number +full_report_rets = False + +# Code begins from IPython.external.path import path -fs = path('..').walkfiles('*.py') -for f in fs: +rets = [] +tabs = [] + +for f in path('..').walkfiles('*.py'): errs = '' cont = f.bytes() if '\t' in cont: errs+='t' + tabs.append(f) if '\r' in cont: errs+='r' + rets.append(f) if errs: print "%3s" % errs, f - + + if 't' in errs and full_report_tabs: + for ln,line in enumerate(f.lines()): + if '\t' in line: + print 'TAB:',ln,':',line, + + if 'r' in errs and full_report_rets: + for ln,line in enumerate(open(f.abspath(),'rb')): + if '\r' in line: + print 'RET:',ln,':',line, + +# Summary at the end, to call cleanup tools if necessary +if tabs: + print 'Hard tabs found. These can be cleaned with untabify:' + for f in tabs: print f, +if rets: + print 'Carriage returns (\\r) found in:' + for f in rets: print f, diff --git a/tools/compile.py b/tools/compile.py index 4a08400..be34c42 100755 --- a/tools/compile.py +++ b/tools/compile.py @@ -1,20 +1,19 @@ #!/usr/bin/env python -"""Call the compile script to check that all code we ship compiles correctly. -""" +"""Script to check that all code in a directory compiles correctly. -import os -import sys +Usage: + compile.py -vstr = '.'.join(map(str,sys.version_info[:2])) +This script verifies that all Python files in the directory where run, and all +of its subdirectories, compile correctly. -stat = os.system('python %s/lib/python%s/compileall.py .' % (sys.prefix,vstr)) +Before a release, call this script from the top-level directory. +""" + +import sys -print -if stat: - print '*** THERE WAS AN ERROR! ***' - print 'See messages above for the actual file that produced it.' -else: - print 'OK' +from toollib import compile_tree -sys.exit(stat) +if __name__ == '__main__': + compile_tree() diff --git a/tools/make_tarball.py b/tools/make_tarball.py index d143bd5..d934ad7 100755 --- a/tools/make_tarball.py +++ b/tools/make_tarball.py @@ -2,35 +2,22 @@ """Simple script to create a tarball with proper bzr version info. """ -import os,sys,shutil +import os +import sys +import shutil -basever = '0.9.0' +from toollib import * -def oscmd(c): - print ">",c - s = os.system(c) - if s: - print "Error",s - sys.exit(s) +c('python update_revnum.py') -def verinfo(): - - out = os.popen('bzr version-info') - pairs = (l.split(':',1) for l in out) - d = dict(((k,v.strip()) for (k,v) in pairs)) - return d - -basename = 'ipython' - -#tarname = '%s.r%s.tgz' % (basename, ver) -oscmd('python update_revnum.py') +execfile('../IPython/core/release.py') # defines version_base -ver = verinfo() +ver = version_info() if ver['branch-nick'] == 'ipython': - tarname = 'ipython-%s.bzr.r%s.tgz' % (basever, ver['revno']) + tarname = 'ipython-%s.bzr.r%s.tgz' % (version_base, ver['revno']) else: - tarname = 'ipython-%s.bzr.r%s.%s.tgz' % (basever, ver['revno'], + tarname = 'ipython-%s.bzr.r%s.%s.tgz' % (version_base, ver['revno'], ver['branch-nick']) -oscmd('bzr export ' + tarname) +c('bzr export ' + tarname) diff --git a/tools/mkrel.py b/tools/mkrel.py deleted file mode 100755 index 01c8a8a..0000000 --- a/tools/mkrel.py +++ /dev/null @@ -1,48 +0,0 @@ -#!/usr/bin/env python -"""IPython release script - -Create ipykit and exe installer - -requires py2exe -""" - -import os -import distutils.dir_util -import sys - -execfile('../IPython/Release.py') - -def c(cmd): - print ">",cmd - os.system(cmd) - -ipykit_name = "ipykit-%s" % version - -os.chdir('..') -if os.path.isdir('dist'): - distutils.dir_util.remove_tree('dist') -if os.path.isdir(ipykit_name): - distutils.dir_util.remove_tree(ipykit_name) - -if sys.platform == 'win32': - c("python exesetup.py py2exe") - - os.rename('dist',ipykit_name) - - c("zip -r %s.zip %s" % (ipykit_name, ipykit_name)) - -# Build source and binary distros -c('./setup.py sdist --formats=gztar') - -c("python2.4 ./setup.py bdist_rpm --binary-only --release=py24 --python=/usr/bin/python2.4") -c("python2.5 ./setup.py bdist_rpm --binary-only --release=py25 --python=/usr/bin/python2.5") - -# Build eggs -c('python2.4 ./eggsetup.py bdist_egg') -c('python2.5 ./eggsetup.py bdist_egg') - -c("python setup.py bdist_wininst --install-script=ipython_win_post_install.py") - -os.chdir('tools') -c('python make_tarball.py') - diff --git a/tools/release b/tools/release index 170515a..3eff29e 100755 --- a/tools/release +++ b/tools/release @@ -1,58 +1,43 @@ -#!/bin/sh -# IPython release script +#!/usr/bin/env python +"""IPython release script. -PYVER=`python -V 2>&1 | awk '{print $2}' | awk -F '.' '{print $1$2}' ` -version=`ipython -Version` -ipdir=~/ipython/ipython -ipbackupdir=~/ipython/backup +This should only be run at real release time. +""" -echo -echo "Releasing IPython version $version" -echo "==================================" +from toollib import * -# Perform local backup -cd $ipdir/tools -./make_tarball.py -mv ipython-*.tgz $ipbackupdir - -# Clean up build/dist directories -rm -rf $ipdir/build/* -rm -rf $ipdir/dist/* +# Get main ipython dir, this will raise if it doesn't pass some checks +ipdir = get_ipdir() +cd(ipdir) -# Build source and binary distros -cd $ipdir -./setup.py sdist --formats=gztar +# Load release info +execfile(pjoin('IPython','core','release.py')) -# Build version-specific RPMs, where we must use the --python option to ensure -# that the resulting RPM is really built with the requested python version (so -# things go to lib/python2.X/...) -#python2.4 ./setup.py bdist_rpm --binary-only --release=py24 --python=/usr/bin/python2.4 -#python2.5 ./setup.py bdist_rpm --binary-only --release=py25 --python=/usr/bin/python2.5 +# Where I keep static backups of each release +ipbackupdir = os.path.expanduser('~/ipython/backup') -# Build eggs -python2.4 ./setup_bdist_egg.py -python2.5 ./setup_bdist_egg.py +print +print "Releasing IPython version $version" +print "==================================" -# Call the windows build separately, so that the extra Windows scripts don't -# get pulled into Unix builds (setup.py has code which checks for -# bdist_wininst) -./setup.py bdist_wininst --install-script=ipython_win_post_install.py +# Perform local backup +c('./make_tarball.py') +c('mv ipython-*.tgz %s' % ipbackupdir) -# Change name so retarded Vista runs the installer correctly -rename 's/win32/win32-setup/' $ipdir/dist/*.exe +# Build release files +c('./mkrel.py %s' % ipdir) # Register with the Python Package Index (PyPI) -echo "Registering with PyPI..." -cd $ipdir -./setup.py register +print "Registering with PyPI..." +c('./setup.py register') # Upload all files -cd $ipdir/dist -echo "Uploading distribution files..." -scp * ipython@ipython.scipy.org:www/dist/ +cd('dist') +print "Uploading distribution files..." +c('scp * ipython@ipython.scipy.org:www/dist/') -echo "Uploading backup files..." -cd $ipbackupdir -scp `ls -1tr *tgz | tail -1` ipython@ipython.scipy.org:www/backup/ +print "Uploading backup files..." +cd(ipbackupdir) +c('scp `ls -1tr *tgz | tail -1` ipython@ipython.scipy.org:www/backup/') -echo "Done!" +print "Done!" diff --git a/tools/run_ipy_in_profiler.py b/tools/run_ipy_in_profiler.py index bcd0ef7..a58e0c5 100755 --- a/tools/run_ipy_in_profiler.py +++ b/tools/run_ipy_in_profiler.py @@ -1,3 +1,9 @@ +"""XXX - What exactly is the use of this script? + +I (fperez) tried it quickly and it doesn't work in its current form. Either it +needs to be fixed and documented or removed. +""" + import cProfile as profile import sys #import profile diff --git a/tools/testrel b/tools/testrel deleted file mode 100755 index b6901e2..0000000 --- a/tools/testrel +++ /dev/null @@ -1,31 +0,0 @@ -#!/bin/sh - -# release test - -ipdir=$PWD/.. - -cd $ipdir - -# Clean up build/dist directories -rm -rf $ipdir/build/* -rm -rf $ipdir/dist/* - -# build source distros -cd $ipdir -./setup.py sdist --formats=gztar - -# Build rpms -python2.4 ./setup.py bdist_rpm --binary-only --release=py24 --python=/usr/bin/python2.4 -python2.5 ./setup.py bdist_rpm --binary-only --release=py25 --python=/usr/bin/python2.5 - -# Build eggs -python2.4 ./setup_bdist_egg.py -python2.5 ./setup_bdist_egg.py - -# Call the windows build separately, so that the extra Windows scripts don't -# get pulled into Unix builds (setup.py has code which checks for -# bdist_wininst) -./setup.py bdist_wininst --install-script=ipython_win_post_install.py - -# Change name so retarded Vista runs the installer correctly -rename 's/win32/win32-setup/' $ipdir/dist/*.exe diff --git a/tools/testupload b/tools/testupload index c2a19c0..a3a04db 100755 --- a/tools/testupload +++ b/tools/testupload @@ -1,5 +1,5 @@ #!/bin/sh - +# Simple upload script to push up into the testing directory a local build ipdir=$PWD/.. cd $ipdir/dist diff --git a/tools/toollib.py b/tools/toollib.py new file mode 100644 index 0000000..206bd7a --- /dev/null +++ b/tools/toollib.py @@ -0,0 +1,55 @@ +"""Various utilities common to IPython release and maintenance tools. +""" +# Library imports +import os +import sys + +from distutils.dir_util import remove_tree + +# Useful shorthands +pjoin = os.path.join +cd = os.chdir + +# Utility functions +def c(cmd): + """Run system command, raise SystemExit if it returns an error.""" + print "$",cmd + stat = os.system(cmd) + #stat = 0 # Uncomment this and comment previous to run in debug mode + if stat: + raise SystemExit("Command %s failed with code: %s" % (cmd, stat)) + + +def get_ipdir(): + """Get IPython directory from command line, or assume it's the one above.""" + + # Initialize arguments and check location + try: + ipdir = sys.argv[1] + except IndexError: + ipdir = '..' + + ipdir = os.path.abspath(ipdir) + + cd(ipdir) + if not os.path.isdir('IPython') and os.path.isfile('setup.py'): + raise SystemExit('Invalid ipython directory: %s' % ipdir) + return ipdir + + +def compile_tree(): + """Compile all Python files below current directory.""" + vstr = '.'.join(map(str,sys.version_info[:2])) + stat = os.system('python %s/lib/python%s/compileall.py .' % + (sys.prefix,vstr)) + if stat: + msg = '*** ERROR: Some Python files in tree do NOT compile! ***\n' + msg += 'See messages above for the actual file that produced it.\n' + raise SystemExit(msg) + + +def version_info(): + """Return bzr version info as a dict.""" + out = os.popen('bzr version-info') + pairs = (l.split(':',1) for l in out) + return dict(((k,v.strip()) for (k,v) in pairs)) diff --git a/tools/update_revnum.py b/tools/update_revnum.py index f97b933..7e03e74 100755 --- a/tools/update_revnum.py +++ b/tools/update_revnum.py @@ -1,23 +1,32 @@ #!/usr/bin/env python -""" Change the revision number in Release.py """ +"""Change the revision number in release.py + +This edits in-place release.py to update the revision number from bzr info. + +Usage: + +./update_revnum.py""" import os -import re,pprint +import pprint +import re -def verinfo(): - - out = os.popen('bzr version-info') - pairs = (l.split(':',1) for l in out) - d = dict(((k,v.strip()) for (k,v) in pairs)) - return d +from toollib import * -ver = verinfo() +if __name__ == '__main__': + ver = version_info() -pprint.pprint(ver) + pprint.pprint(ver) -rfile = open('../IPython/Release.py','rb').read() -newcont = re.sub(r'revision\s*=.*', "revision = '%s'" % ver['revno'], rfile) + rfile = open('../IPython/core/release.py','rb').read() + newcont = re.sub(r'revision\s*=.*', + "revision = '%s'" % ver['revno'], + rfile) -newcont = re.sub(r'^branch\s*=[^=].*', "branch = '%s'" % ver['branch-nick'], newcont ) + newcont = re.sub(r'^branch\s*=[^=].*', + "branch = '%s'" % ver['branch-nick'], + newcont) -open('../IPython/Release.py','wb').write(newcont) + f = open('../IPython/core/release.py','wb') + f.write(newcont) + f.close()

    mK|J6gM5q7;AIJp9^%huxS7BgC31zS^FG+*UjP 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 BmgoQg literal 0 Hc$@] + +The resulting plot of the single digit counts shows that each digit occurs +approximately 1,000 times, but that with only 10,000 digits the +statistical fluctuations are still rather large: + +.. image:: single_digits.* + +It is clear that to reduce the relative fluctuations in the counts, we need +to look at many more digits of pi. That brings us to the parallel calculation. + +Parallel calculation +-------------------- + +Calculating many digits of pi is a challenging computational problem in itself. +Because we want to focus on the distribution of digits in this example, we +will use pre-computed digit of pi from the website of Professor Yasumasa +Kanada at the University of Tokoyo (http://www.super-computing.org). These +digits come in a set of text files (ftp://pi.super-computing.org/.2/pi200m/) +that each have 10 million digits of pi. + +For the parallel calculation, we have copied these files to the local hard +drives of the compute nodes. A total of 15 of these files will be used, for a +total of 150 million digits of pi. To make things a little more interesting we +will calculate the frequencies of all 2 digits sequences (00-99) and then plot +the result using a 2D matrix in Matplotlib. + +The overall idea of the calculation is simple: each IPython engine will +compute the two digit counts for the digits in a single file. Then in a final +step the counts from each engine will be added up. To perform this +calculation, we will need two top-level functions from :file:`pidigits.py`: + +.. literalinclude:: ../../examples/kernel/pidigits.py + :language: python + :lines: 34-49 + +We will also use the :func:`plot_two_digit_freqs` function to plot the +results. The code to run this calculation in parallel is contained in +:file:`docs/examples/kernel/parallelpi.py`. This code can be run in parallel +using IPython by following these steps: + +1. Copy the text files with the digits of pi + (ftp://pi.super-computing.org/.2/pi200m/) to the working directory of the + engines on the compute nodes. +2. Use :command:`ipcluster` to start 15 engines. We used an 8 core (2 quad + core CPUs) cluster with hyperthreading enabled which makes the 8 cores + looks like 16 (1 controller + 15 engines) in the OS. However, the maximum + speedup we can observe is still only 8x. +3. With the file :file:`parallelpi.py` in your current working directory, open + up IPython in pylab mode and type ``run parallelpi.py``. + +When run on our 8 core cluster, we observe a speedup of 7.7x. This is slightly +less than linear scaling (8x) because the controller is also running on one of +the cores. + +To emphasize the interactive nature of IPython, we now show how the +calculation can also be run by simply typing the commands from +:file:`parallelpi.py` interactively into IPython: + +.. sourcecode:: ipython + + In [1]: from IPython.kernel import client + 2009-11-19 11:32:38-0800 [-] Log opened. + + # The MultiEngineClient allows us to use the engines interactively. + # We simply pass MultiEngineClient the name of the cluster profile we + # are using. + In [2]: mec = client.MultiEngineClient(profile='mycluster') + 2009-11-19 11:32:44-0800 [-] Connecting [0] + 2009-11-19 11:32:44-0800 [Negotiation,client] Connected: ./ipcontroller-mec.furl + + In [3]: mec.get_ids() + Out[3]: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14] + + In [4]: run pidigits.py + + In [5]: filestring = 'pi200m-ascii-%(i)02dof20.txt' + + # Create the list of files to process. + In [6]: files = [filestring % {'i':i} for i in range(1,16)] + + In [7]: files + Out[7]: + ['pi200m-ascii-01of20.txt', + 'pi200m-ascii-02of20.txt', + 'pi200m-ascii-03of20.txt', + 'pi200m-ascii-04of20.txt', + 'pi200m-ascii-05of20.txt', + 'pi200m-ascii-06of20.txt', + 'pi200m-ascii-07of20.txt', + 'pi200m-ascii-08of20.txt', + 'pi200m-ascii-09of20.txt', + 'pi200m-ascii-10of20.txt', + 'pi200m-ascii-11of20.txt', + 'pi200m-ascii-12of20.txt', + 'pi200m-ascii-13of20.txt', + 'pi200m-ascii-14of20.txt', + 'pi200m-ascii-15of20.txt'] + + # This is the parallel calculation using the MultiEngineClient.map method + # which applies compute_two_digit_freqs to each file in files in parallel. + In [8]: freqs_all = mec.map(compute_two_digit_freqs, files) + + # Add up the frequencies from each engine. + In [8]: freqs = reduce_freqs(freqs_all) + + In [9]: plot_two_digit_freqs(freqs) + Out[9]: + + In [10]: plt.title('2 digit counts of 150m digits of pi') + Out[10]: + +The resulting plot generated by Matplotlib is shown below. The colors indicate +which two digit sequences are more (red) or less (blue) likely to occur in the +first 150 million digits of pi. We clearly see that the sequence "41" is +most likely and that "06" and "07" are least likely. Further analysis would +show that the relative size of the statistical fluctuations have decreased +compared to the 10,000 digit calculation. + +.. image:: two_digit_counts.* + + +Parallel options pricing +======================== + +An option is a financial contract that gives the buyer of the contract the +right to buy (a "call") or sell (a "put") a secondary asset (a stock for +example) at a particular date in the future (the expiration date) for a +pre-agreed upon price (the strike price). For this right, the buyer pays the +seller a premium (the option price). There are a wide variety of flavors of +options (American, European, Asian, etc.) that are useful for different +purposes: hedging against risk, speculation, etc. + +Much of modern finance is driven by the need to price these contracts +accurately based on what is known about the properties (such as volatility) of +the underlying asset. One method of pricing options is to use a Monte Carlo +simulation of the underlying asset price. In this example we use this approach +to price both European and Asian (path dependent) options for various strike +prices and volatilities. + +The code for this example can be found in the :file:`docs/examples/kernel` +directory of the IPython source. The function :func:`price_options` in +:file:`mcpricer.py` implements the basic Monte Carlo pricing algorithm using +the NumPy package and is shown here: + +.. literalinclude:: ../../examples/kernel/mcpricer.py + :language: python + +To run this code in parallel, we will use IPython's :class:`TaskClient` class, +which distributes work to the engines using dynamic load balancing. This +client can be used along side the :class:`MultiEngineClient` class shown in +the previous example. The parallel calculation using :class:`TaskClient` can +be found in the file :file:`mcpricer.py`. The code in this file creates a +:class:`TaskClient` instance and then submits a set of tasks using +:meth:`TaskClient.run` that calculate the option prices for different +volatilities and strike prices. The results are then plotted as a 2D contour +plot using Matplotlib. + +.. literalinclude:: ../../examples/kernel/mcdriver.py + :language: python + +To use this code, start an IPython cluster using :command:`ipcluster`, open +IPython in the pylab mode with the file :file:`mcdriver.py` in your current +working directory and then type: + +.. sourcecode:: ipython + + In [7]: run mcdriver.py + Submitted tasks: [0, 1, 2, ...] + +Once all the tasks have finished, the results can be plotted using the +:func:`plot_options` function. Here we make contour plots of the Asian +call and Asian put options as function of the volatility and strike price: + +.. sourcecode:: ipython + + In [8]: plot_options(sigma_vals, K_vals, prices['acall']) + + In [9]: plt.figure() + Out[9]: + + In [10]: plot_options(sigma_vals, K_vals, prices['aput']) + +These results are shown in the two figures below. On a 8 core cluster the +entire calculation (10 strike prices, 10 volatilities, 100,000 paths for each) +took 30 seconds in parallel, giving a speedup of 7.7x, which is comparable +to the speedup observed in our previous example. + +.. image:: asian_call.* + +.. image:: asian_put.* + +Conclusion +========== + +To conclude these examples, we summarize the key features of IPython's +parallel architecture that have been demonstrated: + +* Serial code can be parallelized often with only a few extra lines of code. + We have used the :class:`MultiEngineClient` and :class:`TaskClient` classes + for this purpose. +* The resulting parallel code can be run without ever leaving the IPython's + interactive shell. +* Any data computed in parallel can be explored interactively through + visualization or further numerical calculations. +* We have run these examples on a cluster running Windows HPC Server 2008. + IPython's built in support for the Windows HPC job scheduler makes it + easy to get started with IPython's parallel capabilities. diff --git a/docs/source/parallel/parallel_intro.txt b/docs/source/parallel/parallel_intro.txt index 92db52b..0b2b984 100644 --- a/docs/source/parallel/parallel_intro.txt +++ b/docs/source/parallel/parallel_intro.txt @@ -90,13 +90,15 @@ asynchronous interface to a set of engines. Because the controller listens on a network port for engines to connect to it, it must be started *before* any engines are started. -The controller also provides a single point of contact for users who wish -to utilize the engines connected to the controller. There are different -ways of working with a controller. In IPython these ways correspond to different interfaces that the controller is adapted to. Currently we have two default interfaces to the controller: +The controller also provides a single point of contact for users who wish to +utilize the engines connected to the controller. There are different ways of +working with a controller. In IPython these ways correspond to different +interfaces that the controller is adapted to. Currently we have two default +interfaces to the controller: * The MultiEngine interface, which provides the simplest possible way of working with engines interactively. -* The Task interface, which provides presents the engines as a load balanced +* The Task interface, which presents the engines as a load balanced task farming system. Advanced users can easily add new custom interfaces to enable other @@ -121,11 +123,30 @@ interface. Here are the two default clients: Security -------- -By default (as long as `pyOpenSSL` is installed) all network connections between the controller and engines and the controller and clients are secure. What does this mean? First of all, all of the connections will be encrypted using SSL. Second, the connections are authenticated. We handle authentication in a capability based security model [Capability]_. In this model, a "capability (known in some systems as a key) is a communicable, unforgeable token of authority". Put simply, a capability is like a key to your house. If you have the key to your house, you can get in. If not, you can't. - -In our architecture, the controller is the only process that listens on network ports, and is thus responsible to creating these keys. In IPython, these keys are known as Foolscap URLs, or FURLs, because of the underlying network protocol we are using. As a user, you don't need to know anything about the details of these FURLs, other than that when the controller starts, it saves a set of FURLs to files named :file:`something.furl`. The default location of these files is the :file:`~./ipython/security` directory. - -To connect and authenticate to the controller an engine or client simply needs to present an appropriate FURL (that was originally created by the controller) to the controller. Thus, the FURL files need to be copied to a location where the clients and engines can find them. Typically, this is the :file:`~./ipython/security` directory on the host where the client/engine is running (which could be a different host than the controller). Once the FURL files are copied over, everything should work fine. +By default (as long as `pyOpenSSL` is installed) all network connections +between the controller and engines and the controller and clients are secure. +What does this mean? First of all, all of the connections will be encrypted +using SSL. Second, the connections are authenticated. We handle authentication +in a capability based security model [Capability]_. In this model, a +"capability (known in some systems as a key) is a communicable, unforgeable +token of authority". Put simply, a capability is like a key to your house. If +you have the key to your house, you can get in. If not, you can't. + +In our architecture, the controller is the only process that listens on +network ports, and is thus responsible to creating these keys. In IPython, +these keys are known as Foolscap URLs, or FURLs, because of the underlying +network protocol we are using. As a user, you don't need to know anything +about the details of these FURLs, other than that when the controller starts, +it saves a set of FURLs to files named :file:`something.furl`. The default +location of these files is the :file:`~./ipython/security` directory. + +To connect and authenticate to the controller an engine or client simply needs +to present an appropriate FURL (that was originally created by the controller) +to the controller. Thus, the FURL files need to be copied to a location where +the clients and engines can find them. Typically, this is the +:file:`~./ipython/security` directory on the host where the client/engine is +running (which could be a different host than the controller). Once the FURL +files are copied over, everything should work fine. Currently, there are three FURL files that the controller creates: @@ -149,12 +170,16 @@ can be found :ref:`here `. Getting Started =============== -To use IPython for parallel computing, you need to start one instance of -the controller and one or more instances of the engine. Initially, it is best to simply start a controller and engines on a single host using the :command:`ipcluster` command. To start a controller and 4 engines on you localhost, just do:: +To use IPython for parallel computing, you need to start one instance of the +controller and one or more instances of the engine. Initially, it is best to +simply start a controller and engines on a single host using the +:command:`ipcluster` command. To start a controller and 4 engines on your +localhost, just do:: $ ipcluster local -n 4 -More details about starting the IPython controller and engines can be found :ref:`here ` +More details about starting the IPython controller and engines can be found +:ref:`here ` Once you have started the IPython controller and one or more engines, you are ready to use the engines to do something useful. To make sure @@ -184,7 +209,12 @@ everything is working correctly, try the following commands: [3] In [1]: print "Hello World" [3] Out[1]: Hello World -Remember, a client also needs to present a FURL file to the controller. How does this happen? When a multiengine client is created with no arguments, the client tries to find the corresponding FURL file in the local :file:`~./ipython/security` directory. If it finds it, you are set. If you have put the FURL file in a different location or it has a different name, create the client like this:: +Remember, a client also needs to present a FURL file to the controller. How +does this happen? When a multiengine client is created with no arguments, the +client tries to find the corresponding FURL file in the local +:file:`~./ipython/security` directory. If it finds it, you are set. If you +have put the FURL file in a different location or it has a different name, +create the client like this:: mec = client.MultiEngineClient('/path/to/my/ipcontroller-mec.furl') @@ -192,7 +222,9 @@ Same thing hold true of creating a task client:: tc = client.TaskClient('/path/to/my/ipcontroller-tc.furl') -You are now ready to learn more about the :ref:`MultiEngine ` and :ref:`Task ` interfaces to the controller. +You are now ready to learn more about the :ref:`MultiEngine +` and :ref:`Task ` interfaces to the +controller. .. note:: diff --git a/docs/source/parallel/parallel_mpi.txt b/docs/source/parallel/parallel_mpi.txt index 4df70f3..cdb54df 100644 --- a/docs/source/parallel/parallel_mpi.txt +++ b/docs/source/parallel/parallel_mpi.txt @@ -4,9 +4,16 @@ Using MPI with IPython ======================= -Often, a parallel algorithm will require moving data between the engines. One way of accomplishing this is by doing a pull and then a push using the multiengine client. However, this will be slow as all the data has to go through the controller to the client and then back through the controller, to its final destination. +Often, a parallel algorithm will require moving data between the engines. One +way of accomplishing this is by doing a pull and then a push using the +multiengine client. However, this will be slow as all the data has to go +through the controller to the client and then back through the controller, to +its final destination. -A much better way of moving data between engines is to use a message passing library, such as the Message Passing Interface (MPI) [MPI]_. IPython's parallel computing architecture has been designed from the ground up to integrate with MPI. This document describes how to use MPI with IPython. +A much better way of moving data between engines is to use a message passing +library, such as the Message Passing Interface (MPI) [MPI]_. IPython's +parallel computing architecture has been designed from the ground up to +integrate with MPI. This document describes how to use MPI with IPython. Additional installation requirements ==================================== @@ -35,12 +42,15 @@ To use code that calls MPI, there are typically two things that MPI requires. :command:`mpiexec` or a batch system (like PBS) that has MPI support. 2. Once the process starts, it must call :func:`MPI_Init`. -There are a couple of ways that you can start the IPython engines and get these things to happen. +There are a couple of ways that you can start the IPython engines and get +these things to happen. Automatic starting using :command:`mpiexec` and :command:`ipcluster` -------------------------------------------------------------------- +-------------------------------------------------------------------- -The easiest approach is to use the `mpiexec` mode of :command:`ipcluster`, which will first start a controller and then a set of engines using :command:`mpiexec`:: +The easiest approach is to use the `mpiexec` mode of :command:`ipcluster`, +which will first start a controller and then a set of engines using +:command:`mpiexec`:: $ ipcluster mpiexec -n 4 @@ -48,9 +58,10 @@ This approach is best as interrupting :command:`ipcluster` will automatically stop and clean up the controller and engines. Manual starting using :command:`mpiexec` ---------------------------------------- +---------------------------------------- -If you want to start the IPython engines using the :command:`mpiexec`, just do:: +If you want to start the IPython engines using the :command:`mpiexec`, just +do:: $ mpiexec -n 4 ipengine --mpi=mpi4py @@ -64,29 +75,41 @@ starting the engines with:: Automatic starting using PBS and :command:`ipcluster` ----------------------------------------------------- -The :command:`ipcluster` command also has built-in integration with PBS. For more information on this approach, see our documentation on :ref:`ipcluster `. +The :command:`ipcluster` command also has built-in integration with PBS. For +more information on this approach, see our documentation on :ref:`ipcluster +`. Actually using MPI ================== -Once the engines are running with MPI enabled, you are ready to go. You can now call any code that uses MPI in the IPython engines. And, all of this can be done interactively. Here we show a simple example that uses mpi4py [mpi4py]_. +Once the engines are running with MPI enabled, you are ready to go. You can +now call any code that uses MPI in the IPython engines. And, all of this can +be done interactively. Here we show a simple example that uses mpi4py +[mpi4py]_ version 1.1.0 or later. -First, lets define a simply function that uses MPI to calculate the sum of a distributed array. Save the following text in a file called :file:`psum.py`: +First, lets define a simply function that uses MPI to calculate the sum of a +distributed array. Save the following text in a file called :file:`psum.py`: .. sourcecode:: python from mpi4py import MPI import numpy as np - + def psum(a): s = np.sum(a) - return MPI.COMM_WORLD.Allreduce(s,MPI.SUM) + rcvBuf = np.array(0.0,'d') + MPI.COMM_WORLD.Allreduce([s, MPI.DOUBLE], + [rcvBuf, MPI.DOUBLE], + op=MPI.SUM) + return rcvBuf Now, start an IPython cluster in the same directory as :file:`psum.py`:: $ ipcluster mpiexec -n 4 -Finally, connect to the cluster and use this function interactively. In this case, we create a random array on each engine and sum up all the random arrays using our :func:`psum` function: +Finally, connect to the cluster and use this function interactively. In this +case, we create a random array on each engine and sum up all the random arrays +using our :func:`psum` function: .. sourcecode:: ipython @@ -154,4 +177,6 @@ compiled C, C++ and Fortran libraries that have been exposed to Python. .. [MPI] Message Passing Interface. http://www-unix.mcs.anl.gov/mpi/ .. [mpi4py] MPI for Python. mpi4py: http://mpi4py.scipy.org/ .. [OpenMPI] Open MPI. http://www.open-mpi.org/ -.. [PyTrilinos] PyTrilinos. http://trilinos.sandia.gov/packages/pytrilinos/ \ No newline at end of file +.. [PyTrilinos] PyTrilinos. http://trilinos.sandia.gov/packages/pytrilinos/ + + diff --git a/docs/source/parallel/parallel_multiengine.txt b/docs/source/parallel/parallel_multiengine.txt index 8c91e38..b39123f 100644 --- a/docs/source/parallel/parallel_multiengine.txt +++ b/docs/source/parallel/parallel_multiengine.txt @@ -58,7 +58,10 @@ Here we see that there are four engines ready to do work for us. Quick and easy parallelism ========================== -In many cases, you simply want to apply a Python function to a sequence of objects, but *in parallel*. The multiengine interface provides two simple ways of accomplishing this: a parallel version of :func:`map` and ``@parallel`` function decorator. +In many cases, you simply want to apply a Python function to a sequence of +objects, but *in parallel*. The multiengine interface provides two simple ways +of accomplishing this: a parallel version of :func:`map` and ``@parallel`` +function decorator. Parallel map ------------ @@ -90,7 +93,9 @@ parallel version of :meth:`map` that works just like its serial counterpart: Parallel function decorator --------------------------- -Parallel functions are just like normal function, but they can be called on sequences and *in parallel*. The multiengine interface provides a decorator that turns any Python function into a parallel function: +Parallel functions are just like normal function, but they can be called on +sequences and *in parallel*. The multiengine interface provides a decorator +that turns any Python function into a parallel function: .. sourcecode:: ipython @@ -667,7 +672,9 @@ more other types of exceptions. Here is how it works: [2:execute]: ZeroDivisionError: integer division or modulo by zero [3:execute]: ZeroDivisionError: integer division or modulo by zero -Notice how the error message printed when :exc:`CompositeError` is raised has information about the individual exceptions that were raised on each engine. If you want, you can even raise one of these original exceptions: +Notice how the error message printed when :exc:`CompositeError` is raised has +information about the individual exceptions that were raised on each engine. +If you want, you can even raise one of these original exceptions: .. sourcecode:: ipython diff --git a/docs/source/parallel/parallel_pi.pdf b/docs/source/parallel/parallel_pi.pdf new file mode 100644 index 0000000000000000000000000000000000000000..5ad1fe2068c5f929ae4188cdb0caab9a22b7e37b GIT binary patch literal 128061 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 WVRimXAy~Qu`FZgf8D&&t@&6AyQ&K$u literal 0 Hc$@{41>#D6p-8T*?=~Ix?+oOX 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 literal 0 Hc$@`. Using :command:`ipcluster` in PBS mode -------------------------------------- -The PBS mode uses the Portable Batch System [PBS]_ to start the engines. To use this mode, you first need to create a PBS script template that will be used to start the engines. Here is a sample PBS script template: +The PBS mode uses the Portable Batch System [PBS]_ to start the engines. To +use this mode, you first need to create a PBS script template that will be +used to start the engines. Here is a sample PBS script template: .. sourcecode:: bash @@ -161,7 +181,8 @@ There are a few important points about this template: 5. Depending on the configuration of you system, you may have to set environment variables in the script template. -Once you have created such a script, save it with a name like :file:`pbs.template`. Now you are ready to start your job:: +Once you have created such a script, save it with a name like +:file:`pbs.template`. Now you are ready to start your job:: $ ipcluster pbs -n 128 --pbs-script=pbs.template @@ -175,9 +196,11 @@ Using :command:`ipcluster` in SSH mode The SSH mode uses :command:`ssh` to execute :command:`ipengine` on remote nodes and the :command:`ipcontroller` on localhost. -When using using this mode it highly recommended that you have set up SSH keys and are using ssh-agent [SSH]_ for password-less logins. +When using using this mode it highly recommended that you have set up SSH keys +and are using ssh-agent [SSH]_ for password-less logins. -To use this mode you need a python file describing the cluster, here is an example of such a "clusterfile": +To use this mode you need a python file describing the cluster, here is an +example of such a "clusterfile": .. sourcecode:: python @@ -187,7 +210,8 @@ To use this mode you need a python file describing the cluster, here is an examp 'host3.example.com' : 1, 'host4.example.com' : 8 } -Since this is a regular python file usual python syntax applies. Things to note: +Since this is a regular python file usual python syntax applies. Things to +note: * The `engines` dict, where the keys is the host we want to run engines on and the value is the number of engines to run on that host. @@ -204,12 +228,16 @@ start your cluster like so: $ ipcluster ssh --clusterfile /path/to/my/clusterfile.py -Two helper shell scripts are used to start and stop :command:`ipengine` on remote hosts: +Two helper shell scripts are used to start and stop :command:`ipengine` on +remote hosts: * sshx.sh * engine_killer.sh -Defaults for both of these are contained in the source code for :command:`ipcluster`. The default scripts are written to a local file in a tmep directory and then copied to a temp directory on the remote host and executed from there. On most Unix, Linux and OS X systems this is /tmp. +Defaults for both of these are contained in the source code for +:command:`ipcluster`. The default scripts are written to a local file in a +tmep directory and then copied to a temp directory on the remote host and +executed from there. On most Unix, Linux and OS X systems this is /tmp. The default sshx.sh is the following: @@ -241,7 +269,9 @@ Current limitations of the SSH mode of :command:`ipcluster` are: Using the :command:`ipcontroller` and :command:`ipengine` commands ================================================================== -It is also possible to use the :command:`ipcontroller` and :command:`ipengine` commands to start your controller and engines. This approach gives you full control over all aspects of the startup process. +It is also possible to use the :command:`ipcontroller` and :command:`ipengine` +commands to start your controller and engines. This approach gives you full +control over all aspects of the startup process. Starting the controller and engine on your local machine -------------------------------------------------------- @@ -253,11 +283,14 @@ First start the controller:: $ ipcontroller -Next, start however many instances of the engine you want using (repeatedly) the command:: +Next, start however many instances of the engine you want using (repeatedly) +the command:: $ ipengine -The engines should start and automatically connect to the controller using the FURL files in :file:`~./ipython/security`. You are now ready to use the controller and engines from IPython. +The engines should start and automatically connect to the controller using the +FURL files in :file:`~./ipython/security`. You are now ready to use the +controller and engines from IPython. .. warning:: @@ -279,10 +312,13 @@ When the controller and engines are running on different hosts, things are slightly more complicated, but the underlying ideas are the same: 1. Start the controller on a host using :command:`ipcontroller`. -2. Copy :file:`ipcontroller-engine.furl` from :file:`~./ipython/security` on the controller's host to the host where the engines will run. +2. Copy :file:`ipcontroller-engine.furl` from :file:`~./ipython/security` on + the controller's host to the host where the engines will run. 3. Use :command:`ipengine` on the engine's hosts to start the engines. -The only thing you have to be careful of is to tell :command:`ipengine` where the :file:`ipcontroller-engine.furl` file is located. There are two ways you can do this: +The only thing you have to be careful of is to tell :command:`ipengine` where +the :file:`ipcontroller-engine.furl` file is located. There are two ways you +can do this: * Put :file:`ipcontroller-engine.furl` in the :file:`~./ipython/security` directory on the engine's host, where it will be found automatically. @@ -349,3 +385,5 @@ the log files to us will often help us to debug any problems. .. [PBS] Portable Batch System. http://www.openpbs.org/ .. [SSH] SSH-Agent http://en.wikipedia.org/wiki/Ssh-agent + + diff --git a/docs/source/parallel/parallel_security.txt b/docs/source/parallel/parallel_security.txt index abd876c..ac95119 100644 --- a/docs/source/parallel/parallel_security.txt +++ b/docs/source/parallel/parallel_security.txt @@ -52,7 +52,8 @@ When running the IPython kernel to perform a parallel computation, a user utilizes the IPython client to send Python commands and data through the IPython controller to the IPython engines, where those commands are executed and the data processed. The design of IPython ensures that the client is the -only access point for the capabilities of the engines. That is, the only way of addressing the engines is through a client. +only access point for the capabilities of the engines. That is, the only way +of addressing the engines is through a client. A user can utilize the client to instruct the IPython engines to execute arbitrary Python commands. These Python commands can include calls to the @@ -116,8 +117,8 @@ controller creates a number of FURLs for different purposes: to execute. Upon starting, the controller creates these different FURLS and writes them -files in the user-read-only directory :file:`$HOME/.ipython/security`. Thus, only the -user who starts the controller has access to the FURLs. +files in the user-read-only directory :file:`$HOME/.ipython/security`. Thus, +only the user who starts the controller has access to the FURLs. For an IPython client or engine to authenticate with a controller, it must present the appropriate FURL to the controller upon connecting. If the @@ -361,3 +362,5 @@ authentication steps that the CLIENT and SERVER perform after an encrypted channel is established. .. [RFC5246] + + diff --git a/docs/source/parallel/parallel_task.txt b/docs/source/parallel/parallel_task.txt index 14d4565..e88cbd9 100644 --- a/docs/source/parallel/parallel_task.txt +++ b/docs/source/parallel/parallel_task.txt @@ -4,9 +4,17 @@ The IPython task interface ========================== -The task interface to the controller presents the engines as a fault tolerant, dynamic load-balanced system or workers. Unlike the multiengine interface, in the task interface, the user have no direct access to individual engines. In some ways, this interface is simpler, but in other ways it is more powerful. - -Best of all the user can use both of these interfaces running at the same time to take advantage or both of their strengths. When the user can break up the user's work into segments that do not depend on previous execution, the task interface is ideal. But it also has more power and flexibility, allowing the user to guide the distribution of jobs, without having to assign tasks to engines explicitly. +The task interface to the controller presents the engines as a fault tolerant, +dynamic load-balanced system or workers. Unlike the multiengine interface, in +the task interface, the user have no direct access to individual engines. In +some ways, this interface is simpler, but in other ways it is more powerful. + +Best of all the user can use both of these interfaces running at the same time +to take advantage or both of their strengths. When the user can break up the +user's work into segments that do not depend on previous execution, the task +interface is ideal. But it also has more power and flexibility, allowing the +user to guide the distribution of jobs, without having to assign tasks to +engines explicitly. Starting the IPython controller and engines =========================================== @@ -44,12 +52,19 @@ constructor: Quick and easy parallelism ========================== -In many cases, you simply want to apply a Python function to a sequence of objects, but *in parallel*. Like the multiengine interface, the task interface provides two simple ways of accomplishing this: a parallel version of :func:`map` and ``@parallel`` function decorator. However, the verions in the task interface have one important difference: they are dynamically load balanced. Thus, if the execution time per item varies significantly, you should use the versions in the task interface. +In many cases, you simply want to apply a Python function to a sequence of +objects, but *in parallel*. Like the multiengine interface, the task interface +provides two simple ways of accomplishing this: a parallel version of +:func:`map` and ``@parallel`` function decorator. However, the verions in the +task interface have one important difference: they are dynamically load +balanced. Thus, if the execution time per item varies significantly, you +should use the versions in the task interface. Parallel map ------------ -The parallel :meth:`map` in the task interface is similar to that in the multiengine interface: +The parallel :meth:`map` in the task interface is similar to that in the +multiengine interface: .. sourcecode:: ipython @@ -63,7 +78,9 @@ The parallel :meth:`map` in the task interface is similar to that in the multien Parallel function decorator --------------------------- -Parallel functions are just like normal function, but they can be called on sequences and *in parallel*. The multiengine interface provides a decorator that turns any Python function into a parallel function: +Parallel functions are just like normal function, but they can be called on +sequences and *in parallel*. The multiengine interface provides a decorator +that turns any Python function into a parallel function: .. sourcecode:: ipython @@ -79,7 +96,9 @@ Parallel functions are just like normal function, but they can be called on sequ More details ============ -The :class:`TaskClient` has many more powerful features that allow quite a bit of flexibility in how tasks are defined and run. The next places to look are in the following classes: +The :class:`TaskClient` has many more powerful features that allow quite a bit +of flexibility in how tasks are defined and run. The next places to look are +in the following classes: * :class:`IPython.kernel.client.TaskClient` * :class:`IPython.kernel.client.StringTask` @@ -95,5 +114,8 @@ The following is an overview of how to use these classes together: 4. Use :meth:`TaskClient.get_task_result` to get the results of the tasks. -We are in the process of developing more detailed information about the task interface. For now, the docstrings of the :class:`TaskClient`, :class:`StringTask` and :class:`MapTask` classes should be consulted. +We are in the process of developing more detailed information about the task +interface. For now, the docstrings of the :class:`TaskClient`, +:class:`StringTask` and :class:`MapTask` classes should be consulted. + diff --git a/docs/source/parallel/parallel_winhpc.txt b/docs/source/parallel/parallel_winhpc.txt new file mode 100644 index 0000000..cb9ff10 --- /dev/null +++ b/docs/source/parallel/parallel_winhpc.txt @@ -0,0 +1,333 @@ +============================================ +Getting started with Windows HPC Server 2008 +============================================ + +Introduction +============ + +The Python programming language is an increasingly popular language for +numerical computing. This is due to a unique combination of factors. First, +Python is a high-level and *interactive* language that is well matched to +interactive numerical work. Second, it is easy (often times trivial) to +integrate legacy C/C++/Fortran code into Python. Third, a large number of +high-quality open source projects provide all the needed building blocks for +numerical computing: numerical arrays (NumPy), algorithms (SciPy), 2D/3D +Visualization (Matplotlib, Mayavi, Chaco), Symbolic Mathematics (Sage, Sympy) +and others. + +The IPython project is a core part of this open-source toolchain and is +focused on creating a comprehensive environment for interactive and +exploratory computing in the Python programming language. It enables all of +the above tools to be used interactively and consists of two main components: + +* An enhanced interactive Python shell with support for interactive plotting + and visualization. +* An architecture for interactive parallel computing. + +With these components, it is possible to perform all aspects of a parallel +computation interactively. This type of workflow is particularly relevant in +scientific and numerical computing where algorithms, code and data are +continually evolving as the user/developer explores a problem. The broad +treads in computing (commodity clusters, multicore, cloud computing, etc.) +make these capabilities of IPython particularly relevant. + +While IPython is a cross platform tool, it has particularly strong support for +Windows based compute clusters running Windows HPC Server 2008. This document +describes how to get started with IPython on Windows HPC Server 2008. The +content and emphasis here is practical: installing IPython, configuring +IPython to use the Windows job scheduler and running example parallel programs +interactively. A more complete description of IPython's parallel computing +capabilities can be found in IPython's online documentation +(http://ipython.scipy.org/moin/Documentation). + +Setting up your Windows cluster +=============================== + +This document assumes that you already have a cluster running Windows +HPC Server 2008. Here is a broad overview of what is involved with setting up +such a cluster: + +1. Install Windows Server 2008 on the head and compute nodes in the cluster. +2. Setup the network configuration on each host. Each host should have a + static IP address. +3. On the head node, activate the "Active Directory Domain Services" role + and make the head node the domain controller. +4. Join the compute nodes to the newly created Active Directory (AD) domain. +5. Setup user accounts in the domain with shared home directories. +6. Install the HPC Pack 2008 on the head node to create a cluster. +7. Install the HPC Pack 2008 on the compute nodes. + +More details about installing and configuring Windows HPC Server 2008 can be +found on the Windows HPC Home Page (http://www.microsoft.com/hpc). Regardless +of what steps you follow to set up your cluster, the remainder of this +document will assume that: + +* There are domain users that can log on to the AD domain and submit jobs + to the cluster scheduler. +* These domain users have shared home directories. While shared home + directories are not required to use IPython, they make it much easier to + use IPython. + +Installation of IPython and its dependencies +============================================ + +IPython and all of its dependencies are freely available and open source. +These packages provide a powerful and cost-effective approach to numerical and +scientific computing on Windows. The following dependencies are needed to run +IPython on Windows: + +* Python 2.5 or 2.6 (http://www.python.org) +* pywin32 (http://sourceforge.net/projects/pywin32/) +* PyReadline (https://launchpad.net/pyreadline) +* zope.interface and Twisted (http://twistedmatrix.com) +* Foolcap (http://foolscap.lothar.com/trac) +* pyOpenSSL (https://launchpad.net/pyopenssl) +* IPython (http://ipython.scipy.org) + +In addition, the following dependencies are needed to run the demos described +in this document. + +* NumPy and SciPy (http://www.scipy.org) +* wxPython (http://www.wxpython.org) +* Matplotlib (http://matplotlib.sourceforge.net/) + +The easiest way of obtaining these dependencies is through the Enthought +Python Distribution (EPD) (http://www.enthought.com/products/epd.php). EPD is +produced by Enthought, Inc. and contains all of these packages and others in a +single installer and is available free for academic users. While it is also +possible to download and install each package individually, this is a tedious +process. Thus, we highly recommend using EPD to install these packages on +Windows. + +Regardless of how you install the dependencies, here are the steps you will +need to follow: + +1. Install all of the packages listed above, either individually or using EPD + on the head node, compute nodes and user workstations. + +2. Make sure that :file:`C:\\Python25` and :file:`C:\\Python25\\Scripts` are + in the system :envvar:`%PATH%` variable on each node. + +3. Install the latest development version of IPython. This can be done by + downloading the the development version from the IPython website + (http://ipython.scipy.org) and following the installation instructions. + +Further details about installing IPython or its dependencies can be found in +the online IPython documentation (http://ipython.scipy.org/moin/Documentation) +Once you are finished with the installation, you can try IPython out by +opening a Windows Command Prompt and typing ``ipython``. This will +start IPython's interactive shell and you should see something like the +following screenshot: + +.. image:: ipython_shell.* + +Starting an IPython cluster +=========================== + +To use IPython's parallel computing capabilities, you will need to start an +IPython cluster. An IPython cluster consists of one controller and multiple +engines: + +IPython controller + The IPython controller manages the engines and acts as a gateway between + the engines and the client, which runs in the user's interactive IPython + session. The controller is started using the :command:`ipcontroller` + command. + +IPython engine + IPython engines run a user's Python code in parallel on the compute nodes. + Engines are starting using the :command:`ipengine` command. + +Once these processes are started, a user can run Python code interactively and +in parallel on the engines from within the IPython shell using an appropriate +client. This includes the ability to interact with, plot and visualize data +from the engines. + +IPython has a command line program called :command:`ipcluster` that automates +all aspects of starting the controller and engines on the compute nodes. +:command:`ipcluster` has full support for the Windows HPC job scheduler, +meaning that :command:`ipcluster` can use this job scheduler to start the +controller and engines. In our experience, the Windows HPC job scheduler is +particularly well suited for interactive applications, such as IPython. Once +:command:`ipcluster` is configured properly, a user can start an IPython +cluster from their local workstation almost instantly, without having to log +on to the head node (as is typically required by Unix based job schedulers). +This enables a user to move seamlessly between serial and parallel +computations. + +In this section we show how to use :command:`ipcluster` to start an IPython +cluster using the Windows HPC Server 2008 job scheduler. To make sure that +:command:`ipcluster` is installed and working properly, you should first try +to start an IPython cluster on your local host. To do this, open a Windows +Command Prompt and type the following command:: + + ipcluster start -n 2 + +You should see a number of messages printed to the screen, ending with +"IPython cluster: started". The result should look something like the following +screenshot: + +.. image:: ipcluster_start.* + +At this point, the controller and two engines are running on your local host. +This configuration is useful for testing and for situations where you want to +take advantage of multiple cores on your local computer. + +Now that we have confirmed that :command:`ipcluster` is working properly, we +describe how to configure and run an IPython cluster on an actual compute +cluster running Windows HPC Server 2008. Here is an outline of the needed +steps: + +1. Create a cluster profile using: ``ipcluster create -p mycluster`` + +2. Edit configuration files in the directory :file:`.ipython\\cluster_mycluster` + +3. Start the cluster using: ``ipcluser start -p mycluster -n 32`` + +Creating a cluster profile +-------------------------- + +In most cases, you will have to create a cluster profile to use IPython on a +cluster. A cluster profile is a name (like "mycluster") that is associated +with a particular cluster configuration. The profile name is used by +:command:`ipcluster` when working with the cluster. + +Associated with each cluster profile is a cluster directory. This cluster +directory is a specially named directory (typically located in the +:file:`.ipython` subdirectory of your home directory) that contains the +configuration files for a particular cluster profile, as well as log files and +security keys. The naming convention for cluster directories is: +:file:`cluster_`. Thus, the cluster directory for a profile named +"foo" would be :file:`.ipython\\cluster_foo`. + +To create a new cluster profile (named "mycluster") and the associated cluster +directory, type the following command at the Windows Command Prompt:: + + ipcluster create -p mycluster + +The output of this command is shown in the screenshot below. Notice how +:command:`ipcluster` prints out the location of the newly created cluster +directory. + +.. image:: ipcluster_create.* + +Configuring a cluster profile +----------------------------- + +Next, you will need to configure the newly created cluster profile by editing +the following configuration files in the cluster directory: + +* :file:`ipcluster_config.py` +* :file:`ipcontroller_config.py` +* :file:`ipengine_config.py` + +When :command:`ipcluster` is run, these configuration files are used to +determine how the engines and controller will be started. In most cases, +you will only have to set a few of the attributes in these files. + +To configure :command:`ipcluster` to use the Windows HPC job scheduler, you +will need to edit the following attributes in the file +:file:`ipcluster_config.py`:: + + # Set these at the top of the file to tell ipcluster to use the + # Windows HPC job scheduler. + c.Global.controller_launcher = \ + 'IPython.kernel.launcher.WindowsHPCControllerLauncher' + c.Global.engine_launcher = \ + 'IPython.kernel.launcher.WindowsHPCEngineSetLauncher' + + # Set these to the host name of the scheduler (head node) of your cluster. + c.WindowsHPCControllerLauncher.scheduler = 'HEADNODE' + c.WindowsHPCEngineSetLauncher.scheduler = 'HEADNODE' + +There are a number of other configuration attributes that can be set, but +in most cases these will be sufficient to get you started. + +.. warning:: + If any of your configuration attributes involve specifying the location + of shared directories or files, you must make sure that you use UNC paths + like :file:`\\\\host\\share`. It is also important that you specify + these paths using raw Python strings: ``r'\\host\share'`` to make sure + that the backslashes are properly escaped. + +Starting the cluster profile +---------------------------- + +Once a cluster profile has been configured, starting an IPython cluster using +the profile is simple:: + + ipcluster start -p mycluster -n 32 + +The ``-n`` option tells :command:`ipcluster` how many engines to start (in +this case 32). Stopping the cluster is as simple as typing Control-C. + +Using the HPC Job Manager +------------------------- + +When ``ipcluster start`` is run the first time, :command:`ipcluster` creates +two XML job description files in the cluster directory: + +* :file:`ipcontroller_job.xml` +* :file:`ipengineset_job.xml` + +Once these files have been created, they can be imported into the HPC Job +Manager application. Then, the controller and engines for that profile can be +started using the HPC Job Manager directly, without using :command:`ipcluster`. +However, anytime the cluster profile is re-configured, ``ipcluster start`` +must be run again to regenerate the XML job description files. The +following screenshot shows what the HPC Job Manager interface looks like +with a running IPython cluster. + +.. image:: hpc_job_manager.* + +Performing a simple interactive parallel computation +==================================================== + +Once you have started your IPython cluster, you can start to use it. To do +this, open up a new Windows Command Prompt and start up IPython's interactive +shell by typing:: + + ipython + +Then you can create a :class:`MultiEngineClient` instance for your profile and +use the resulting instance to do a simple interactive parallel computation. In +the code and screenshot that follows, we take a simple Python function and +apply it to each element of an array of integers in parallel using the +:meth:`MultiEngineClient.map` method: + +.. sourcecode:: ipython + + In [1]: from IPython.kernel.client import * + + In [2]: mec = MultiEngineClient(profile='mycluster') + + In [3]: mec.get_ids() + Out[3]: [0, 1, 2, 3, 4, 5, 67, 8, 9, 10, 11, 12, 13, 14] + + In [4]: def f(x): + ...: return x**10 + + In [5]: mec.map(f, range(15)) # f is applied in parallel + Out[5]: + [0, + 1, + 1024, + 59049, + 1048576, + 9765625, + 60466176, + 282475249, + 1073741824, + 3486784401L, + 10000000000L, + 25937424601L, + 61917364224L, + 137858491849L, + 289254654976L] + +The :meth:`map` method has the same signature as Python's builtin :func:`map` +function, but runs the calculation in parallel. More involved examples of using +:class:`MultiEngineClient` are provided in the examples that follow. + +.. image:: mec_simple.* + diff --git a/docs/source/parallel/single_digits.pdf b/docs/source/parallel/single_digits.pdf new file mode 100644 index 0000000000000000000000000000000000000000..d62bf3cd563a6e2e70e557d22baa073dafec929a GIT binary patch literal 41799 zc%1CJ1yfvIvjz&2fgr)%T>=F6V8Pwp26uOt;2zwQ0Kq*t!QCae1h?Q0LGR|x`<-)c zo%0KB-Kt&8%$mKsd$m0M^qPW7QB?dL3nLo>RnPYM_Cevr*NL7!1a?wpQhOsS1U^1e zCRtNEa~BIz4q!={lu6vu#>LbLc(pNfF%>m6wl^^)6%ashc5yN_v_Idt((-7g8PIt*AIDlawu> zQuyCjk$+#MNcBjWG!=}jOpRSgU!Ib(Wg+GIN1K2E>3<96`rrQK{%h0U?fgdslZva6 z%Rejsh$m&zurztO7ciAc($v!2!sW%}mz~+zJE=Gr8k-{U{FNgGY}LuZ{v|4;E>5n% zqbhQS&eo(X%zwWZwsdh;G<6cOw{@_$dvO}~{~1e!{~61}Z1_gGnfv}q&oMsJf&oQK zR!d|6l)Ky=tjWH=>w!2I4pKQ}vyMMI;cz zI@C&3i?!=Y-g+9%Jl-8tl1>yVkkkgI}K8$VlwITxgReVX1i(sYIc1-d44)6Z&+cK2{KP+GatiaGfQ)~X<;TL z4nqjbI;|{GD>XyNN@EQ5ckM>vO546|&TCNe@Ob}Dr=Go<+ZP%Kr$F*~$a0L~%Re@g z;(;707=IkHy2P6fr?j5+5s>)ZuD>Ss_-eaU4}*wpGh3Ffb-vQ-&1JV7?mzmTQ@`;L zLRBzaZQL(paDRPNu;HNeur#?u%U5Ny@V&QZrD9IkQN!xf9K>Ht7Zr+L%0`4;y2&oc|k4~`L!0Ah}Y-lm-|i>S;A)C+lNpjf{NB_?eMU4BT3~N zv(bafc49fRHUhuEx$=WbV$UtK-tY_)dCwF{|19r7^)_459#E`N!#9eFbYeKL}>eTt>skB81KQ; z{YhAA$a`9ZU!`@l-TtMT)f@ab%Rzj5mE(fX4^s+)znV$uT26rvgaTL(sN7^I{FkT* zZ}pnl3Cn?G)XdnXCSYYo0}|sY3$@(jTzICg-t~Oe zPvT&CzBBp~+ZJwzwGKwT=8pB+J2SJ^9hj}!igwnAQoCQxSd6;3*7=Ia1)1@M>gC#s zQ$F+$UCgfTZf@f~dix52U|WEwxk-_5ZqZPtIU8gQLl5TR1EP<_OoJ zgH}$|w_A_YP^}rKZpYimE<(Ih=|+DT_c7jMmnk_e zvLzt(?B4NgEmpBGLs~ql|Lg(1tvjk*yN>llG-=sq2=QOUQVfP#ri(uPio=E;ipacy z#;S}t*)FURj)qc-jhpg;Y{wyNvUDu_{anUw6jPhfXIkX(CHwZ6HzHxkb!{OBgNZbo zPhBC1BFC8|F^eL+jw797m1CNLZgYC>>Dy}{KT_`bZAfRgs8S}U?RRy<*3g6SKaHR8 zYBeCfSP8~cf$?mN&j@)Yv*(ktvXJ%m(TVjk9@O5t`x_^i>U!DaDicwy^vt|AG!Q7M#Jr-s8<`{|Islk0AwFGaIFc zM1c+3BfW>I8SEyA4dO9L=+vo7`hf6;U#rIO`x`b1r3ZT1$Q;x%&_}-6=~4}*W&iU* zn(}-9gl)L$6-OEMTMw`-Ii^QP`#Dj`rXOSqMS5A}tMH zPOSuqFZ(z43uB`hydWF0C#a#8zMpNCKwB6gaXrdFecY$0j@CE!_|7@pj`kg-|v-pLCJZcqVY~EtEs1IPY`J-;e zC}pYDD7RQ&ea!WKi+j?A{i|K77whprY85Ty`^ z@+%UwaWPx{CLTW34g^m`1ZWZkSctrKmjg<=pe>2eJ!{u8n2b41mtY)`E_?%M|I7X8 zD-HJ*&q6I^#;T(COII42cp(YK5&o{%3+5DsuB{DqjJn2KNI%{P&qDBi;E!r)Gqg*P zH7q+tUHw}1f97v?Gcz-(xzk`lcpjwrvL+__m17vb9quiU)N_e(e=M1r!6R0Si-py4 z+%aBg%W&L_mB6(IAE;Gh=6l-tIt`_Lwcm0HB{GOF}BGc)eq6j6EORM zek6*l0<=L5kcAOCtO^$I5N;(huF<$Ya;qNb`Bv`+M~w7pYbGz(zNZhWH&=6zOffR) zS1ZB|#HJ4j#H8n6l`qQ67^_m?d8!j9oBdHoMjwO1eL~*Ms~a4Xh+;s2z|6z1u@8%T zERykJ5ZtS_QJKU8EhB?%)Su~cPaS-ppYH81uyhSD(U{1!KLSb4yq#J@sMA&fSi5b z+ZiS8{4V?wY8~9Q4&UM2v4;MU!mvC^%gxJ0oX9F_OhQTUs9%jb63eB7E=0K*$EwdBO*US@-A*B@UU6_y_EN)=Fth}^?SV{a-dIl8GHu61ZZSz;5 zSA5~x5xqy6$H_2OInj5#GOyU16 z-~YN$H9_|`C;i0xoVJSwo6I|CBXYCUs5Xeg9oP@r{bUr}HXErBKNb=^oNy@*fczU= zF}RVZ(rYkoNvdScgp{9o;81Ny^w(i3t1+w89-&L20bcq6{Ouyo%(38j!nT` zug!-?pJn`GgBe>z^#X!YRQU}WG(nhq+G}%7lCzBep3c}QP>&(D^HtEtQ(J?H{8>^7`GQKug2KW44E*K`>V^2*Da->(vqW6Ezl>cdi<4jPRKDpc-1Qnr-%w`|oJ9 zQ3Jy8Xhzze?he#jqq}t)>{nwm`y6e!DPGaI``rC5|}Y>XuHJ8_qZ7+xF#Kg z$JTepE5Ft&N2q7m8c_l;_z3i>l+O|Wv60jbw8uXgmP=FTR@R~(CIos)uBIe@ot3U_ zz|YrLM(lTMxE%4T{iESG=J@{vkWw0ozjy)XLijCc{YkC8@5p*)D;2ibSc;*c&DCg|jSuv=fPR z+p3DVi9(J3Ay-G5QN(R+tpF$P)2T5Z5B2AG>4>Ph01EZK2@FX-T|ZcI%<>($b9+$g zXzyyDb@K7GML(^>E;v=~7tL7U;G}-U|sNu9xJeJVXq+`Ra$9(>~Y1G+d_;^6#WOUq4``tol zazt@~KT}cckZqyL!QMASo6!AJlzt-6-Br%9gL4kH=u;Lj7+?OLTE}1CV%4QEbVE@y zj%226v*?Y)Du3O(@xamIx@6nndpRiq$D$3;6GESQ^KoJkn_HW3Ca*D`-ImAerK0Tw z8OBbd+tb_Jy9L;G4ZW(R;8KGF%ILA*I1B9Z81e-^_42HjguWW>0@F&mcG$>qa02M) zDFvB0a8y&EyE{b9qF>FrT}WlVBLUVpA{G6t^^wE|8qXf zRIV^e$1R8(E0+EKL0_%#T^H44v8&5Zn0I2XU88E*?|JKxAL(5Wr8%T7YmkC-6p@@* z&lR&x_IMsE{&BE{d&y?)8I4NYKMu~29X{8ihh7X#hOLzPuf#eOF*e7l&8m(0TE~4w z^%oGMpzalaNbYMO+lsVv=8ON$>|;~|l6_=z!CB#@Q{z%4v;R=yDipOGtm&k{`W&+# zigbThui$~z_L!E0Pxb3qp0hH4`Gz~7>~ZDe*r39aL+m>l>VK3?WCF@sm$>kbEC>cC zHh=t8_JO)XfPJ8xM9+Ou+(p1g`rqo(LNg%Az`|Q8w4H$cPi?ROQZY8MgBvUeK3JIU zOa9dbhQ7{`SDeX{T?{2Yl6?2~=4F}=fob0zzvB4%_0*KytiM7_0HGz?QAHFewj$Z} zUn&2tK9K|WLXNfzYHN|WENdA5ZY&$v?o2Vq(M(9aA(KLi_+J1f1WX8;nmx0UUJ6ge zy-55QK|cie(!L|32i3-u4V?bx&_fikft~jYsbIb>xaq3+{Xfe>tl;@DY7vDKcS{1V z2BtcSe_Z1b3%F)?{;t(zF?3bu)A?Vy9%2E$MH*W(m8tbPegsOwe+}b!@pz4%>Zi#1 z9ASDz*uSfS=s?W+)tx2G7DL}>X(9f#%MVbtKUd&pDY{e@MLA6Nk1{xXz+)e*&*rc4 zZF|NddH&6i4rxFekTB(73j%fg7V3XNcOFm|ldU%4^po@B?8bEJ-+LJWKOB)4LwIVD z9v74F|6)<22q58o)}tn(ZFgL`c=}(#s-ztPsv+u%DzJ;88Arm!e*-iPI7ju7^ITeZ zF;rtqi|MbutAMgAZ$w@iLECPgdWQ6WlnL1b%C78IO=|OODMjK={&p^KI@J9HX@j53 zFH3^SKc{d1b-D@QGYfSSk1zEf2>#PIdVo;MMeCrhcRkg!}~!kjui+* zEk)hmn;pIYfw)P(&lg_|g=j}Q{nb?@4s6WoU2ZI2Et2cv$ArJCZeNPWzb-ZN=TFY% zZ==nBH|7FllWO5?tp?6wXe*x<{@-3ktwWZKE8d3N+%VB{?)CQNqO>llE z@4o*}m(K=vw&(NlA*mj1Yqs$C@A4o}lo@aMTUYaK_kOia$AWV)Bo!1+rfBNqvynNh z-MZQv`?B{cUmOu21(+N05r+(jB$*H}Fnts;=#M4hu&R}n(n?NnVxllLTb4+WL-=c?yK+Wo!5;xt=4R<|i z>YEb-E(SIn_+K0T$`2h2v0Uv@ov;L?XHy@po?8+;*}j*S2T7r?-IY>8pSuEyEln^G z31v$`&)T8@TEyrO=p0I}_Gdnt9{V7RZ1ilP-TCpI`=LQN>=D5igvFO6)&M4^Qr>n=9BI|Kc^P_1xFqe5rg$EBl4>-RxX?92g`Iq)P`9Y2;mP46Va$IQWwK zbTf`zgOSD0hx(m;ixMUygWn&jDLFqNIl?7@n?)H~;6b%W8PM?Hl#Z*psPpaK&Y*Ot z!(a3z>R6*05u5#Nzjj{(S?2)ks5|{M_CrAZ2N}{p1N8Z-4_hN|sb#~zKAvxrOCYn@ z=>|)*hyfgt3IHJKpgDCwEp_b1_90dIWRco-QV!n&wnA|?1AZ_%HG7{Lz0@*?Rrg(~N{PNn`Luc04 z@ZdxsHx25&8MLQ3jdFVUD8FI^T35us2gebQ>K7XKi{^=MM6>k>KAU_==aD3Ru>iJe z(pWXU9*u(5i~^b;&gUwbujfw)j?s+S6tz;-*S$HW*@qDcP#{n@?24mAso!g38XZ{J z;BT{qcE@tXW71$NW#wq(lNc8gsCJNE4&EMQ0JxR=Z@Ar)Si2a{FTDIIb7tvw{>@@e zUYz{#pz>DV#14JpGf<>QncRjjmIUy5n^BZ&??KtT5p3g_nrc|~i_L17OpIQa;Wzv{ zG|;0!9n7=@CMw3&$AkIWq(Oe3~yZfe6T9?&}_|wzvB~ zKiMwUzEvwxjV0s>MIyi^C(tfeD^(9c#9>q~(@bU5^Bde~nK@~@XS82wpy%h5DRWZ6#5_23@6VPaDllxh`h2quW?LshpNNCXBF}Pn ze(?C7@lK!Nv6lAr0i_P~Dh)K`rAIqC8>-UvwDYw|yQDb%UHJ2@*CvuT`;Q|| zJTyed>a>5#qQvIa`qK;$ur)-v&wLAXZdO)w)X;Gzg!NwMTZ3k!8Ls_aaAaMNrB2&J zcyS}>*bJHhtb(^|uP$O8KPB*o9_MauQQE~ewNPWOGu*>rO96%xOaMl+s$=vZjfkZql&jD0xTjd}nH~tPrRq4NPtN3AZtuEt zW6(uOU+ccR)~>U{i&QPw#!B>EhleJKeq*~-AJf?8yHsyWM*DATe$GWDr_l3A+yxyAv%CknNs#q3dM5E#T8 zLqzYig&Hq>z@zy#iW=JWCB+Nhl_fA}*IJBa@o0*}K#Fu4>>9;+Jx)o_4gNDipuHWw zYxX2+dqP-xGkTjl&oEo5flHdT{ zp+v2BrIoickC76U9%zNt2~J#Jct*oI5c^C>7*VCCKY_|anbkuOu;>5_k}e!S;XSEI zKDaHjkM+OR`c5fq35;LCyPJ(m^<^<&K57FnNbh@(Ba7dY7SO2dng`D!HKE~YB?F$4 zKgQ&SOV%QZ;eRyx6c7Vu_6UimkA|#wg|NGpWQF_e65*5>VNrg?2CT5E3zPmi!KrG6 zG$ahVM(xO;=jJn94?cR!*MjNcNc29E)wHKLz-N8jGwdb00;)>|a@s75;y>ZE0B$4( zwSs*8#z-saeUL`z`Fw`%_iua43p3I)8}gPQfQeX;X7W?#TN1Ds8d0O4qCkuO=vu=f zhcsoa_^8a@pAR!?4DU;J$N-?)`mNIKS1nTAQrORUAs?{A0BM&Wx!DhzVhtw8MN4wy zxL6wUDrIzsWB>~_9Pdk57egyEqPh}5g zSicCigLXYAv@KfVOo|3?PzH3C){JA9Yxn-iC|^irOh#T|8=u#vMWldMd#S2A)0Whz z`gLCdMG$&bL>rj)3Hx_)D>0;|a>!m!(DcvP2iO$V^tcgZCdQGUsRkcFNUdKcc{6hTCsh?g6p zGQz=k9_~`TX2B8<^!52LtK~+i67eNTbW#WnFsikjM-sMZH^{?iJtadY2Bnb)&$;gp zc!hxTXkU(`u5&oMrpss-L(Pz+T|ts-^DK@#>A5O2+p7KO?hG#dPVUs`6EAMo)8X`N z1;#Zt6L`wCcY&$;2|*eVpY?Lgcxw_t{h`pWNV1e)sQ`J}oTcxiCpm382ZSX%=s{_r zrTTir4~1sJl8nIwViQWi#HQrXk1sP-rQFfa@OncWsT2{=XH<`M7#^X^tGUmr#47pR zjIYCX*7gXgU;4wn;^X2pV~hoXTf7Mm<(fbsg@ou#$wr3tawSh2zWK-}c~ay6GjizB z7olYzY*oYR4Jl-?#XyW09#5X}6k)eJ1gWJlKbjM1w%?cyCKxT%H?D3YQhsFx%%y9H zZTLRP*|I&pC`D=r3PXt+N*230hR0dkkNW*PR~B!wIxnEIR}{1e(C!jBmBk7;+itgS z44=TuWXzTcge~WjEdCd}!&+UIQ_G#*+L9eeK(u~+Jsjt?Ab7Ijk|uHig&{{EVD-4p z{fX|5;fm}E>1Aram9>eK`G3#09dTf4SyUe9$QS}&>id;erw@KLW}w%1%<%!RFL9j+ zM=lmxa)4uqqJrLqTkA;&2FIJFCwQ0K^9m)r#k2SB$}jngQ{w-c4lrpP)&gjC)R!HCHjKnEP@?+xXR&ra z)1$UWMSy+efV(R*$CcJC31T-)*C`F&2hw~-ZE}wK;$Potpn=$98R2;z-WjABkWajU&=RXV{Fh^ zUcncM7>wZZ99}Hh?@J~%QufD2h{uS=;2S+Z^&qZi0=WkSZRK2}$!#%os9a0OC`rwwH5D3F$_rXw`Sui-rG{}k_b=%ux=oXv~=wEtVa}K^9ii9 zhc@kdW<(Haga)J)AorN~EP!+UF2KKn z2fd8V7TY~8-)t%SV(jAshQY+Xs$0V}^>(gJ+FT_;$__-{mkxQ1vmZd$z|>;Jq>wG0 zXS+9@F)gM6^1cqw73<}{*(>?tam2wR6Ap064hZ0`yVBO>q?Gz~YTH>cPz0LC7FxVu z`|ZVe&&qYb&aWTCN(}E_?>$UHC9#sdoPTs-ho@Q)1X`HU{Wb?_rnxLyZam#TmWjOi zE+SXf)uqi)Hef^!-TrcflbYfLkzWkGAJL+K?-4T?K*j4ZxW8`HKQ}cyB=2D$bHikf z4}bvWb0s6w#6)A;?YmPJ4>qEyvQDMd)x10*xhHrUi`dh5phm-aNrewUDllaXVvt!9 zd^Rx8r$nU)416V)BMgw3zQeZh&{qMdlSGcc(0*k{{O|eRLDlBjtuimUSZNP<>Sg=m$)m|~ifq=nVSVzGoqy^*AB zUyn2UKaSCb{s*zsf5e_A}uSUrGC|LdHl3cUl5g?q@e z2kxLXK>zun=FPL~lpM6$7pqbQz_WL@Tgkz-NR0N8`C$6DVvQNiLzMNhQYL>sMIaoQ(>~>(nZ%o+ zE6-r1%Q5($4i3UJ;YdR|QYo+dE#f-_F|EF;iXnRMHeEEyN@c1hJtXxoEhohjXuX^h zmH+)nsXiQR*Q%C8M|FJ^b<6yftHBn7N+xnF+w;;{2uaAa?rKj&jrtI>a9M>Et)A^v zvBmdsyPbXc#(qli918vRN+X^o4wsEOpu{}yJkx$i@Cc9X`(R%`bIH*F^_x!a+DW*xhFESUYtnj zH4i(hs})-Q#I~&X0D(bd#~^YkKDn6q3bsQw%@^bmuRNLk*{7SVNG~< zrZ6VAf9{LB(MOklIi>+@t}np&=jmC9p119OX=UU=MPiikSzR>$3BPw_I$Q5pwtSUI zI^`$sK)KAVH7x!x8+h__I`Sp|XWx1}fEX{6ew85Ehb>;4$qBPH__9{~7-U)KhTN6h z_0-9J@bn>CH$Q(c&uLr`!mG{glILiQ*M4>}-bC@%L%i}^pQf(M-S3UH4^$y4Qnb-0 zGbL1vFT|oGK{JV}=*2%li(X^<GguIv%?}>QG1s)XnP6LYcba=IaCXn*3x6#pQADRpaKr;?dT7BQpQDfO0$HH_O z+!&TF1Gl#93wGPyDcZAGY5m}=pZpflxL?wfxt9v*>50e{dEHd|str3fsrAvV+JOv* zfF?G{zE5)P$H`Xk)z%=3nK`Ev)C+&<5hXKMwM1P(pC~}N!p=*nJ1`{Btc)eLT4X24 zfP>6AlsZ5WzUXskSZu%<=cic<0GJU5xY-pWnI18^fVEc)YIEfFqUVL$oPG)*_ALs7 z;;7I@S@QT}`Wx}_PhmDv$Xj8bK*6)%=!T$TktF|FEdW`2zndN+9BmPvexI#O6+|kK zh35_h&-A;=JQ$K&yuyI@5EF_Mn5Osgnkxl@3WiFA8KYwG{qkO8*(Fi;P29;vSID-K z1umoUsq${x>u+17mDn>Sj`X@8%VUJ{ZvO=3Tf^}Wi2_7uA`j&XoPC{aWk8@A5valz zisYPE&RlfWi(|AUiMImXjeJ_SKWBcb%r#)G)t?HH#^|zBF9?QTYb1R5L(vQam?A6` zBruI(_n}Dj)lvtskc;vc-yJt+!%3WvsYbgvT`9s)uuQH$pdTP4ZYI-3ot>p71n6M` zn<%Jc>S>Z4z99U|oMRD0$ryZ~$V3en$GRy7N7nMW!(Xpn;LaJH{tS5dLzzeIht{FJ zM!-xZ1(i1hXCB6eUvDe(ow7|H$OcKfx<8>mei-qhdWChmbJ{K@(W%i%#1eLQZE)qx0OPed7+Jy-5IqOCJb*47;!`&WJzsjJqFNT0gWd2NY5HPZjz=?2e z4npyE=ZiN`{q!30{M))xM-0#ThN8oQR!Tnyzyg0n>wjnQ#D6)rV+EhS?h?#0fja*M zhP!P>krRSITFwtD+q)dCCUeDB+kDp{NF)Nwbk`_?4~pL;<99|gne1kpe{DN|QCLj_7NiT@O5Y(M{|Q z61Zmvb~^}%+J!4g3AHjo{$icAMx*~so%I~6>5xJ6aFNBu&Zy_b4iU3Kz}cirtt?AL z(@utP2m+B9?S+cvMo$E-GhlXK|7L%jaaE~dcc_`)MOS-$U|navbVT4QIkn5CBCVQ4 z)A@Zt9K@R|CDug=am*Ax|5cl58aNRS*+ox)>Z}BRWXPJyR{I`DuFZz^=$Wh zywjSoovR?~D=Tv^o5LLzCR=K-=d!D}`_=4rv^e`FPa;k{RIoc%=(^GtH!4`8qSmT- zki$RJ`sb(gnU32ns#G{|PA^O%2KY8?6RehkmmT1LKe6Za`;2|Lz3ZIrRydJHp`I=+ zBO`;&e2fCF6!Wd&=W3gU?=);}jl;?GbFJR))_pLD*k5HwXteD&7TfDtJgTDS%eIl- z87bhamH7pX;3;7%cgE*L2(aqbNMa+)bAQM_9f`Qw;}t_1{p*~zrEi5R+n<`%9{Y*C z-*Anh(H-O!s}ydaVVkCNv83a&J|w54g!0vxk8@t&;yl$MF&Ir=DQX;I@YVf2!gm>( z7fkDW3?eHysEoV)Jyl!WxCTPj=b5(yJcr5{HTLmkPYjqp_uUenW^%=%*5q6dW=9dI z^q)(B1j%NAFWo55f9uieSwo00a2QBOYqmk2epV?jNMv2jtLte>7sN&>uM)_$>JQOA z>03x!%gRMiBK6>ahWE8*^6bUksi9JC@WA}S*TUFE>yXY;@fh%R zs)_8Hb!9a=LP5ch2t1oV2OSx1$V3EGf2FG3sKYbD82U%`5ZO~hU+;45y0S>zF}3^a zqfy(SovC`;C2`%=ws(gCo?jMW$1*GzeyVC*L@n9lVA1V0{gk~(8CJAEGve{6Gm+{u ze_F)?^^wKNq&6$#(Zkb0f4RgbhAIyksu)V=&A22yz@jfx!Lhw`V!8*|- zh?`Njo-JcJ?=}?iffdE$fN(KASP(ovP>d7$oJ*;|k20y3C$zHtfd9hD)>p>ZEXi)! z)k)t#fVx$xhS9LR;nrHlqzHbP4};AKta7m$%i@_TR0v}VhEZ)4=M87u5AMJ;l=PVd z>XlqMC2%`YLEr;anAJ?l%g^nl43+>sh{JO0nfWLk;vFtGv8kK*KAiQht3Fx~0J#1b z6GPBlH+g~F{BlSSS1`0C0Xk`;ALX#wtsqf5`!bTI+0VEc-5Btc_yxUGp(74p&Z>#2 zRe=1{vQMZUoh<-$&oimT@6sf*vH;*nWURl_kaxCW#JwDCG3LTiI)wpVp-d`~nm0Qx zh~(QT7bsag8Z`tVBHE!QQ2E#DP#2fLv(IE1-s{R1O9=+vQ0My<@0IB>?I59mCpA=U z9u^{BbAr7pc|pBpcZkvc-POCS>TtV_?bJs)e}iqmd)oyE5?@SBC}R59$|$xmRIZi1 zYTPn?V^G_H2!|cwjOs6kVdO5z{`)$h=-C8?a^FW4SOAw8GxbryHJ8f<~ zl;JpWsKD?j92_mMS?o3Wmv|utu+LmrUoPgwyl{tmeV|I2!uxmOt0_z^q)uC?I`#8E z4;gxpK+d~EtDyK;HTOKXSwqy@JL?66Xg+aohwy-tJH;^Ws(A-cTTKislIUE;y4Rk+ z`H4O-aN@Fw`^#NTpayAN?K=)k-uLxjl7W^kkB?d@lZ0emOs}(n;YdDDwOxC#dce$P z5s^!Ym6^#4D^dxg&nMcok%8gXg{m*v{R9tBM?`Eg7nI`#&_XxR)4qtU#upfI zvNfiqGy%P_`20c%CCOchDC|MT%w6W9)jn=F!p{yr-1cSg;o#dJ)t}Z$D-bc@=jA&f zzC}m-H666z2U7yQ=5Wam%6WT*GZa~uO7&%pyTs*5Kb7$a*CLR8udLoBU*s#h9z;Ih z3qqvjbFgUEDg{&>(=sA4E#r?dOp; zd9DYlsceepkEcO4NewFqNk3I*kw60bBELF1d4Xt|5V)_tqf`-*1sexrHx3?|V7hp7q^y z-k`(L-*2MCvlXVX_I`matefVRX!LTOY^s@+zEf`k#uWE~ES@&CfRFT29ZbGO38(Bh zLrnE7Ze_HJLWF!pp3Xf*#GFrDw)n>SX3lXPwU*ysquio@xN+!*OnL6Yu=6@T6y1uK zfggT`^_8GpEJ$=P)jJMS<*Lx!PhWdVv6&C~J?wt*tq9fM*bM$br`E$Ux0j!+zsUF0 z_>g`*F6d<`X3IylmMSo>oBTkk3R9oORxAQ_9kJH1 z>XR2Mnnald<(IG)Hh8#@bMq%1j86LL^XHX91*xB&LhdE`KGRkvX$+eXKEbrf(?Z8Jz$8ovB%pRF(w;o>J<6{zQI`_gG44 zxPRBo^icYl9|l{VSqt9hHs^tAZ1bp0mapYAw-U0xAr5Tf2Ds`Gd-vPPQek5nrh+R7 zw~2RKcisEe`A(yxAw}Z6_yj`sRq1z@xbfk`sa%W714&=JvR)s2KSdXP;aA$3btLK~>QhDXZgkPxW#eg{zGy+)tkev@-I%r23XK^HjweS@R|4U#<^sUj?_hJDLq=AxE$7nre4 zQ`gV4%l<+Py`#bI?qxQ9`}Ux*EgKdsK49YsOTB(;uzOShEmMAFxk=?t<7>|rH8jOB z5;dH3Y9D?yvIZnO^1U5GM{@$Dflu|}O124$5B~(%?NE|QlliK43ip|A!VFc+v-h#I z-c~G=Q>kZ{!f`tpR1+*)fmYSi%HoES-N9-5kdPm>t&KqmK|?+h<%{_}mB zV<~tIW*f`&ksjPar=xy(LXs3JDL!7yzA(w8lOl`f!=(-)Y>!M%I{(Ais#7h24EBwIeUJB+zLX8vO9tktDTly&tnHgoQ^A%CY@m1|u!e z^k)EogHDkSOPolmIS9vSx97vpDV0SFVa@#?IZWk*@D2$BzDIs-kVY&J1gduYG<_&yJQu=~P4TtB z)@A?lWV+rt@<8K!)j!JTPtSbyon^r%ajsvK0QDf`$^9gWNhJk3T8ffkl=}XrjhF$1 z+r#Ppxnv;>wv=CWM*YJQw};{rJJ8wHOb~`=$_WZ92yKW*$k&Ae`$^oZ5U`H~0&yTo z=G$vMgw*KaAsv%>PHdCLagUiAodWFG@6!5VKnRO{T_H{guox_|XXu($Lb=_GP}DSV zZ}7<5HEs?ab8S+7<3Y32axPvFGLGYS*ke^{Q?;9ER6c3g!Q3mhqs^%6!`S9Q1eMD1 z=TK;joNf)(3ZffN+C*2j-%rAmUW)-cfzSo-?&~3NEpdLc{ z#29caNvd%Pzt_AbWt4Jw$4aY{Jsf8UOT3T^Xr3)i(4SgGsT?JSQSeK%ln}=fE{8NG z-Q`J}1{aTo3JxsP!E8Q_fwT8mIz{WO>wyhpN^h~q+7P{1cj$h&h!iGOl~2o(@+c=h7k2jh*WalQRq%C~&JYpT@zZR`pi=8Tyqe4nc^6WRDh5B2L3J-T z_Dr|PHxwQcw1nD3bhb45O`F6Dp$~B!p4OCr8~F`EKE$KbneGW2icCBZEwDJu{NWOfe=@UAR-c?rlodDi=6)$#@;6nI3@KK>(~JP+dKTl*!vPOOZ8-c%;#U+CA4pq@_d+r^y54df>KM9F3@pllDc}r20_w zF%*Y5o87Q*^tL$A-tGHuW68VCPkkcbbYPw!PS{U9zdJau+$sDrotlL1B?3iIX+XB> z|E1V1|87Dy;VftlJY9{H#pASSX>{vQ8DXI$=x1)2*%6MWO}kl)-mpS9l!6t8>`4cv zcMpMjvO>Vd%#-*5j5%jyfzYo+o1Aj0@n(LD_)4le$In#9m=13#1dR*M5jI-y>-o2G zUyMvtg66E|@^XpZZ)Ie~Q(R4iYMUrP(a6qJe(~kpfun|&t-L(MD{1ESwJ0)qIOKqK zwRxq~@94yMZN;)6Z17w8cjX4{>w;i-zVmRxiTOJEb^FH5d^$WqZ#f~fr4AJFwBv_A`kND_Pl2zt z<2~WHN0Oid2NSo))hgWNp|UF>P1o9v*9&d40bTEfp(NteiqUgS#O@~G63$ZQz?gt< zWdkqXR<$J!ii1{tX0)5|78gpb9W`u2`G$j zL#$r$9mDSF#AtL+38?DIvygyBu5~Fuh%R&X@zy}R@n{AI8GgA=10&52E~Y%)eBWn& za`)qjueJzm7l{_*d=#%Xj+TGZq^o_p+K3XHDxiZC#^WQMC`~O==@0VK%Kr{t@esMw z;KrxmE1n1omC0Yo4u5Gs*1sX9OB3Rn?W=ynoD^(5a!_cnYd-{TbeEZ6|Af@C|Iv%| zBrh;P_qfrsgj)5%kUc7P1%u{kRZG=_?;FLQ=~!`ePQ))O^*z=@HZ%>(9F#jbPp#t- zrL$||<+HcWdSt6HI(Xh{`k3-}aa_BntGoh*yXw{!dE6Slp#L>$fV1n8ZG4n+4oVC| z`t!-l{~rUB=uC-PkiYBQ@50S4+1v0;J;%MMu0iLe4&jXis@}Cu1h(@g3+J5yl1=X2 zkhj>NdFJq`>gStw&2!ec`j_7!IILL-ztkTQq#C7R_h`@-z)?_--%o@q|8~5X(SWo* zsD7lMskKzuT)b_NNoDK~MOq4)5ksIEw>93Dwroy|H*^YznmyR)zx_mpj8&;q?Y>Ob ztxtxGuJQ?;7!qeJ2-k%x4MVl=)46bhBn!ZE0fFqsTJks(#wt@2zK!J zO`fLg7+T{two(%-P0RP!(TYbf2o5Kj*I#SL9WRTbiNfq@sL_QqVZr7Dbe=0=W>mN* z-try5Mu+ILlKQu30~G|51^Wc=1}3FlXbEYIHaj}Y{EGI!lDUSbcgS%?;B#<`;j%IL zvZ67;&S)zIx-6RzOLH8KSS}l z|00zCDe z+g*v%_K;=>)@QzC`plu$x|j$I(@UtV%)u>o=pGpcWzjWR{(>?%*=#zE`U{)YvRm-c zQ?;?v@2(&eSULOs%m|?MpJ$cP4i~i^iLecpoV;eRt5nMCd-!wV4@4WnMw6U;J~)QaqlYQy zZGS&ukzjer0BQ5N@bUPW8CD-S1?#x8rlMeJ0=6psn6{OwS5P0mB4r+&)FaG6jm`|u zphNAS9GpFDD6ku{176ckrKf>?6JAgd=4-)Ted!fyq49F8dg5|E!O5H#b5bFf>C-<_ z2@-s6A>4bqP-q`IDOw)QjOvRaDh~{~kAu4AFqT?*g>tLlByEEjNaFV?d)oU%jDDnB zfIUOgi=Tkc!(p|}*S9b+tR7lBe64>AKk(O&?SOR*g+}!pdphkO>V8pU*#`uHZ;PzK zcI|qqB|Y=h6=&+DI zbzh_gOTX~V`Vwr+d%jg3BYOX*ria<{^wOD-9moNRkp+N>q6$ zeJ(%v6=#K8jFrhOsOy#_TCz~&QE|CDjRmOhn`aVQH18wbOWsu47~eWLE?R; zLapV?IP*(UZ|(6qf~YApOg?s@V1wL$QOOHts{~_{cQ!tp%kaPjK#%MVmI|;ptNN6H zpet=&z1RQYssb&vX3}_X*F*tZ?VBb9W&INY3mrvJ7MJo974+t9P@Lcx>njoqDJbtH z$Bgdv?giRiHIj@N%}&1-|L3Qt$NL7$sRGqvJh!2Sfck{H^(tuh}aDCjgFh9^Ol5fJ&g#K>IYY0 znA!=YNLQTGpf;3VJ#F+NYiuTNNW`UBKQ-9jL^NVfA%fCqN)$gVdlurj*2tg0xg64J z*A2CKDcnsQ_Wq>*f7p7&1%cWvM2R6Qjgfk~$oOt$?^L=?pzJqnGnj8TfA!l`^!k&R`oj29Bq(pZfk_FmomId3kDDb57H| zFplPhcKP>s+Qoh%lg@!+TB+BueCPUfci!ssj}5dq`P){{)d)9(JAzzR2E}!g4P0Vl zLBWM~c&t^bH$3QhDkAO@^@FQ@5sGTdL>7$9m@gS|a`IDdxdT%tOXOggf(oYjK8O{M zbME1#-{I*h|Bwp0Q~!BDqmV7o<^5bv-)OZ^`Aw|3IEC44E0Hy+x_70~x)0w~bC3p{ zR>AByUSCh;iIenY8TOE^H6eV$F&K~z&R6^Fc8uyrr@eExd3Eqrwdflw_E4F&`uD^9 z!tV)Ebb#ulkwnqIRVEFj`7Af60!TsUWxpeAh!-}z32-!{HrBkZ=hSgo%#n3Z?OrXx zdFCvLTs{-z#mLHvL82lah1WCY_u(UPUr)ftiqBHXU%l3x7CyU-B+@M4GkVcl^!P)$ z?gk+7RjfWoZtTdC@FC` zwWh_dL`ujBT|zOR5!3N@b54=tb?HKilq`oZ>5Ze1*%4J|S?$tlaUk=|S}r#n7V_Yx+9Mp@$eBM%1ToVPWCwb#t^d&t!kxX>Cx)>08>Rn{>b8MB$fy9{ef85z$sd@DuJ0v2q=ormsNJOSY6|_Xvq6tu=DC&!H-9>A z|7d@qG0!8%cq)6_C0t8d9v)5Yu!S_0p;|_6w*!oC0#?^n83-~u zbbX$$_cC0*1pny$Kp!}Zd7?kS!SA|1No1u6Ghi@|)LWwL=zZ39n!jdb89eL==x`6y zY5;Kg&eRnZYb84JX0BvU0K1_{0&`7OOtM(DpSJieLMiol=0j`2ojrlwsx3I46qJ%> z*bjbQTH?-{rWjm`|iP+)`*>{-g<>?3;b^YQtXiF z3b?OCOfzx*Buu&`o($9e+Ii(fmqw~nx^QX9W{cV45N4Knu7eD>!3@Bj?9TcfN5+Si zwlyf7t;sx9AYD%1vKlf-jd__tjC`WM$tIRHl0=95-$_8|y0tc!5D-J4S7HcK^0k^l^{!ogiyWToS zOba+4I2?v7 z5YgW`O&ANO;{LG@kwfAn7*{1OtoAM(q7Y_+OZw{Mm?nn`!~;aGVXEBjTRjrqo)Ru= zUu4h#+hEoCB{@lje%BWlJO1t9@K={HHsoZa?L*;^QW&CNOuWzacoRLd*2t()=tCQQ zP3zEt>WBO2ke*=}bkxYiVUqj+pI17p8kr27kZ+aOIrhKub=B~&I6esCG8ZN5dw({Z zV_sMO7jM;QD1t8dgO9ubhR}eHZALaVKS3c1CLlJ%aUA7%gZZtrY*-Hn0+ z=cB}o%X(tuRa3HxFNRrHy6tCH7s{o|%#uDB{yvYl-X8qz((pLjSiy1H&XH7M`Oz0{ zNzCBRt_7a;60*7xW89yI^lb;eeAeBeO9fJ-JUG!se`bb;U$%#1PSPzXD%#d>)^2gw z#fd!Vz5)SHLegLIEXj^m zsnRgYShl53u5#?d^7a3sQOK=2jM9nygu2x4&*us=C3qwFp{ABmw$(PPm(ZUMM^3fR zjS*+!k5RH@ffUaY>7trV*^S2NLS9G(SuH~q?C%tk=Q29&t@yqSJ{Go@#h@_tE9+5-NlNz z3PgSy>oUv8&Ckw?b>CK(n`c4>>5?$%34c)J=Fw|Z6Y8Y7tb1YE3swj&^j*4o4?(ST zS7Q)E@I039wJQ12!W!(9YO&hS$ZCE&yZ#VZ!hg(C<5E?Xn0;o6a(6C%9}gt?0wynD z!aPbvSyQ?zM1WI9xx9MXvqOa@XT@n^h;_SJYD^y7d5|V=E119ip`?6EK9x91m2Wn=tm4$&f*sIJWx< z{#VcRiu6P-ja6@Ojbd`G67=|W5xUoOp(M}0GJyyOa{Kn~Xld%=?tDpEet5XYbwTA$ zQF-Ku>A!O0EZZ)*gj;p(cgl3vat+DJh3>xac9LW=797^s_U{MN<@G&FBfnzilrc`Y zt#N<4QY(uY_yKD{xL)oMo_IZ2+mMBd=S7jRVy1%&IdBO} zo~J!RY=}h^LW7adz32POaIhtb-^Y{v-$RBMfO@WiU;ee5 z$rDR)tc8#h$A3Nbe7;_2aojtZ%z2BG6uwDAi?u`~g3 z>Qm>l8do^;w>v1F-76ZuqO7F4!!QJ|J5{reok98Pi(Rz!z+H2KQmg}DgQT?dzr`A3 zaE%rjZx4q-{e1vKy&T+C^la&Pjw!C}&r0ezghh;0Q**(Ou`;+Yn)LFdj7at4^ENfR8m0z7HZ6RV9;y7MQ{8i#^fR&T>kAOW zz7k1Rfv~gi6XYJqcvEOe*5|c@sZ=Ru5yqe%Z%@QP(BB6fJOPG#9H!grm{-X=98d7# zzQ=!#=}V}NyMUD~VmhAoTP*4Sgwc}2Y)b{ZIal@<($HjDxc6E)qF`w$K4|RlrjcC7K9R#LbQNx z9&QlEV4r(+=yD8~G>>B*ah!F386g~ksy>jz$71hySI7j!tBq95+X4LS3rU=c_wSe7 z7{J;^#)PhYO8nz0%ypCHa{`t~#VIUk{p$x6xs)vrQU5{+wib$e;b|}pbA+6NPQ)d# z?ZI4WEIL0ZQ7XK9zvC>8(A~U1O^fu8$vaI2(N_hP+e zC}Fl_n<8YA-Gs{g6VUl2LAsh;K8lE@u2|6E%Dg3NcYi8B-#!;fR*JB*{1YSv=)=8A z$uvLdy97XV&R6$;Uu`z|SKEOkt47#a^$Bvt+W0_;v*c<3Ys6eaFpjX=y8|?Ke;H5N zo2Xv1Po~e8RWaxS^@+wl5n{Y{NRnoPch9n*oR$$V=rFc(i?i}4guD=OT`304k^O1M zg^n%(UAS7cVghQ3x0n6(oRlQG;LjXk(HtQ_2QC_{t8})P$5eSr6LIy;Pnb`*f|fl#7PdwX6{QsJ)>~ni$L_j}Vmefe$%i#ygmo5uKJ}5r+R=cF zCm$B2?|RzOfTH~gG$Qv^9i(gJ+#SvI1WIS<32yyAX}NB}y3Iq#Bui2N zeqNYQizo&$v_})o*sP&rL>ocH2n7)=-1(~ zYco%gr6Nu&g({V8C=Bkg_s9DARu)6Z$syL2l6)f@89ms18mLyrhvA%OWOKDxJ-koI zXu@}&6KM*D?K`rdVCfSH?dDVgyd3eVzzqmv%I?a(X6qy2xqi_SRC0qn>!xc?WJzI0PLf#3)9991uF<(F2uoauz@gL}qSrv0X8#DMMYGQ=ds)deD|0_wN}C`R+L~dodu$nN#`6*VmK&`3h!&FgRPgx{ z`o#c^i-V@gVQLj1^l;G-0z?o(~pI-*ZV3QCfAsB46h8X*h)w2 znncRu@MtndMpko{(TOBsE^_uBB* z`Bi$lt6oiG%cS7xw8#<4!2)rOegh15M z+1ar63>vmyBwL-{k9CehelQ^=C7s0J$Z-hnqla>wS8+#gHA?{usReG97x@qx--85I z2IY@B;H0ua5T`pr^2`K(L^>ywkqASqZ!}E%W93ua(_dVIu=1kfnW9mzp*EM5a$PW) zvFU1##5Xe7w}UBE;?bq~sHXOr-M?a)oQWIRno~|%r4wkW5b^BlI!>HYHqMnjl zu2kfm=Yw1cw395hF=Jm6osEj?T{Wfl{NLhHn_pvG5*z4{<=Fx%@qz)N zeawJ(icw*)bdBj48G^&lh-yc0ekP#p(z5;mtTtjY8pUXcDB+PTeV&(Q+_;W2|EJ?H z{cIW14P`*6ali&dI`LKMef4#-B$_z2d?AvP2q9+aC%-`J5B9on7^r#TGRn5Il*BV5yi{cy4x4X7LQ1ds3EQn5FsYD0M2*F;j;zvA^l&V=dwSDWTMzey}@(=+{0ap-_-yIAdH+kFLM}i-xbblLzx)=0E zPh*hy>kFzUg-}m~h2gi%j$ueaxGsOFN>%JVkA%4YXi)`z&C}tupWq#*o~tb|pY%=o zOKXyJ0`Swe#ZWDqj?*Q?*Mf=~8aToJ-NF$IrjmgiY)UOk$hgu?4e(UlNJlU9MHAS6 znJ&Qp^mbC;Ydg}{xbT4sS))-VDz{uSFW~FTG-Mh48S_|9f(F5139APjl)|ffr{j$YyH&yPR85QV*}YClhgocAjHc1Lu1O_H z4Dbc5`O(G=`AZSZngujoWn=-Z@er4W_WwzR*PX)wu0=|PQrZ)!=cU@Z%KwbMuC3^> zt&QR}Da$T8+$&DHAq~j#rhe7Ho*2s*eB42kT$#f5g1yQ)*&o;9p+K&(Ho$DtU_Adt zk_j&diB4>{;hR2+%l4Zs_S^56_3(8sGi^WGP#`*yZ)_PH25{>mQ;UGXo!ye5q1Jp0 zx-G$BIw`FtE8u*2@M5=iTf0e)m6tMl2x!wRI9qHee+S@s#Y2nSw~_=Z8?(GCQ_;#q5Yl0eqZbj@xZ3ggcu2MtMN z>$W*3(x~(KTdNdJ`+fL0l1%S&K7fTwz~~C)1N$XGbGcHt&2BIAN1k)n^NqHrC!XIN z(==Vvs#3K<&sv*{+3MZxtNqg8-MK@v)9XtN(_Ji(^zFOp$!$8P>2S3J5% zXE_fft>FN{=tlxd`r<4o(5%Gw)01X^{dG>1bRV1%p)Y3-pVxy1vr+fksh$XkYrcZ%PsBk?#w11#`uOG~vB-+|wSAkZ ze=daUD*{cZ9|ZYF#x0Yv#WojP4haI2r;UU4E}!$Q0W3PLz?_N-?WR9=eF$XINvqBF zdFN8mUwB87=&Zb+vwU89{t5T?8V=duGA!`8iC~U4m#dz0H3Ks}{Q1-}d&V&}D&m=ni(-PSsgB2n6HD|`s!#)snzO|EM@vN)R zt{WZNBh7|_ltIa~4{kmeqf1bf54#*kThKoZ)z~K(>qN!c=T%lxD&NM9njI0cCS&;a z8yjdA6QIoLz+%1peWTm2nIV^@T)PQzVZxA!oW#(#OfG|KQPh03`^g}M$&~5K$SVaW zf9TrnVrWkd6pfOeI4Ofm<&)|c+kK~Wcwd6F4i9nKICe<^d4NWhA6?Vum7iC$Q)m^= z$EchRp~-psK8FNX=@TqS z|CRx~ke^*ITzEKb3i(*&WM2(dwU4E(A=3@=%_1mjhzkK5K}$;Y=87^{IJR%QE9|l| z-Tr>AdqYn$^qarm%d%bdQx7iC#D-*MUUmOh@l$@a&Mew(s5x|s?Tfp_*MXhNdt~<~ z>{x*=ZwChI8^YJ0lqLFE+R7xgJF*$D-4`e|OF0&_89k;&EBG$ITGU;Xes~pg_f{uq zz!3Uk7}2)h{_!C2c{=Y8L!(hGodtUp2!rJhuiJ&m_FyE+1RRkt3g6Y5%erVgEr-h6 z<`PZU^-izIyWG%!e6D8(Jovos_4l*wV0I>-bzL&#{~@np@P5`qJUeXr}w^(hFN zDXEkASbDA6F(3dni(*4jxo(#b)!E@UC<5vA*d65k&&3LUikQd3 z6LC*s5d30rSePjkluv~LQWskr9PGld1Jfa;fg!cc;)L2O-KkYi_XExLCt>}mSA=%M zWVzNWAeH-Vq|cY(^N9%LeDkTk_I7*iZdx=4xegqz`;~30W%Mh2v%HEL@kYSf288Vd zNY4)pu>v~4LRmCU$8Tu#=!SUV=qNs~`071idu&DF*u$I33$c-M%bfrh@3D^T8FQdV z1$mBYB@7r7D_jo6A~A$YUbUPGPx24#a_*g2Ze`-ZHK#j4DisYs@Q1+n zmAgZld-aKK5lCjZm03{(%SEK|8U*=JRqX<}CfT5X%?w{uUv;}NR>--vuMOwQh-s!u z%E#37g$}v5IgA)RGO4JcHn=3>Baw7GU!-RSU3BO;6gi+TJ0FQvSUjAQD%As5+eHDb zXs%E8irK!4UHUG(8MC(AV)qrb=CSckfr0jxRAuiYiu~IsOtLogg7d5&H%g0 z4Wpp+^1!SVEFD6vhT0A`ObLKDnL!|nA@-ih@&wFWXi$bt;l5XKT3q$lIT(?eNPO-c zAwgO!6JozWSkL={C(+Li{>K-)7k{?~V3S9%Bn5;4Sh}QKF7pcX3o3{6vMe@7TGp>> z=mkhl+UWB|*nX6a=hM>DT@G|`l|}}XQM^C~71gTDZXp*dp?mSTq36xV*;!%ja9)*QPMcg_R5fqTGD2uhCFVz{0eLis>H0S5rZ|{HNYuDt# zmVv$MTYdn|I z(U%S1-tHsf<5dc`Z7o#9dmj{Kxo2zi^UZ$X)F^NV5-$%1BTTEUd#Ur;zncmYQ+}B^!SXzo^RQdq+L&2Qm0vL$??6Un2KGiRhFAtCACig@=2{!1 zG06ShHOvbm+SMik9?l-bFgloCSIKR7OUS)l>~MD}{~3EP2up$(5pcX5Hu^P!q$^UB zV>XbxOFqMVW;d03p9I3~|0@V0 zy83@jTF|99Tu@g~3g1x&EK-E7`BPE7AxUf{liwf}+P`M&r^P$832jVEyFs{@XV+vy z^;ES>ZF&`}$5rdi!2~W2))?oCz@ASEmxf8YJ;_3R#T2VyPz+^Nzr$MiG`?|)S#X8; z2BO3n*kByeH+R?N3IvIZ%$sGN%f!d<%`$Up)VKqMK-=;1J0drSbBC}}VebIF48ZC; zxVQK6h(CJ6sKY;eoHE-y6Rc8_AUI6KE>{->5H&Iy`11JUeT-%P4>HzO0-{LD&0!-V zgqlL(I)>=WttOF|Ie_{MLl-u1QEaU>SDrxEsX*@?=@%C;Q#dT3!BHDe1?M*yDcWSC zn92P@nUCbu&Zo9P08m@G+r{FXeE6I85}O(K2Homg2z+^vhIYte5LCWCQm|U*JI#aD zd{p56ZuI#9xq2D;CFXJm#pCuc51;iN9QPH~d|i*l>aYGeezNDL5GQ%HMf^5~Xi4{S zh}iI;I3%e-1A@pWn>8-qPsL2{(6AC41<5<;oAVXaBZdAlB_+8+Q1%!3$+a4f3iP`M zYlkQd*2b^iEu&6PuGb>@BJTjN5&#a_4~}q8TH~eX6a4yDhlz8-$tP)E#nRU`IIqYI z*4q_LI|BdY`R~QQOT8l^YoQ7JY!1gzhDSHpXoea4aczOU>Tx~*6TRAQdXr88~~0>GXFu&QX_BlWK3yqj&qAf;!M>wpC`N|;wC(6h8W^j zwO??0!_exV$m3JK-z#J;4nZMl`a@^fFZIW(+(xH!D|s$*F6$ppM`HTrN5p3u54S^= zR72dR)eE;8qpf|#43@mF?^-nmaFjFm6)|PdxcNQu&5w{zHQ6RpejBSTX(kQN@ks>o zjOnw2@|=$+Jgz2nluO@n_4$aq&&wzyua~W+yOfBUd7sND$I#EjgRS%PJ^38JbxSIE zyZ zEu>3GO44Xl@7^3AJW%78ebZQqx;~~ReNd;a_fKWQZj7a0AGoUsNnx0p-&n|}B^*>e+37M&`6dXxe z@w!ez4A^rffzY2~C~&HDKAXb~)(D3GZkgmJ zVD;$ZR)pw4xDd)|FBu`MSZ3b#Ss!@WO`J`>r$!?qeOjUClJ;3Ot1x+hK}y4^Cm0HM~}MG zHj6qOtd|0^*?1B#BiHJO4aj6~_j&m-05n49^23T%B%iL;7nv(;zn^}^VO5e4eo%*F z4K~U(xl?~b1BhS3u)+&}zhy-ewOgnhl}uzP3LqT^h|9J(m^L~y2%0VMDNrz`Bo8;l zohxO0Ni;$bwttU7+;87U1|;NpVs-g+Gkq>2XI&1Ig06D@8xChJTmzlM@vgFuelO3W zC#yztr5t+aw&3W7;&vGx@DXF$*Q;14GUq2{J;DG^;MO~SGaj%Sxy;^JvBiN9(@57T z(R_*+k>jh0DZwGYBuhMRKY1{aoHU=rrWPNa|%vOChCm zU#KO8R}@NEDc{k#Nrg5tTy}c%DY9gCbi3Lwm&}xgo_t4+J5(k1Cvq3|=ydB3-hNjz@- zsjO`11#&1PVsD6NHO7Y$S5>6{H(e08$J#)^9SPECb6HVHB^Ip*QSI?!$Q%h{fY3!n zz$2%T(F5K_9ufNnX~19vnWwws$%gtK-(Q5CZy)y>n{gfqALo~-Yt|!lEXOauy9OzP zPp3|GexW?7Ej*nzxszrW-=EvnUlZV-a^$5vpmB4;IdAq&{V@oBk0)ROpzEMzMy?u0 z6SzwxP?O1}!+2zOBi!V`vH0f_PqLv~V~2~r!*8R2%@7(vWUka`B6A1s;^CNFR%D@4 z4?b4(`AE19qHkqYTk1c=ackJ8hu7l_*aVD$>yQvp=bqSKfxMx8T+k~1cqr%}C?zFG z#kR8zV_9QhjCguTL&+r=wis*0kOM{7ppr!(dkAy=i+d1sV1OUOeeqsv;V7?O7rswB z{~i0$C*#sp>a9$IVH9uAr_U9G&8AhN~mLTlqc^K|sQY$ZhDqI^6QVZX%80!%(C9HA>K_kEigqO{R&N zOo!m)K$8S@v;9zk7ZQX3Aj70|=S8>r$;6Wf>45SF+=je;oe~+GQq?l$BDpkn+ZzH-yN&Kp6p}aGJ45Hic>63fgjB)Zt{+qCWj9N35)fVUvYou}Z&dBjf4Gvqgr$>nfIM z*G9EU?>QLP28It=1__U&`rBBlpsi((Ka{)I@p41YP{JS^9KsZAJH7ldV3d1dV#7%AZtLtAsE7-cOS`!mO|w zB}!yt7?!_BeAylBpJ`{Wx`XLY&%|AuT7#GU-3cDym9M%#5h*W~*iLWJoz`hK#DP}6Le$)>8A#tJBEqo)x783y@Q zjqwnh^>V%Lc79Zz9c_!YIE&TG`f#UJWefRT9H9QnH+h>k9cTT^>Sm7z>Iw`KDFP&n z0AU_Xu{7taXOV@n**agqas}0BFhYi6-Y3)&O2vKQ2x4RDmneL0bnAixA$^16xtREP z$GbB_dU3QM1Y9;1j-*@rrCL**4Xt|f*w=P8&&h9^6iVdFH3$SKux~E@S;AlxlO0*Q zYeD%Tq`v2FkAE#}6r3p2AjqnI^40H@e{xfraoZk@RBmrgf|JYOQtgcY`&dsGK%eQX zHHjm9%-#C^K*>${0^BKb@Dw0ajq_Z}&|&K|SYkXzJgiE9e*&yrNI*2t)&uiQA;vGc zm7&K|7itv-D~Sq9%sX|_SVlH|iZx10Ib*eD4?&ubwr$63KE)%+_d!^CF+K%9X>>2D z{KpeBrw%xm1611LMoy>iIeKbnyMScXk^dugrg&dxAqB$uU<9%Hmu0Jr*EPdmFePvp z{(SWcv?k#Yjeeis8S%x;qd}I|mMh>zmYzv)I^<~jV;Lg1FMK?y%$48IHZ7RJ^%jJ% z#RU}QwMYtr4h|6dmz$?D%6VFn8|_1tpmLF9O@*SwAy&Fk3Bdyex=FG{6)&s{<>e`& zCF569Mg*Gexq|LTS`UP~P!$LHe63B&4=p&}yh#Pn7tEPR!(G*pxdqLm-*6X@&+J?q$IUK_iZ^+f&zXT%ZtqH?7V&&`ktF6X53BddT>26mvCO@?#(L- zG>wJlsKzmGD1v|oOwPgkRY?X^dN74=Kin1ovXkkHk8+rgk ztF!;Wxg0|M_uoldMyYB>p15&kTfnZj|4?&t8eQ_ocqsjFE&~?L zsPt=s<-V|R&(B{%)sZ5liDGYwhmso-%4n{5b7yY|J+#5Of}5(v+iM9bx(fI(d8e_W zhSq49HW-8T-?}hq?YuHSGjA2!uX5bxA58E^=T%l8W+Nd)m|TqXu*DTAYXRRpLajWv}7tbkx2yiMO^VJ$6lea4iVHR*FnVkCi|Z-czj3DMyytt)`UsD9q4jCryY!} z)a76f`-oH89jYUD4iYfmQ)6hf8Xz8FKC3WiR1b(=%_A;N`K09Dg<+keCE^uH^m0Bo&YTJZYor4 zmR|_{?w4cAo)!B;D-Dj7ZI$~g(Cax!tQwpamJAE=!zFTI3=yH-Sc8 z7h%es?aWq5*64C0(gc2bK=`-}%rQ?2z?J`Ht}aH3SEe z9=YsjTaNwDK8uAB4c>!-HY--g>Y~`yj}pi?YhZ%?guS6saoB#XmZFuMyjP4TP5+=E zje!-gxG451rUdXdh!_@+Yv&g?wKU=U$=bs- z9avt)9jy^s4j>ZC-UOsK(>n*+H21uPi1P&8)UctO)WoJS;FAQ-1?gJ#>%wSP^_MX~nwlJ}@3hI|I7si#V69F*$Vak22V8VH}|PTHLe(JHLQ9vEOV3Y1lS z&jF#njgAY2C(Fyc*Qq>9oX7ROx>d6wvg2Uu)#N@%1Y0k?k7WorMaLKbxDph$RhGr; zyXVT1XIdQncxd>iL$pd7H3Gq|(<4LnPB1|ri_eBI$=T($YI`W-2xNAo!QZz9^eP@0 ziO~2CA~EVlf471>SW0B+J=DLuUYxkih5_8g*Q<__-9iMZKsvD*Xb=fbWda=Jn+#ux z&gQjk26Z2-~WGzHfudDY7kkUZZrNp3AmJnp&-|a@5M`l{RZ4CC-)JA|ND8s6gE}-Vc3h z`mGuxO{qv;#dC)dDy|?BrFL}0_Z1U&-(h@hV`G0AS%zX$dwU}#r(7AI?AGQ^9j z`@J&v+lplfvz`34=zbtVa^q%B7f&0Q^Q$ZGS2DpV5K)3%AH74LHr_r3+ zo3keJE=8H-oqf^(2rqM{D{N7k>PX6BCGz)&CIHBXNtsE9gXK``geY9wx2ymb#=dZk zUoMORu4pU~!TSS31s?Fgt$ULTT@XopgZ#>yRSRU(f=7-D(J!4E;SSH!R|LE(bRT9i zL~LmOCawlyg*Ei)eR|ASFh>W(w%#3!!S`H3LT}@J?cYHK}}2J>3t4MioSMSO;f9umTNbCRn1Sc?QHRWez>SQY1zx%So8bv@eqSk zcY^nJwW8^dR;TCv`S#F9g0t!2y2wmkk3zPYKX|FewI^};8Ke!OY{HIrePJO27Y~!J z%>^|{qx4^A8R&ZmEl`O9nQck`oCC??qMh%XXt z3;@U2#pv>+D#qoOq}a=hYTU#Bgd;`qlMHYYr$d#i>pQ(3M+CK69161T|LMOzfZHCm zr#Wyl4R@!$pyy#NCl~dakNOem%Pg}y+0T;i7oY(8TmUuOA;~hM6uzLLf^1B%g+;w_ zs+gx++(O8MthD7;`{nrP=;)=k%hTOCxbGYm)M~cNNxomUs!}ag8Fp+^dVagSy#cf| z?XxER%k1r7mY+<}n3<{R+ukOoI1`-=E+;85;dZr2y!eI`(qqyRy4ugFeFSnmz*&z{ zB(KdNEV!!*PnM+aqoulC*+{Sfo&M=a{fo;6(ijNGNW47jLz4bG!T;KBz0&CQaCN>z zAK|mR6#P2?{y%qv@KZuokhd)7_A@8}p0xh^YCTSvCA9#yKpyuibFy|_oRiMS6XtOa zL4a0Q|<%*sY!;2FvRu6#i?ej{6i;`rxmt z&_59H5SWc5Q1vVz;c+k+^@qA&9}a(1E^hClWxV?#YQegfq-`@*I$Y&-Y=twT+((Dn z>03;rR<3ZT6HhKH$9>Eqx}I|Died&h$G*A|hr{!-Pl+)DP`3sZxO zo!xF4IF(A{*OzBg5KdZTaVtl}^F{#D6}&`|G8F^UxVv!q=w;RS$p9|+*9)UhLgi%R zxqrvf-md!k)8WB?zZ3sp$YeH-=P`8LWV3ciBN~>h=YCLLH$L6+g+eYp6wQLtspI}{ z+A^U;9YZw#OY>DOjG>z*|6LD~5Eajhkd(Kt?o+Lzu{-R(fbg|_4nvK}}w z$)sF;GeqfxW~*WL_xDm+t)M%oDrgc)te)7)si#$=Fo#Qtz+9BJ&6Xq1D=rEgKn$7`<6rs6PMgnl?XiFd%#yJpDt%!#%Dc=2s^&M+;&-m ze*I)L&QJAvyt(iF$AH3W1{S<`lxSESo`IR7BQmQ90H5o7^I0!^02Fn)-e8qMoIzpa z!Op$f{a&uF|BtMk)|*+Ky^R(fwLH?=RS_m(7L09YYqQ(a;|Ez(48kdt`${d8#>OjP0m+ zkevkng04%(d@s$B6jxl~5xxcOFr;&1F*Ii%Sxj15+1-Re3b)Hif=>a%kN?e=Se$UJ zVIMT^+~#Qcv7m`T>+HYhO(dVQOFG$KYjYQ9@~&miWefT~y0}>M+Kn=syx}_2H9AT$ zNp#w!$zV$wg;k{d>vg+Ug#TJ!EWZ6|IelsRFK{4T_*yyZJO6y&AKzML&071no_XG9Kl|O!`@Fy3%&eJo^Z3=`*a544=SjkYp@bxo z*vBZouJa=STjF&SNAZfx8JIT3pf_mt5lTNNJcubU-hSoIW80#u{sr#0Ge@^4D(7UJ zy+tX1YD+b9)E|Df;mg={gZCxFxqKc1u=6Tj9ZWU;`ACGDVc!LtD3tKctV2hpN3)GI zdf_%woKB%;x)%p4de!sgQ_8_XY5m!cD)s#AP+w!lFwy<)Azoy39ovR*XyQTj(RUXMYFlZN!Zc@=iqi6&f!yHvttG+IY^iR%p%ce6ShGJTOBx(DvI^W(=x zSumIQi-_M?!U@)8msxZgq|^|5l>@pJl1T^ktz45f?Ox{H%+~4?(JS72q@Z(^ZBg`6 zJsmc$$Z4u*$3sQohiVM&6(F}r%6afa@>Q3u;-7st$(VK#)j?cd7BnoVb+F<#+E$%n z-1sQamsCviYb_f3+?yU{WnC2(+Fi;1QYIB{pX%nESZ_RmxreRMw>J&yIiMA-)=ai& zag``H@2yTl*89en+9i88BgL2HVQuugVp9$`WW1+4NiUIKm2`N@+?@X5B?obG=;{;R zy&gpltEtE+uLm=FCreyXv9mTNGwO-|Ph`hz3V3HM;emO5xxvt5**GV={sLBzOLz8t zW$rtkS_vMzXU9ptzH?LvOtxrXbbYNAEIyKpxkjpKTZ0GhkJI#S2sa)o;cUKS$2t@@ z`YxoQHZWNElxcAxcxQtk(=I{oihJ0|@NmHmoq>F|iHLb!@}x+yU2+Z5N!Ip8OmO{8 zx4So0S@PA2%Fpu)+y#|#Q}SGiM$YksgHL=%f=i|Lo0b&1TtDY}+uNO4YaRzVRA&lhBMkL*?W+{fjuZnk#A7)|_%&5fyB;-@>d ztzD|wOJmIlWhe3|qwj9s(ygMkhf)c(>7|9DXJ}V0rwc~2Qv)Rn>oP|**1W2t}@AaP|xef-a+cLb+Rr2e(jl%ZJR#<|?DKXecb z=)6W@$3;aK&qV<&7wb=3wOrwmO7|Bx3BSy(du#k8w)&cTF(0m(cXvjQEjN7eW>sN0 z+-bvIrVrLES1~N?EvTU@xX}Ilzu5U60DSC!2F{5`Q8hAVM?ZVo)lLwi5!?D z#VLe|&!z5Sb=h{>DxlUlMKR=MMw^wwd6l}bqAZJq%8H5{r`)}8VX{IKs!0v8`skgDJ}mc-{E$Q%*>^4`%O}=oAkl=UYtBKc%a$wXhkU^(HLgP{dxE_HSWl{ z!P05LRhtVm!&_0)g8i~b1@#TNZo;3gB08snqc^j*pSsHB)Jf0H;WOFGQxd zl*z>Qn9c<5klkEaDtw=P@|xaYiT&;+Q(TjB-gT$kxTx+8>Y}q#a6)!7oYR)l6EaHP zK0bqQik%~l3fKx!9Od*D$|(aU>q)kgV&6x&g-`Zc{+m~N$$pyT5`%hRCA6zw^xRfg z45RBAOf%D14X#{g9>010C@)#~mlO1<4tO)496KhqWa+2%%WkSTBwF#G{$3FIiS4}q z_uApjLq@Z;D7CTUvk~J6)A8m$SHC4J!|LNwvL$DbyiVPYjYUSs_%1~-i5a_cRUGx( zdsX5tD`fdW7Xf=czoz_*skZM6Ai5H1uP{MUfBmbk;%rRvN|>%5i?}T=z52K#(ES}T z-jD~YL0F;jTyB|+#KOcj7pAZyJ%2Rm;9gZ7=9|oTu24M!v#A`@UzX{n*(#MZ=DIl} zB|-+8F-~gvt!Eg&3yv zfCKbED3A&004NKpKkNn{g??rbh3*9bhK7ilD+v`Hcmaxvh=@G8EXeU$suHt}{P3_S zT-IyQ9t&UrEPw^D02aUkSOD|Dk(!4ht;-Zy^O@0C!XpG25G2eQF@M91u-}-`E&vOl zjD<`kP)KKHc#hq6lYb^K(aAPDcft6>5k!3|DXVT;=m;w5IdHB7(b-C0yun!dJ=nv!e|uIVR2k!i)QVa?$N|Cn;q zi>oh*I^3~O!YZHpSyk_uruhaW#SrKZZZ4=8W;MQUT!uC6=Y?narcPrlI&K1wz+dn@yC?cLOypN_l9eb{L2 zDqh`0Y6(4sI2d|*H(%kNTFJ-u8&w>8_lxSOuN$%p-uG=JoF4S4x@5>O;Ek`n>!ah* zeK=)M38BNNEPSi4kP}@Aih@s~-FALZ7qNcr-AMIj~C3N;-_z?k3Sry7} zX%X6W97%}wSm#~EO=#Ra{|TY9*X=o<_hHXO1mDs|y)pl{P;ZSd5%_&Fe$%(P9xwB` zpSluGYw7!Cd~{rX{SYm_8DV2^wU8S?Q)o}TF4n!S>>@i>`bG;hros21q_$OBI(x@= zv_C?fnC9#!OE=tV!(5@*@y2vo_+4$qiN$ zrluz3Ajk{&x=if=_gNq8cdPcBfU$+uN)UjufxM_>6M87%CX_rv*#gTIccETk!O{Z+ zP&=t!L4X@D6HmBkXH&t=i)l(`lKtsEv%zQDf)m6DqS6Ba6sU#!e*AaCZ5ebg76on) zn{CJxV7EQs0)S{OZ7m&O4~xuTh6A={7N&GqDupsN;M2e}!h|E53A@Z@GhGZCO$0$u z2ZO&zRA zh6m5H5s5#>gXaMI-SsjVWU4>J_<9fRso@Yje*nskPKPT8_wD9e271$h8Rcwg1{adP zCl2fFiN<5GXq>mFHmHro5Iw!fUSxtd1Yrqe9Ye%FyL>CeASPTJUvC@^#Nt4Ny1Myp H3&g(wpQ5r- literal 0 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 literal 0 Hc$@S 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 z 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 ANB{r; literal 0 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