From 3ddcb2dc9b68a5a389b12e83eb625fc8fa9fdbab 2009-09-13 22:06:04
From: Brian Granger <ellisonbg@gmail.com>
Date: 2009-09-13 22:06:04
Subject: [PATCH] Work on startup related things.

* Extension loading works.
* Execution of code from user config file works.
* Reworked logging in application.py and ipapp.py to use logging.
* Updated config files.
* Changed how display_banner is handled and implemented show_banner.
* Implemented safe_execfile_ipy to run .ipy files using runlines.

---

diff --git a/IPython/config/profile/ipython_config_math.py b/IPython/config/profile/ipython_config_math.py
index f18448e..2238227 100644
--- a/IPython/config/profile/ipython_config_math.py
+++ b/IPython/config/profile/ipython_config_math.py
@@ -1,9 +1,8 @@
 from ipython_config import *
 
-EXECUTE.extend([
+Global.exec_lines.extend([
     'import cmath',
     'from math import *',
     'print "*** math functions available globally, cmath as a module"'
-
 ])
 
diff --git a/IPython/config/profile/ipython_config_numeric.py b/IPython/config/profile/ipython_config_numeric.py
index 70a731e..28031fa 100644
--- a/IPython/config/profile/ipython_config_numeric.py
+++ b/IPython/config/profile/ipython_config_numeric.py
@@ -1,6 +1,6 @@
 from ipython_config import *
 
-EXECUTE.extend([
+Global.exec_lines.extend([
     'import numpy',
     'import scipy',
     'import numpy as np',
diff --git a/IPython/config/profile/ipython_config_pylab.py b/IPython/config/profile/ipython_config_pylab.py
index 705b665..edf6c5d 100644
--- a/IPython/config/profile/ipython_config_pylab.py
+++ b/IPython/config/profile/ipython_config_pylab.py
@@ -1,7 +1,8 @@
 from ipython_config_numeric import *
 
-EXECUTE.extend([
-
-
+Global.exec_lines.extend([
+    'import matplotlib',
+    'from matplotlib import pyplot as plt',
+    'from matplotlib.pyplot import *'
 ])
 
diff --git a/IPython/config/profile/ipython_config_pysh.py b/IPython/config/profile/ipython_config_pysh.py
index 1720b87..3fe3f95 100644
--- a/IPython/config/profile/ipython_config_pysh.py
+++ b/IPython/config/profile/ipython_config_pysh.py
@@ -1,15 +1,13 @@
 from ipython_config import *
 
-EXECUTE.insert(0, 'from IPython.extensions.InterpreterExec import *')
+InteractiveShell.prompt_in2 = '\C_LightGreen\u@\h\C_LightBlue[\C_LightCyan\Y1\C_LightBlue]\C_Green|\#> '
+InteractiveShell.prompt_in2 = '\C_Green|\C_LightGreen\D\C_Green> '
+InteractiveShell.prompt_out = '<\#> '
 
-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 = '<\#> '
+InteractiveShell.prompts_pad_left = True
 
-PROMPTS_PAD_LEFT = True
+InteractiveShell.separate_in = ''
+InteractiveShell.separate_out = ''
+InteractiveShell.separate_out2 = ''
 
-SEPARATE_IN = 0
-SEPARATE_OUT = 0
-SEPARATE_OUT2 = 0
-
-MULTI_LINE_SPECIALS = True
\ No newline at end of file
+PrefilterManager.multi_line_specials = True
\ No newline at end of file
diff --git a/IPython/config/profile/ipython_config_sympy.py b/IPython/config/profile/ipython_config_sympy.py
index a36b833..2bf5bef 100644
--- a/IPython/config/profile/ipython_config_sympy.py
+++ b/IPython/config/profile/ipython_config_sympy.py
@@ -1,9 +1,9 @@
 from ipython_config import *
 
-EXECUTE.extend([
-    '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')'
+Global.exec_lines.extend([
+    "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')"
 ])
diff --git a/IPython/core/alias.py b/IPython/core/alias.py
index 60cabc1..3dbb8cd 100644
--- a/IPython/core/alias.py
+++ b/IPython/core/alias.py
@@ -112,11 +112,9 @@ class AliasManager(Component):
 
     @auto_attr
     def shell(self):
-        shell = Component.get_instances(
+        return Component.get_instances(
             root=self.root,
-            klass='IPython.core.iplib.InteractiveShell'
-        )[0]
-        return shell
+            klass='IPython.core.iplib.InteractiveShell')[0]
 
     def __contains__(self, name):
         if name in self.alias_table:
diff --git a/IPython/core/application.py b/IPython/core/application.py
index 882064b..90b2f40 100644
--- a/IPython/core/application.py
+++ b/IPython/core/application.py
@@ -23,6 +23,7 @@ Notes
 # Imports
 #-----------------------------------------------------------------------------
 
+import logging
 import os
 import sys
 import traceback
@@ -53,8 +54,8 @@ class IPythonArgParseConfigLoader(ArgParseConfigLoader):
             help='The string name of the ipython profile to be used.',
             default=NoConfigDefault,
             metavar='Global.profile')
-        self.parser.add_argument('-debug',dest="Global.debug",action='store_true',
-            help='Debug the application startup process.',
+        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)
         self.parser.add_argument('-config_file',dest='Global.config_file',type=str,
             help='Set the config file name to override default.',
@@ -72,10 +73,26 @@ class Application(object):
 
     config_file_name = 'ipython_config.py'
     name = 'ipython'
-    debug = False
 
     def __init__(self):
-        pass
+        self.init_logger()
+
+    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(logging.WARN)
+        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."""
@@ -103,6 +120,8 @@ class Application(object):
         """Create defaults that can't be set elsewhere."""
         self.default_config = Config()
         self.default_config.Global.ipythondir = get_ipython_dir()
+        self.log.debug('Default config loaded:')
+        self.log.debug(repr(self.default_config))
 
     def create_command_line_config(self):
         """Create and return a command line config loader."""
@@ -120,12 +139,14 @@ class Application(object):
 
         loader = self.create_command_line_config()
         self.command_line_config = loader.load_config()
+
         try:
-            self.debug = self.command_line_config.Global.debug
+            self.log_level = self.command_line_config.Global.log_level
         except AttributeError:
-            pass # use class default
-        self.log("Default config loaded:", self.default_config)
-        self.log("Command line config loaded:", self.command_line_config)
+            pass # Use existing value which is set in Application.init_logger.
+
+        self.log.debug("Command line config loaded:")
+        self.log.debug(repr(self.command_line_config))
 
     def post_load_command_line_config(self):
         """Do actions just after loading the command line config."""
@@ -148,7 +169,7 @@ class Application(object):
         sys.path.append(os.path.abspath(self.ipythondir))
         if not os.path.isdir(self.ipythondir):
             os.makedirs(self.ipythondir, mode = 0777)
-        self.log("IPYTHONDIR set to: %s" % self.ipythondir)
+        self.log.debug("IPYTHONDIR set to: %s" % self.ipythondir)
 
     def find_config_file_name(self):
         """Find the config file name for this application.
@@ -187,18 +208,28 @@ class Application(object):
         ``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:
-            self.log("Config file not found, skipping: %s" % \
-                     self.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()
         else:
-            self.log("Config file loaded: %s" % loader.full_filename,
-                     self.file_config)
+            self.log.debug("Config file loaded: <%s>" % loader.full_filename)
+            self.log.debug(repr(self.file_config))
+        # We need to keeep self.log_level updated.
+        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."""
@@ -211,7 +242,8 @@ class Application(object):
         config._merge(self.file_config)
         config._merge(self.command_line_config)
         self.master_config = config
-        self.log("Master config created:", self.master_config)
+        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."""
@@ -219,7 +251,7 @@ class Application(object):
 
     def construct(self):
         """Construct the main components that make up this app."""
-        self.log("Constructing components for application...")
+        self.log.debug("Constructing components for application")
 
     def post_construct(self):
         """Do actions after construct, but before starting the app."""
@@ -227,7 +259,7 @@ class Application(object):
 
     def start_app(self):
         """Actually start the app."""
-        self.log("Starting application...")
+        self.log.debug("Starting application")
 
     #-------------------------------------------------------------------------
     # Utility methods
@@ -235,11 +267,11 @@ class Application(object):
 
     def abort(self):
         """Abort the starting of the application."""
-        print "Aborting application: ", self.name
+        self.log.critical("Aborting application: %s" % self.name, exc_info=True)
         sys.exit(1)
 
     def exit(self):
-        print "Exiting application: ", self.name
+        self.log.critical("Aborting application: %s" % self.name)
         sys.exit(1)
 
     def attempt(self, func, action='abort'):
@@ -249,17 +281,7 @@ class Application(object):
             self.exit()
         except:
             if action == 'abort':
-                self.print_traceback()
                 self.abort()
             elif action == 'exit':
                 self.exit()
-
-    def print_traceback(self):
-        print "Error in appliction startup: ", self.name
-        print
-        traceback.print_exc()
-
-    def log(self, *args):
-        if self.debug:
-            for arg in args:
-                print "[%s] %s" % (self.name, arg)        
\ No newline at end of file
+      
\ No newline at end of file
diff --git a/IPython/core/builtin_trap.py b/IPython/core/builtin_trap.py
index a05fc49..f528dbb 100644
--- a/IPython/core/builtin_trap.py
+++ b/IPython/core/builtin_trap.py
@@ -46,11 +46,9 @@ class BuiltinTrap(Component):
 
     @auto_attr
     def shell(self):
-        shell = Component.get_instances(
+        return Component.get_instances(
             root=self.root,
-            klass='IPython.core.iplib.InteractiveShell'
-        )[0]
-        return shell
+            klass='IPython.core.iplib.InteractiveShell')[0]
 
     def __enter__(self):
         if self._nested_level == 0:
diff --git a/IPython/core/display_trap.py b/IPython/core/display_trap.py
index ff41a81..2f9fc3b 100644
--- a/IPython/core/display_trap.py
+++ b/IPython/core/display_trap.py
@@ -48,11 +48,9 @@ class DisplayTrap(Component):
 
     @auto_attr
     def shell(self):
-        shell = Component.get_instances(
+        return Component.get_instances(
             root=self.root,
-            klass='IPython.core.iplib.InteractiveShell'
-        )[0]
-        return shell
+            klass='IPython.core.iplib.InteractiveShell')[0]
 
     def __enter__(self):
         if self._nested_level == 0:
diff --git a/IPython/core/embed.py b/IPython/core/embed.py
index bed686e..421c30c 100644
--- a/IPython/core/embed.py
+++ b/IPython/core/embed.py
@@ -64,10 +64,13 @@ class InteractiveShellEmbed(InteractiveShell):
     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, ipythondir=None, usage=None,
                  user_ns=None, user_global_ns=None,
-                 banner1=None, banner2=None,
+                 banner1=None, banner2=None, display_banner=None,
                  custom_exceptions=((),None), exit_msg=''):
 
         self.save_sys_ipcompleter()
@@ -75,7 +78,7 @@ class InteractiveShellEmbed(InteractiveShell):
         super(InteractiveShellEmbed,self).__init__(
             parent=parent, config=config, ipythondir=ipythondir, usage=usage, 
             user_ns=user_ns, user_global_ns=user_global_ns,
-            banner1=banner1, banner2=banner2,
+            banner1=banner1, banner2=banner2, display_banner=display_banner,
             custom_exceptions=custom_exceptions)
 
         self.exit_msg = exit_msg
@@ -148,23 +151,24 @@ class InteractiveShellEmbed(InteractiveShell):
         if self.has_readline:
             self.set_completer()
 
-        if self.banner and header:
-            format = '%s\n%s\n'
-        else:
-            format = '%s%s\n'
-        banner =  format % (self.banner,header)
+        # 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(banner, local_ns, global_ns,
-                            stack_depth=stack_depth)
+        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,header='',local_ns=None,global_ns=None,stack_depth=0):
+    def mainloop(self, local_ns=None, global_ns=None, stack_depth=0,
+                 display_banner=None):
         """Embeds IPython into a running python program.
 
         Input:
@@ -221,7 +225,7 @@ class InteractiveShellEmbed(InteractiveShell):
         self.set_completer_frame()
 
         with nested(self.builtin_trap, self.display_trap):
-            self.interact(header)
+            self.interact(display_banner=display_banner)
         
             # now, purge out the user namespace from anything we might have added
             # from the caller's local namespace
@@ -242,7 +246,7 @@ _embedded_shell = None
 
 
 def embed(header='', config=None, usage=None, banner1=None, banner2=None,
-          exit_msg=''):
+          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`
@@ -264,9 +268,13 @@ def embed(header='', config=None, usage=None, banner1=None, banner2=None,
     """
     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, exit_msg=exit_msg)
+        _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/ipapp.py b/IPython/core/ipapp.py
index 2c4ace6..a61abcf 100644
--- a/IPython/core/ipapp.py
+++ b/IPython/core/ipapp.py
@@ -23,6 +23,7 @@ Notes
 # Imports
 #-----------------------------------------------------------------------------
 
+import logging
 import os
 import sys
 import warnings
@@ -33,11 +34,12 @@ from IPython.core.iplib import InteractiveShell
 from IPython.config.loader import (
     NoConfigDefault, 
     Config,
+    ConfigError,
     PyFileConfigLoader
 )
 
 from IPython.utils.ipstruct import Struct
-from IPython.utils.genutils import get_ipython_dir
+from IPython.utils.genutils import filefind, get_ipython_dir
 
 #-----------------------------------------------------------------------------
 # Utilities and helpers
@@ -95,11 +97,11 @@ cl_args = (
         help='Turn off auto editing of files with syntax errors.')
     ),
     (('-banner',), dict(
-        action='store_true', dest='InteractiveShell.display_banner', default=NoConfigDefault,
+        action='store_true', dest='Global.display_banner', default=NoConfigDefault,
         help='Display a banner upon starting IPython.')
     ),
     (('-nobanner',), dict(
-        action='store_false', dest='InteractiveShell.display_banner', default=NoConfigDefault,
+        action='store_false', dest='Global.display_banner', default=NoConfigDefault,
         help="Don't display a banner upon starting IPython.")
     ),
     (('-c',), dict(
@@ -244,6 +246,11 @@ cl_args = (
         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')
+    ),
     # These are only here to get the proper deprecation warnings
     (('-pylab','-wthread','-qthread','-q4thread','-gthread'), dict(
         action='store_true', dest='Global.threaded_shell', default=NoConfigDefault,
@@ -263,6 +270,14 @@ class IPythonApp(Application):
     name = 'ipython'
     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
+        # Let the parent class set the default, but each time log_level
+        # changes from config, we need to update self.log_level as that is
+        # what updates the actual log level in self.log.
+        self.default_config.Global.log_level = self.log_level
+
     def create_command_line_config(self):
         """Create and return a command line config loader."""
         return IPythonAppCLConfigLoader(
@@ -286,7 +301,12 @@ class IPythonApp(Application):
         super(IPythonApp, self).load_file_config()
 
     def post_load_file_config(self):
-        """Logic goes here."""
+        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
@@ -318,17 +338,90 @@ class IPythonApp(Application):
         # I am a little hesitant to put these into InteractiveShell itself.
         # But that might be the place for them
         sys.path.insert(0, '')
-        # add personal ipythondir to sys.path so that users can put things in
-        # there for customization
-        sys.path.append(os.path.abspath(self.ipythondir))
-        
+
         # 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."""
+        # 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 self.master_config.Global.display_banner:
+            self.shell.show_banner()
+
+        # Make sure there is a space below the banner.
+        if self.log_level <= logging.INFO: print
+
+        self._load_extensions()
+        self._run_exec_lines()
+        self._run_exec_files()
+
+    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 _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:
+                    full_filename = filefind(fname, ['.', self.ipythondir])
+                    if os.path.isfile(full_filename):
+                        if full_filename.endswith('.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)
+        except:
+            self.log.warn("Unknown error in handling Global.exec_files:")
+            self.shell.showtraceback()
+
     def start_app(self):
+        self.log.debug("Starting IPython's mainloop...")
         self.shell.mainloop()
 
 
diff --git a/IPython/core/iplib.py b/IPython/core/iplib.py
index 811c958..58c526f 100644
--- a/IPython/core/iplib.py
+++ b/IPython/core/iplib.py
@@ -55,8 +55,9 @@ 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.strdispatch import StrDispatch
 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")
@@ -185,7 +186,6 @@ class InteractiveShell(Component, Magic):
     autoedit_syntax = CBool(False, config=True)
     autoindent = CBool(True, config=True)
     automagic = CBool(True, config=True)
-    display_banner = CBool(True, config=True)
     banner = Str('')
     banner1 = Str(default_banner, config=True)
     banner2 = Str('', config=True)
@@ -197,6 +197,12 @@ class InteractiveShell(Component, Magic):
     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)
@@ -262,7 +268,7 @@ class InteractiveShell(Component, Magic):
 
     def __init__(self, parent=None, config=None, ipythondir=None, usage=None,
                  user_ns=None, user_global_ns=None,
-                 banner1=None, banner2=None,
+                 banner1=None, banner2=None, display_banner=None,
                  custom_exceptions=((),None)):
 
         # This is where traitlets with a config_key argument are updated
@@ -274,7 +280,7 @@ class InteractiveShell(Component, Magic):
         self.init_instance_attrs()
         self.init_term_title()
         self.init_usage(usage)
-        self.init_banner(banner1, banner2)
+        self.init_banner(banner1, banner2, display_banner)
 
         # Create namespaces (user_ns, user_global_ns, etc.)
         self.init_create_namespaces(user_ns, user_global_ns)
@@ -331,6 +337,12 @@ class InteractiveShell(Component, Magic):
     def _ipythondir_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.ipythondir, 'extensions')
 
     @property
     def usable_screen_length(self):
@@ -429,22 +441,6 @@ class InteractiveShell(Component, Magic):
         else:
             self.usage = usage
 
-    def init_banner(self, banner1, banner2):
-        if self.c:  # regular python doesn't print the banner with -c
-            self.display_banner = False
-        if banner1 is not None:
-            self.banner1 = banner1
-        if banner2 is not None:
-            self.banner2 = banner2
-        self.compute_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'
-
     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
@@ -486,7 +482,7 @@ class InteractiveShell(Component, Magic):
     def init_logstart(self):
         if self.logplay:
             self.magic_logstart(self.logplay + ' append')
-        elif  self.logfile:
+        elif self.logfile:
             self.magic_logstart(self.logfile)
         elif self.logstart:
             self.magic_logstart()
@@ -532,6 +528,31 @@ class InteractiveShell(Component, Magic):
             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
     #-------------------------------------------------------------------------
 
@@ -1673,7 +1694,7 @@ class InteractiveShell(Component, Magic):
         with nested(self.builtin_trap,):
             return eval(expr, self.user_global_ns, self.user_ns)
 
-    def mainloop(self, banner=None):
+    def mainloop(self, display_banner=None):
         """Start the mainloop.
 
         If an optional banner argument is given, it will override the
@@ -1684,10 +1705,6 @@ class InteractiveShell(Component, Magic):
             if self.c:  # Emulate Python's -c option
                 self.exec_init_cmd()
 
-            if self.display_banner:
-                if banner is None:
-                    banner = self.banner
-
             # if you run stuff with -c <cmd>, raw hist is not updated
             # ensure that it's in sync
             if len(self.input_hist) != len (self.input_hist_raw):
@@ -1695,7 +1712,7 @@ class InteractiveShell(Component, Magic):
 
             while 1:
                 try:
-                    self.interact()
+                    self.interact(display_banner=display_banner)
                     #self.interact_with_readline()                
                     # XXX for testing of a readline-decoupled repl loop, call
                     # interact_with_readline above
@@ -1774,17 +1791,17 @@ class InteractiveShell(Component, Magic):
             line = raw_input_original().decode(self.stdin_encoding)
             self.interact_handle_input(line)
 
-    def interact(self, banner=None):
+    def interact(self, display_banner=None):
         """Closely emulate the interactive Python console."""
 
         # batch run -> do not interact        
         if self.exit_now:
             return
 
-        if self.display_banner:
-            if banner is None:
-                banner = self.banner
-            self.write(banner)
+        if display_banner is None:
+            display_banner = self.display_banner
+        if display_banner:
+            self.show_banner()
 
         more = 0
         
@@ -1856,128 +1873,45 @@ class InteractiveShell(Component, Magic):
         # We are off again...
         __builtin__.__dict__['__IPYTHON__active'] -= 1
 
-    def safe_execfile(self,fname,*where,**kw):
+    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.
+        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
-            Name of the file to be executed.
-            
-          where : tuple
+        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)
 
-        :Keywords:
-          islog : boolean (False)
-
-          quiet : boolean (True)
+        fname = os.path.abspath(os.path.expanduser(fname))
 
-          exit_ignore : boolean (False)
-          """
+        # 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)
 
-        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)
+        # 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(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
+        dname = os.path.dirname(fname)
 
-        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
+        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
@@ -1997,7 +1931,7 @@ class InteractiveShell(Component, Magic):
             except SyntaxError:
                 self.showsyntaxerror()
                 warn('Failure executing file: <%s>' % fname)
-            except SystemExit,status:
+            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
@@ -2005,13 +1939,8 @@ class InteractiveShell(Component, Magic):
                 # SystemExit exception changed between Python 2.4 and 2.5, so
                 # the checks must be done in a version-dependent way.
                 show = False
-
-                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 status.message!=0 and not kw['exit_ignore']:
+                    show = True
                 if show:
                     self.showtraceback()
                     warn('Failure executing file: <%s>' % fname)
@@ -2019,46 +1948,82 @@ class InteractiveShell(Component, Magic):
                 self.showtraceback()
                 warn('Failure executing file: <%s>' % fname)
 
-        syspath_cleanup()
+    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()
 
-        Notes
-        -----
-        This was copied over from the old ipapi and probably can be done
-        away with once we move to block based interpreter.
-        
-        - Removes empty lines Suffixes all indented blocks that end with
-        - unindented lines with empty lines
+        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)
-            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
-                    
+            newlevel = len(l) - len(lstripped)                    
             if level > 0 and newlevel == 0 and \
-                   not is_secondary_block_start(stripped): 
+                   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):
@@ -2091,7 +2056,8 @@ class InteractiveShell(Component, Magic):
                 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_line(self.prefilter_manager.prefilter_lines(line,more))
+                    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.
@@ -2304,7 +2270,7 @@ class InteractiveShell(Component, Magic):
         if line.strip():
             if continue_prompt:
                 self.input_hist_raw[-1] += '%s\n' % line
-                if self.has_readline: # and some config option is set?
+                if self.has_readline and self.readline_use:
                     try:
                         histlen = self.readline.get_current_history_length()
                         if histlen > 1:
@@ -2349,37 +2315,56 @@ class InteractiveShell(Component, Magic):
     #     if batchrun and not self.interactive:
     #         self.ask_exit()            
 
-    # 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
+    #-------------------------------------------------------------------------
+    # IPython extensions
+    #-------------------------------------------------------------------------
+
+    def load_extension(self, module_str):
+        """Load an IPython extension.
+
+        An IPython extension is an importable Python module that has
+        a function with the signature::
+
+            def load_in_ipython(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.
+
+        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.ipythondir, 'extensions')``.  This directory
+        is added to ``sys.path`` automatically.
+        """
+        from IPython.utils.syspathcontext import prepended_to_syspath
+
+        if module_str in sys.modules:
+            return
+
+        with prepended_to_syspath(self.ipython_extension_dir):
+            __import__(module_str)
+            mod = sys.modules[module_str]
+            self._call_load_in_ipython(mod)
+
+    def reload_extension(self, module_str):
+        """Reload an IPython extension by doing reload."""
+        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_in_ipython(mod)
+            else:
+                self.load_extension(self, module_str)
+
+    def _call_load_in_ipython(self, mod):
+        if hasattr(mod, 'load_in_ipython'):
+            mod.load_in_ipython(self)
 
     #-------------------------------------------------------------------------
     # Things related to the prefilter
diff --git a/IPython/core/magic.py b/IPython/core/magic.py
index 9d33b97..678e2b0 100644
--- a/IPython/core/magic.py
+++ b/IPython/core/magic.py
@@ -1573,7 +1573,7 @@ Currently the magic system has the following functions:\n"""
             return
 
         if filename.lower().endswith('.ipy'):
-            self.runlines(open(filename).read(), clean=True)
+            self.safe_execfile_ipy(filename)
             return
         
         # Control the response to exit() calls made by the script being run
@@ -1743,25 +1743,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
diff --git a/IPython/core/page.py b/IPython/core/page.py
index 7de15af..f07c1b5 100644
--- a/IPython/core/page.py
+++ b/IPython/core/page.py
@@ -127,12 +127,14 @@ def page(strng,start=0,screen_lines=0,pager_cmd = None):
 
     # auto-determine screen size
     if screen_lines <= 0:
-        if TERM=='xterm':
+        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
diff --git a/IPython/core/prefilter.py b/IPython/core/prefilter.py
index fd994a1..91983eb 100644
--- a/IPython/core/prefilter.py
+++ b/IPython/core/prefilter.py
@@ -32,6 +32,7 @@ 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
 from IPython.utils.genutils import make_quoted_expr
@@ -183,11 +184,9 @@ class PrefilterManager(Component):
 
     @auto_attr
     def shell(self):
-        shell = Component.get_instances(
+        return Component.get_instances(
             root=self.root,
-            klass='IPython.core.iplib.InteractiveShell'
-        )[0]
-        return shell
+            klass='IPython.core.iplib.InteractiveShell')[0]
 
     def init_checkers(self):
         self._checkers = []
@@ -319,11 +318,9 @@ class PrefilterChecker(Component):
 
     @auto_attr
     def shell(self):
-        shell = Component.get_instances(
+        return Component.get_instances(
             root=self.root,
-            klass='IPython.core.iplib.InteractiveShell'
-        )[0]
-        return shell
+            klass='IPython.core.iplib.InteractiveShell')[0]
 
     @auto_attr
     def prefilter_manager(self):
@@ -523,11 +520,9 @@ class PrefilterHandler(Component):
 
     @auto_attr
     def shell(self):
-        shell = Component.get_instances(
+        return Component.get_instances(
             root=self.root,
-            klass='IPython.core.iplib.InteractiveShell'
-        )[0]
-        return shell
+            klass='IPython.core.iplib.InteractiveShell')[0]
 
     @auto_attr
     def prefilter_manager(self):
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