import os
import textwrap
from optparse import OptionConflictError
from warnings import warn
from nose.util import tolist

class Plugin(object):
    """Base class for nose plugins. It's recommended but not *necessary* to
    subclass this class to create a plugin, but all plugins *must* implement
    `options(self, parser, env)` and `configure(self, options, conf)`, and
    must have the attributes `enabled`, `name` and `score`.  The `name`
    attribute may contain hyphens ('-').

    Plugins should not be enabled by default.

    Subclassing Plugin (and calling the superclass methods in
    __init__, configure, and options, if you override them) will give
    your plugin some friendly default behavior:

    * A --with-$name option will be added to the command line interface
      to enable the plugin, and a corresponding environment variable
      will be used as the default value. The plugin class's docstring
      will be used as the help for this option.
    * The plugin will not be enabled unless this option is selected by
      the user.
    """
    can_configure = False
    enabled = False
    enableOpt = None
    name = None
    score = 100

    def __init__(self):
        if self.name is None:
            self.name = self.__class__.__name__.lower()
        if self.enableOpt is None:
            self.enableOpt = "enable_plugin_%s" % self.name.replace('-', '_')

    def addOptions(self, parser, env=None):
        """Add command-line options for this plugin.

        The base plugin class adds --with-$name by default, used to enable the
        plugin.

        .. warning :: Don't implement addOptions unless you want to override
                      all default option handling behavior, including
                      warnings for conflicting options. Implement
                      :meth:`options
                      <nose.plugins.base.IPluginInterface.options>`
                      instead.
        """
        self.add_options(parser, env)

    def add_options(self, parser, env=None):
        """Non-camel-case version of func name for backwards compatibility.

        .. warning ::

           DEPRECATED: Do not use this method,
           use :meth:`options <nose.plugins.base.IPluginInterface.options>`
           instead.

        """
        # FIXME raise deprecation warning if wasn't called by wrapper
        if env is None:
            env = os.environ
        try:
            self.options(parser, env)
            self.can_configure = True
        except OptionConflictError, e:
            warn("Plugin %s has conflicting option string: %s and will "
                 "be disabled" % (self, e), RuntimeWarning)
            self.enabled = False
            self.can_configure = False

    def options(self, parser, env):
        """Register commandline options.

        Implement this method for normal options behavior with protection from
        OptionConflictErrors. If you override this method and want the default
        --with-$name option to be registered, be sure to call super().
        """
        env_opt = 'NOSE_WITH_%s' % self.name.upper()
        env_opt = env_opt.replace('-', '_')
        parser.add_option("--with-%s" % self.name,
                          action="store_true",
                          dest=self.enableOpt,
                          default=env.get(env_opt),
                          help="Enable plugin %s: %s [%s]" %
                          (self.__class__.__name__, self.help(), env_opt))

    def configure(self, options, conf):
        """Configure the plugin and system, based on selected options.

        The base plugin class sets the plugin to enabled if the enable option
        for the plugin (self.enableOpt) is true.
        """
        if not self.can_configure:
            return
        self.conf = conf
        if hasattr(options, self.enableOpt):
            self.enabled = getattr(options, self.enableOpt)

    def help(self):
        """Return help for this plugin. This will be output as the help
        section of the --with-$name option that enables the plugin.
        """
        if self.__class__.__doc__:
            # doc sections are often indented; compress the spaces
            return textwrap.dedent(self.__class__.__doc__)
        return "(no help available)"

    # Compatiblity shim
    def tolist(self, val):
        warn("Plugin.tolist is deprecated. Use nose.util.tolist instead",
             DeprecationWarning)
        return tolist(val)

class IPluginInterface(object):
    """
    IPluginInterface describes the plugin API. Do not subclass or use this
    class directly.
    """
    def __new__(cls, *arg, **kw):
        raise TypeError("IPluginInterface class is for documentation only")

    def addOptions(self, parser, env):
        """Called to allow plugin to register command-line options with the
        parser. DO NOT return a value from this method unless you want to stop
        all other plugins from setting their options.

        .. warning ::

           DEPRECATED -- implement
           :meth:`options <nose.plugins.base.IPluginInterface.options>` instead.
        """
        pass
    add_options = addOptions
    add_options.deprecated = True

    def addDeprecated(self, test):
        """Called when a deprecated test is seen. DO NOT return a value
        unless you want to stop other plugins from seeing the deprecated
        test.

        .. warning :: DEPRECATED -- check error class in addError instead
        """
        pass
    addDeprecated.deprecated = True

    def addError(self, test, err):
        """Called when a test raises an uncaught exception. DO NOT return a
        value unless you want to stop other plugins from seeing that the
        test has raised an error.

        :param test: the test case
        :type test: :class:`nose.case.Test`
        :param err: sys.exc_info() tuple
        :type err: 3-tuple
        """
        pass
    addError.changed = True

    def addFailure(self, test, err):
        """Called when a test fails. DO NOT return a value unless you
        want to stop other plugins from seeing that the test has failed.

        :param test: the test case
        :type test: :class:`nose.case.Test`
        :param err: 3-tuple
        :type err: sys.exc_info() tuple
        """
        pass
    addFailure.changed = True

    def addSkip(self, test):
        """Called when a test is skipped. DO NOT return a value unless
        you want to stop other plugins from seeing the skipped test.

        .. warning:: DEPRECATED -- check error class in addError instead
        """
        pass
    addSkip.deprecated = True

    def addSuccess(self, test):
        """Called when a test passes. DO NOT return a value unless you
        want to stop other plugins from seeing the passing test.

        :param test: the test case
        :type test: :class:`nose.case.Test`
        """
        pass
    addSuccess.changed = True

    def afterContext(self):
        """Called after a context (generally a module) has been
        lazy-loaded, imported, setup, had its tests loaded and
        executed, and torn down.
        """
        pass
    afterContext._new = True

    def afterDirectory(self, path):
        """Called after all tests have been loaded from directory at path
        and run.

        :param path: the directory that has finished processing
        :type path: string
        """
        pass
    afterDirectory._new = True

    def afterImport(self, filename, module):
        """Called after module is imported from filename. afterImport
        is called even if the import failed.

        :param filename: The file that was loaded
        :type filename: string
        :param filename: The name of the module
        :type module: string
        """
        pass
    afterImport._new = True

    def afterTest(self, test):
        """Called after the test has been run and the result recorded
        (after stopTest).

        :param test: the test case
        :type test: :class:`nose.case.Test`
        """
        pass
    afterTest._new = True

    def beforeContext(self):
        """Called before a context (generally a module) is
        examined. Because the context is not yet loaded, plugins don't
        get to know what the context is; so any context operations
        should use a stack that is pushed in `beforeContext` and popped
        in `afterContext` to ensure they operate symmetrically.

        `beforeContext` and `afterContext` are mainly useful for tracking
        and restoring global state around possible changes from within a
        context, whatever the context may be. If you need to operate on
        contexts themselves, see `startContext` and `stopContext`, which
        are passed the context in question, but are called after
        it has been loaded (imported in the module case).
        """
        pass
    beforeContext._new = True

    def beforeDirectory(self, path):
        """Called before tests are loaded from directory at path.

        :param path: the directory that is about to be processed
        """
        pass
    beforeDirectory._new = True

    def beforeImport(self, filename, module):
        """Called before module is imported from filename.

        :param filename: The file that will be loaded
        :param module: The name of the module found in file
        :type module: string
        """
    beforeImport._new = True

    def beforeTest(self, test):
        """Called before the test is run (before startTest).

        :param test: the test case
        :type test: :class:`nose.case.Test`
        """
        pass
    beforeTest._new = True

    def begin(self):
        """Called before any tests are collected or run. Use this to
        perform any setup needed before testing begins.
        """
        pass

    def configure(self, options, conf):
        """Called after the command line has been parsed, with the
        parsed options and the config container. Here, implement any
        config storage or changes to state or operation that are set
        by command line options.

        DO NOT return a value from this method unless you want to
        stop all other plugins from being configured.
        """
        pass

    def finalize(self, result):
        """Called after all report output, including output from all
        plugins, has been sent to the stream. Use this to print final
        test results or perform final cleanup. Return None to allow
        other plugins to continue printing, or any other value to stop
        them.

        :param result: test result object

        .. Note:: When tests are run under a test runner other than
           :class:`nose.core.TextTestRunner`, such as
           via ``python setup.py test``, this method may be called
           **before** the default report output is sent.
        """
        pass

    def describeTest(self, test):
        """Return a test description.

        Called by :meth:`nose.case.Test.shortDescription`.

        :param test: the test case
        :type test: :class:`nose.case.Test`
        """
        pass
    describeTest._new = True

    def formatError(self, test, err):
        """Called in result.addError, before plugin.addError. If you
        want to replace or modify the error tuple, return a new error
        tuple.

        :param test: the test case
        :type test: :class:`nose.case.Test`
        :param err: sys.exc_info() tuple
        :type err: 3-tuple
        """
        pass
    formatError._new = True
    formatError.chainable = True
    # test arg is not chainable
    formatError.static_args = (True, False)

    def formatFailure(self, test, err):
        """Called in result.addFailure, before plugin.addFailure. If you
        want to replace or modify the error tuple, return a new error
        tuple. Because this method is chainable, you must return the
        test as well, so you'll return something like::

          return (test, err)

        :param test: the test case
        :type test: :class:`nose.case.Test`
        :param err: sys.exc_info() tuple
        :type err: 3-tuple
        """
        pass
    formatFailure._new = True
    formatFailure.chainable = True
    # test arg is not chainable
    formatFailure.static_args = (True, False)

    def handleError(self, test, err):
        """Called on addError. To handle the error yourself and prevent normal
        error processing, return a true value.

        :param test: the test case
        :type test: :class:`nose.case.Test`
        :param err: sys.exc_info() tuple
        :type err: 3-tuple
        """
        pass
    handleError._new = True

    def handleFailure(self, test, err):
        """Called on addFailure. To handle the failure yourself and
        prevent normal failure processing, return a true value.

        :param test: the test case
        :type test: :class:`nose.case.Test`
        :param err: sys.exc_info() tuple
        :type err: 3-tuple
        """
        pass
    handleFailure._new = True

    def loadTestsFromDir(self, path):
        """Return iterable of tests from a directory. May be a
        generator.  Each item returned must be a runnable
        unittest.TestCase (or subclass) instance or suite instance.
        Return None if your plugin cannot collect any tests from
        directory.

        :param  path: The path to the directory.
        """
        pass
    loadTestsFromDir.generative = True
    loadTestsFromDir._new = True

    def loadTestsFromModule(self, module, path=None):
        """Return iterable of tests in a module. May be a
        generator. Each item returned must be a runnable
        unittest.TestCase (or subclass) instance.
        Return None if your plugin cannot
        collect any tests from module.

        :param module: The module object
        :type module: python module
        :param path: the path of the module to search, to distinguish from
            namespace package modules

            .. note::

               NEW. The ``path`` parameter will only be passed by nose 0.11
               or above.
        """
        pass
    loadTestsFromModule.generative = True

    def loadTestsFromName(self, name, module=None, importPath=None):
        """Return tests in this file or module. Return None if you are not able
        to load any tests, or an iterable if you are. May be a
        generator.

        :param name: The test name. May be a file or module name plus a test
            callable. Use split_test_name to split into parts. Or it might
            be some crazy name of your own devising, in which case, do
            whatever you want.
        :param module: Module from which the name is to be loaded
        :param importPath: Path from which file (must be a python module) was
            found

            .. warning:: DEPRECATED: this argument will NOT be passed.
        """
        pass
    loadTestsFromName.generative = True

    def loadTestsFromNames(self, names, module=None):
        """Return a tuple of (tests loaded, remaining names). Return
        None if you are not able to load any tests. Multiple plugins
        may implement loadTestsFromNames; the remaining name list from
        each will be passed to the next as input.

        :param names: List of test names.
        :type names: iterable
        :param module: Module from which the names are to be loaded
        """
        pass
    loadTestsFromNames._new = True
    loadTestsFromNames.chainable = True

    def loadTestsFromFile(self, filename):
        """Return tests in this file. Return None if you are not
        interested in loading any tests, or an iterable if you are and
        can load some. May be a generator. *If you are interested in
        loading tests from the file and encounter no errors, but find
        no tests, yield False or return [False].*

        .. Note:: This method replaces loadTestsFromPath from the 0.9
                  API.

        :param filename: The full path to the file or directory.
        """
        pass
    loadTestsFromFile.generative = True
    loadTestsFromFile._new = True

    def loadTestsFromPath(self, path):
        """
        .. warning:: DEPRECATED -- use loadTestsFromFile instead
        """
        pass
    loadTestsFromPath.deprecated = True

    def loadTestsFromTestCase(self, cls):
        """Return tests in this test case class. Return None if you are
        not able to load any tests, or an iterable if you are. May be a
        generator.

        :param cls: The test case class. Must be subclass of
           :class:`unittest.TestCase`.
        """
        pass
    loadTestsFromTestCase.generative = True

    def loadTestsFromTestClass(self, cls):
        """Return tests in this test class. Class will *not* be a
        unittest.TestCase subclass. Return None if you are not able to
        load any tests, an iterable if you are. May be a generator.

        :param cls: The test case class. Must be **not** be subclass of
           :class:`unittest.TestCase`.
        """
        pass
    loadTestsFromTestClass._new = True
    loadTestsFromTestClass.generative = True

    def makeTest(self, obj, parent):
        """Given an object and its parent, return or yield one or more
        test cases. Each test must be a unittest.TestCase (or subclass)
        instance. This is called before default test loading to allow
        plugins to load an alternate test case or cases for an
        object. May be a generator.

        :param obj: The object to be made into a test
        :param parent: The parent of obj (eg, for a method, the class)
        """
        pass
    makeTest._new = True
    makeTest.generative = True

    def options(self, parser, env):
        """Called to allow plugin to register command line
        options with the parser.

        DO NOT return a value from this method unless you want to stop
        all other plugins from setting their options.

        :param parser: options parser instance
        :type parser: :class:`ConfigParser.ConfigParser`
        :param env: environment, default is os.environ
        """
        pass
    options._new = True

    def prepareTest(self, test):
        """Called before the test is run by the test runner. Please
        note the article *the* in the previous sentence: prepareTest
        is called *only once*, and is passed the test case or test
        suite that the test runner will execute. It is *not* called
        for each individual test case. If you return a non-None value,
        that return value will be run as the test. Use this hook to
        wrap or decorate the test with another function. If you need
        to modify or wrap individual test cases, use `prepareTestCase`
        instead.

        :param test: the test case
        :type test: :class:`nose.case.Test`
        """
        pass

    def prepareTestCase(self, test):
        """Prepare or wrap an individual test case. Called before
        execution of the test. The test passed here is a
        nose.case.Test instance; the case to be executed is in the
        test attribute of the passed case. To modify the test to be
        run, you should return a callable that takes one argument (the
        test result object) -- it is recommended that you *do not*
        side-effect the nose.case.Test instance you have been passed.

        Keep in mind that when you replace the test callable you are
        replacing the run() method of the test case -- including the
        exception handling and result calls, etc.

        :param test: the test case
        :type test: :class:`nose.case.Test`
        """
        pass
    prepareTestCase._new = True

    def prepareTestLoader(self, loader):
        """Called before tests are loaded. To replace the test loader,
        return a test loader. To allow other plugins to process the
        test loader, return None. Only one plugin may replace the test
        loader. Only valid when using nose.TestProgram.

        :param loader: :class:`nose.loader.TestLoader`
             (or other loader) instance
        """
        pass
    prepareTestLoader._new = True

    def prepareTestResult(self, result):
        """Called before the first test is run. To use a different
        test result handler for all tests than the given result,
        return a test result handler. NOTE however that this handler
        will only be seen by tests, that is, inside of the result
        proxy system. The TestRunner and TestProgram -- whether nose's
        or other -- will continue to see the original result
        handler. For this reason, it is usually better to monkeypatch
        the result (for instance, if you want to handle some
        exceptions in a unique way). Only one plugin may replace the
        result, but many may monkeypatch it. If you want to
        monkeypatch and stop other plugins from doing so, monkeypatch
        and return the patched result.

        :param result: :class:`nose.result.TextTestResult`
             (or other result) instance
        """
        pass
    prepareTestResult._new = True

    def prepareTestRunner(self, runner):
        """Called before tests are run. To replace the test runner,
        return a test runner. To allow other plugins to process the
        test runner, return None. Only valid when using nose.TestProgram.

        :param runner: :class:`nose.core.TextTestRunner`
             (or other runner) instance
        """
        pass
    prepareTestRunner._new = True

    def report(self, stream):
        """Called after all error output has been printed. Print your
        plugin's report to the provided stream. Return None to allow
        other plugins to print reports, any other value to stop them.

        :param stream: stream object; send your output here
        :type stream: file-like object
        """
        pass

    def setOutputStream(self, stream):
        """Called before test output begins. To direct test output to a
        new stream, return a stream object, which must implement a
        `write(msg)` method. If you only want to note the stream, not
        capture or redirect it, then return None.

        :param stream: stream object; send your output here
        :type stream: file-like object
        """

    def startContext(self, context):
        """Called before context setup and the running of tests in the
        context. Note that tests have already been *loaded* from the
        context before this call.

        :param context: the context about to be setup. May be a module or
             class, or any other object that contains tests.
        """
        pass
    startContext._new = True

    def startTest(self, test):
        """Called before each test is run. DO NOT return a value unless
        you want to stop other plugins from seeing the test start.

        :param err: sys.exc_info() tuple
        :type err: 3-tuple
        """
        pass

    def stopContext(self, context):
        """Called after the tests in a context have run and the
        context has been torn down.

        :param context: the context about to be setup. May be a module or
             class, or any other object that contains tests.
        """
        pass
    stopContext._new = True

    def stopTest(self, test):
        """Called after each test is run. DO NOT return a value unless
        you want to stop other plugins from seeing that the test has stopped.

        :param err: sys.exc_info() tuple
        :type err: 3-tuple
        """
        pass

    def testName(self, test):
        """Return a short test name. Called by `nose.case.Test.__str__`.

        :param err: sys.exc_info() tuple
        :type err: 3-tuple
        """
        pass
    testName._new = True

    def wantClass(self, cls):
        """Return true if you want the main test selector to collect
        tests from this class, false if you don't, and None if you don't
        care.

        :param cls: The class being examined by the selector
        """
        pass

    def wantDirectory(self, dirname):
        """Return true if you want test collection to descend into this
        directory, false if you do not, and None if you don't care.

        :param dirname: Full path to directory being examined by the selector
        """
        pass

    def wantFile(self, file):
        """Return true if you want to collect tests from this file,
        false if you do not and None if you don't care.

        Change from 0.9: The optional package parameter is no longer passed.

        :param file: Full path to file being examined by the selector
        """
        pass

    def wantFunction(self, function):
        """Return true to collect this function as a test, false to
        prevent it from being collected, and None if you don't care.

        :param function: The function object being examined by the selector
        """
        pass

    def wantMethod(self, method):
        """Return true to collect this method as a test, false to
        prevent it from being collected, and None if you don't care.

        :param method: The method object being examined by the selector
        :type method: unbound method
        """
        pass

    def wantModule(self, module):
        """Return true if you want to collection to descend into this
        module, false to prevent the collector from descending into the
        module, and None if you don't care.

        :param module: The module object being examined by the selector
        :type module: python module
        """
        pass

    def wantModuleTests(self, module):
        """
        .. warning:: DEPRECATED -- this method will not be called, it has
                     been folded into wantModule.
        """
        pass
    wantModuleTests.deprecated = True


"""Base classes to manage the interaction with a running kernel.

TODO
* Create logger to handle debugging and console messages.
"""

#-----------------------------------------------------------------------------
#  Copyright (C) 2008-2011  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
#-----------------------------------------------------------------------------

# Standard library imports.
import atexit
import errno
import json
from subprocess import Popen
import os
import signal
import sys
from threading import Thread
import time

# System library imports.
import zmq
# import ZMQError in top-level namespace, to avoid ugly attribute-error messages
# during garbage collection of threads at exit:
from zmq import ZMQError
from zmq.eventloop import ioloop, zmqstream

# Local imports.
from IPython.config.loader import Config
from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
from IPython.utils.traitlets import (
    HasTraits, Any, Instance, Type, Unicode, Integer, Bool
)
from IPython.utils.py3compat import str_to_bytes
from IPython.zmq.entry_point import write_connection_file
from session import Session

#-----------------------------------------------------------------------------
# Constants and exceptions
#-----------------------------------------------------------------------------

class InvalidPortNumber(Exception):
    pass

#-----------------------------------------------------------------------------
# Utility functions
#-----------------------------------------------------------------------------

# some utilities to validate message structure, these might get moved elsewhere
# if they prove to have more generic utility

def validate_string_list(lst):
    """Validate that the input is a list of strings.

    Raises ValueError if not."""
    if not isinstance(lst, list):
        raise ValueError('input %r must be a list' % lst)
    for x in lst:
        if not isinstance(x, basestring):
            raise ValueError('element %r in list must be a string' % x)

def validate_string_dict(dct):
    """Validate that the input is a dict with string keys and values.

    Raises ValueError if not."""
    for k,v in dct.iteritems():
        if not isinstance(k, basestring):
            raise ValueError('key %r in dict must be a string' % k)
        if not isinstance(v, basestring):
            raise ValueError('value %r in dict must be a string' % v)

#-----------------------------------------------------------------------------
# ZMQ Socket Channel classes
#-----------------------------------------------------------------------------

class ZMQSocketChannel(Thread):
    """The base class for the channels that use ZMQ sockets.
    """
    context = None
    session = None
    socket = None
    ioloop = None
    stream = None
    _address = None
    _exiting = False

    def __init__(self, context, session, address):
        """Create a channel

        Parameters
        ----------
        context : :class:`zmq.Context`
            The ZMQ context to use.
        session : :class:`session.Session`
            The session to use.
        address : tuple
            Standard (ip, port) tuple that the kernel is listening on.
        """
        super(ZMQSocketChannel, self).__init__()
        self.daemon = True

        self.context = context
        self.session = session
        if address[1] == 0:
            message = 'The port number for a channel cannot be 0.'
            raise InvalidPortNumber(message)
        self._address = address
        atexit.register(self._notice_exit)

    def _notice_exit(self):
        self._exiting = True

    def _run_loop(self):
        """Run my loop, ignoring EINTR events in the poller"""
        while True:
            try:
                self.ioloop.start()
            except ZMQError as e:
                if e.errno == errno.EINTR:
                    continue
                else:
                    raise
            except Exception:
                if self._exiting:
                    break
                else:
                    raise
            else:
                break

    def stop(self):
        """Stop the channel's activity.

        This calls :method:`Thread.join` and returns when the thread
        terminates. :class:`RuntimeError` will be raised if
        :method:`self.start` is called again.
        """
        self.join()

    @property
    def address(self):
        """Get the channel's address as an (ip, port) tuple.

        By the default, the address is (localhost, 0), where 0 means a random
        port.
        """
        return self._address

    def _queue_send(self, msg):
        """Queue a message to be sent from the IOLoop's thread.

        Parameters
        ----------
        msg : message to send

        This is threadsafe, as it uses IOLoop.add_callback to give the loop's
        thread control of the action.
        """
        def thread_send():
            self.session.send(self.stream, msg)
        self.ioloop.add_callback(thread_send)

    def _handle_recv(self, msg):
        """callback for stream.on_recv

        unpacks message, and calls handlers with it.
        """
        ident,smsg = self.session.feed_identities(msg)
        self.call_handlers(self.session.unserialize(smsg))

class ShellSocketChannel(ZMQSocketChannel):
    """The DEALER channel for issues request/replies to the kernel.
    """

    command_queue = None
    # flag for whether execute requests should be allowed to call raw_input:
    allow_stdin = True

    def __init__(self, context, session, address):
        super(ShellSocketChannel, self).__init__(context, session, address)
        self.ioloop = ioloop.IOLoop()

    def run(self):
        """The thread's main activity.  Call start() instead."""
        self.socket = self.context.socket(zmq.DEALER)
        self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
        self.socket.connect('tcp://%s:%i' % self.address)
        self.stream = zmqstream.ZMQStream(self.socket, self.ioloop)
        self.stream.on_recv(self._handle_recv)
        self._run_loop()
        try:
            self.socket.close()
        except:
            pass

    def stop(self):
        self.ioloop.stop()
        super(ShellSocketChannel, self).stop()

    def call_handlers(self, msg):
        """This method is called in the ioloop thread when a message arrives.

        Subclasses should override this method to handle incoming messages.
        It is important to remember that this method is called in the thread
        so that some logic must be done to ensure that the application leve
        handlers are called in the application thread.
        """
        raise NotImplementedError('call_handlers must be defined in a subclass.')

    def execute(self, code, silent=False,
                user_variables=None, user_expressions=None, allow_stdin=None):
        """Execute code in the kernel.

        Parameters
        ----------
        code : str
            A string of Python code.

        silent : bool, optional (default False)
            If set, the kernel will execute the code as quietly possible.

        user_variables : list, optional
            A list of variable names to pull from the user's namespace.  They
            will come back as a dict with these names as keys and their
            :func:`repr` as values.

        user_expressions : dict, optional
            A dict with string keys and  to pull from the user's
            namespace.  They will come back as a dict with these names as keys
            and their :func:`repr` as values.

        allow_stdin : bool, optional
            Flag for
            A dict with string keys and  to pull from the user's
            namespace.  They will come back as a dict with these names as keys
            and their :func:`repr` as values.

        Returns
        -------
        The msg_id of the message sent.
        """
        if user_variables is None:
            user_variables = []
        if user_expressions is None:
            user_expressions = {}
        if allow_stdin is None:
            allow_stdin = self.allow_stdin

        # Don't waste network traffic if inputs are invalid
        if not isinstance(code, basestring):
            raise ValueError('code %r must be a string' % code)
        validate_string_list(user_variables)
        validate_string_dict(user_expressions)

        # Create class for content/msg creation. Related to, but possibly
        # not in Session.
        content = dict(code=code, silent=silent,
                       user_variables=user_variables,
                       user_expressions=user_expressions,
                       allow_stdin=allow_stdin,
                       )
        msg = self.session.msg('execute_request', content)
        self._queue_send(msg)
        return msg['header']['msg_id']

    def complete(self, text, line, cursor_pos, block=None):
        """Tab complete text in the kernel's namespace.

        Parameters
        ----------
        text : str
            The text to complete.
        line : str
            The full line of text that is the surrounding context for the
            text to complete.
        cursor_pos : int
            The position of the cursor in the line where the completion was
            requested.
        block : str, optional
            The full block of code in which the completion is being requested.

        Returns
        -------
        The msg_id of the message sent.
        """
        content = dict(text=text, line=line, block=block, cursor_pos=cursor_pos)
        msg = self.session.msg('complete_request', content)
        self._queue_send(msg)
        return msg['header']['msg_id']

    def object_info(self, oname, detail_level=0):
        """Get metadata information about an object.

        Parameters
        ----------
        oname : str
            A string specifying the object name.
        detail_level : int, optional
            The level of detail for the introspection (0-2)

        Returns
        -------
        The msg_id of the message sent.
        """
        content = dict(oname=oname, detail_level=detail_level)
        msg = self.session.msg('object_info_request', content)
        self._queue_send(msg)
        return msg['header']['msg_id']

    def history(self, raw=True, output=False, hist_access_type='range', **kwargs):
        """Get entries from the history list.

        Parameters
        ----------
        raw : bool
            If True, return the raw input.
        output : bool
            If True, then return the output as well.
        hist_access_type : str
            'range' (fill in session, start and stop params), 'tail' (fill in n)
             or 'search' (fill in pattern param).

        session : int
            For a range request, the session from which to get lines. Session
            numbers are positive integers; negative ones count back from the
            current session.
        start : int
            The first line number of a history range.
        stop : int
            The final (excluded) line number of a history range.

        n : int
            The number of lines of history to get for a tail request.

        pattern : str
            The glob-syntax pattern for a search request.

        Returns
        -------
        The msg_id of the message sent.
        """
        content = dict(raw=raw, output=output, hist_access_type=hist_access_type,
                                                                    **kwargs)
        msg = self.session.msg('history_request', content)
        self._queue_send(msg)
        return msg['header']['msg_id']

    def shutdown(self, restart=False):
        """Request an immediate kernel shutdown.

        Upon receipt of the (empty) reply, client code can safely assume that
        the kernel has shut down and it's safe to forcefully terminate it if
        it's still alive.

        The kernel will send the reply via a function registered with Python's
        atexit module, ensuring it's truly done as the kernel is done with all
        normal operation.
        """
        # Send quit message to kernel. Once we implement kernel-side setattr,
        # this should probably be done that way, but for now this will do.
        msg = self.session.msg('shutdown_request', {'restart':restart})
        self._queue_send(msg)
        return msg['header']['msg_id']

class SubSocketChannel(ZMQSocketChannel):
    """The SUB channel which listens for messages that the kernel publishes.
    """

    def __init__(self, context, session, address):
        super(SubSocketChannel, self).__init__(context, session, address)
        self.ioloop = ioloop.IOLoop()

    def run(self):
        """The thread's main activity.  Call start() instead."""
        self.socket = self.context.socket(zmq.SUB)
        self.socket.setsockopt(zmq.SUBSCRIBE,b'')
        self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
        self.socket.connect('tcp://%s:%i' % self.address)
        self.stream = zmqstream.ZMQStream(self.socket, self.ioloop)
        self.stream.on_recv(self._handle_recv)
        self._run_loop()
        try:
            self.socket.close()
        except:
            pass

    def stop(self):
        self.ioloop.stop()
        super(SubSocketChannel, self).stop()

    def call_handlers(self, msg):
        """This method is called in the ioloop thread when a message arrives.

        Subclasses should override this method to handle incoming messages.
        It is important to remember that this method is called in the thread
        so that some logic must be done to ensure that the application leve
        handlers are called in the application thread.
        """
        raise NotImplementedError('call_handlers must be defined in a subclass.')

    def flush(self, timeout=1.0):
        """Immediately processes all pending messages on the SUB channel.

        Callers should use this method to ensure that :method:`call_handlers`
        has been called for all messages that have been received on the
        0MQ SUB socket of this channel.

        This method is thread safe.

        Parameters
        ----------
        timeout : float, optional
            The maximum amount of time to spend flushing, in seconds. The
            default is one second.
        """
        # We do the IOLoop callback process twice to ensure that the IOLoop
        # gets to perform at least one full poll.
        stop_time = time.time() + timeout
        for i in xrange(2):
            self._flushed = False
            self.ioloop.add_callback(self._flush)
            while not self._flushed and time.time() < stop_time:
                time.sleep(0.01)

    def _flush(self):
        """Callback for :method:`self.flush`."""
        self.stream.flush()
        self._flushed = True

class StdInSocketChannel(ZMQSocketChannel):
    """A reply channel to handle raw_input requests that the kernel makes."""

    msg_queue = None

    def __init__(self, context, session, address):
        super(StdInSocketChannel, self).__init__(context, session, address)
        self.ioloop = ioloop.IOLoop()

    def run(self):
        """The thread's main activity.  Call start() instead."""
        self.socket = self.context.socket(zmq.DEALER)
        self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
        self.socket.connect('tcp://%s:%i' % self.address)
        self.stream = zmqstream.ZMQStream(self.socket, self.ioloop)
        self.stream.on_recv(self._handle_recv)
        self._run_loop()
        try:
            self.socket.close()
        except:
            pass

    def stop(self):
        self.ioloop.stop()
        super(StdInSocketChannel, self).stop()

    def call_handlers(self, msg):
        """This method is called in the ioloop thread when a message arrives.

        Subclasses should override this method to handle incoming messages.
        It is important to remember that this method is called in the thread
        so that some logic must be done to ensure that the application leve
        handlers are called in the application thread.
        """
        raise NotImplementedError('call_handlers must be defined in a subclass.')

    def input(self, string):
        """Send a string of raw input to the kernel."""
        content = dict(value=string)
        msg = self.session.msg('input_reply', content)
        self._queue_send(msg)

class HBSocketChannel(ZMQSocketChannel):
    """The heartbeat channel which monitors the kernel heartbeat.

    Note that the heartbeat channel is paused by default. As long as you start
    this channel, the kernel manager will ensure that it is paused and un-paused
    as appropriate.
    """

    time_to_dead = 3.0
    socket = None
    poller = None
    _running = None
    _pause = None
    _beating = None

    def __init__(self, context, session, address):
        super(HBSocketChannel, self).__init__(context, session, address)
        self._running = False
        self._pause =True
        self.poller = zmq.Poller()

    def _create_socket(self):
        if self.socket is not None:
            # close previous socket, before opening a new one
            self.poller.unregister(self.socket)
            self.socket.close()
        self.socket = self.context.socket(zmq.REQ)
        self.socket.setsockopt(zmq.LINGER, 0)
        self.socket.connect('tcp://%s:%i' % self.address)

        self.poller.register(self.socket, zmq.POLLIN)

    def _poll(self, start_time):
        """poll for heartbeat replies until we reach self.time_to_dead

        Ignores interrupts, and returns the result of poll(), which
        will be an empty list if no messages arrived before the timeout,
        or the event tuple if there is a message to receive.
        """

        until_dead = self.time_to_dead - (time.time() - start_time)
        # ensure poll at least once
        until_dead = max(until_dead, 1e-3)
        events = []
        while True:
            try:
                events = self.poller.poll(1000 * until_dead)
            except ZMQError as e:
                if e.errno == errno.EINTR:
                    # ignore interrupts during heartbeat
                    # this may never actually happen
                    until_dead = self.time_to_dead - (time.time() - start_time)
                    until_dead = max(until_dead, 1e-3)
                    pass
                else:
                    raise
            except Exception:
                if self._exiting:
                    break
                else:
                    raise
            else:
                break
        return events

    def run(self):
        """The thread's main activity.  Call start() instead."""
        self._create_socket()
        self._running = True
        self._beating = True

        while self._running:
            if self._pause:
                # just sleep, and skip the rest of the loop
                time.sleep(self.time_to_dead)
                continue

            since_last_heartbeat = 0.0
            # io.rprint('Ping from HB channel') # dbg
            # no need to catch EFSM here, because the previous event was
            # either a recv or connect, which cannot be followed by EFSM
            self.socket.send(b'ping')
            request_time = time.time()
            ready = self._poll(request_time)
            if ready:
                self._beating = True
                # the poll above guarantees we have something to recv
                self.socket.recv()
                # sleep the remainder of the cycle
                remainder = self.time_to_dead - (time.time() - request_time)
                if remainder > 0:
                    time.sleep(remainder)
                continue
            else:
                # nothing was received within the time limit, signal heart failure
                self._beating = False
                since_last_heartbeat = time.time() - request_time
                self.call_handlers(since_last_heartbeat)
                # and close/reopen the socket, because the REQ/REP cycle has been broken
                self._create_socket()
                continue
        try:
            self.socket.close()
        except:
            pass

    def pause(self):
        """Pause the heartbeat."""
        self._pause = True

    def unpause(self):
        """Unpause the heartbeat."""
        self._pause = False

    def is_beating(self):
        """Is the heartbeat running and responsive (and not paused)."""
        if self.is_alive() and not self._pause and self._beating:
            return True
        else:
            return False

    def stop(self):
        self._running = False
        super(HBSocketChannel, self).stop()

    def call_handlers(self, since_last_heartbeat):
        """This method is called in the ioloop thread when a message arrives.

        Subclasses should override this method to handle incoming messages.
        It is important to remember that this method is called in the thread
        so that some logic must be done to ensure that the application level
        handlers are called in the application thread.
        """
        raise NotImplementedError('call_handlers must be defined in a subclass.')

#-----------------------------------------------------------------------------
# Main kernel manager class
#-----------------------------------------------------------------------------

class KernelManager(HasTraits):
    """ Manages a kernel for a frontend.

    The SUB channel is for the frontend to receive messages published by the
    kernel.

    The REQ channel is for the frontend to make requests of the kernel.

    The REP channel is for the kernel to request stdin (raw_input) from the
    frontend.
    """
    # config object for passing to child configurables
    config = Instance(Config)

    # The PyZMQ Context to use for communication with the kernel.
    context = Instance(zmq.Context)
    def _context_default(self):
        return zmq.Context.instance()

    # The Session to use for communication with the kernel.
    session = Instance(Session)

    # The kernel process with which the KernelManager is communicating.
    kernel = Instance(Popen)

    # The addresses for the communication channels.
    connection_file = Unicode('')
    ip = Unicode(LOCALHOST)
    def _ip_changed(self, name, old, new):
        if new == '*':
            self.ip = '0.0.0.0'
    shell_port = Integer(0)
    iopub_port = Integer(0)
    stdin_port = Integer(0)
    hb_port = Integer(0)

    # The classes to use for the various channels.
    shell_channel_class = Type(ShellSocketChannel)
    sub_channel_class = Type(SubSocketChannel)
    stdin_channel_class = Type(StdInSocketChannel)
    hb_channel_class = Type(HBSocketChannel)

    # Protected traits.
    _launch_args = Any
    _shell_channel = Any
    _sub_channel = Any
    _stdin_channel = Any
    _hb_channel = Any
    _connection_file_written=Bool(False)

    def __init__(self, **kwargs):
        super(KernelManager, self).__init__(**kwargs)
        if self.session is None:
            self.session = Session(config=self.config)

    def __del__(self):
        self.cleanup_connection_file()

    #--------------------------------------------------------------------------
    # Channel management methods:
    #--------------------------------------------------------------------------

    def start_channels(self, shell=True, sub=True, stdin=True, hb=True):
        """Starts the channels for this kernel.

        This will create the channels if they do not exist and then start
        them. If port numbers of 0 are being used (random ports) then you
        must first call :method:`start_kernel`. If the channels have been
        stopped and you call this, :class:`RuntimeError` will be raised.
        """
        if shell:
            self.shell_channel.start()
        if sub:
            self.sub_channel.start()
        if stdin:
            self.stdin_channel.start()
            self.shell_channel.allow_stdin = True
        else:
            self.shell_channel.allow_stdin = False
        if hb:
            self.hb_channel.start()

    def stop_channels(self):
        """Stops all the running channels for this kernel.
        """
        if self.shell_channel.is_alive():
            self.shell_channel.stop()
        if self.sub_channel.is_alive():
            self.sub_channel.stop()
        if self.stdin_channel.is_alive():
            self.stdin_channel.stop()
        if self.hb_channel.is_alive():
            self.hb_channel.stop()

    @property
    def channels_running(self):
        """Are any of the channels created and running?"""
        return (self.shell_channel.is_alive() or self.sub_channel.is_alive() or
                self.stdin_channel.is_alive() or self.hb_channel.is_alive())

    #--------------------------------------------------------------------------
    # Kernel process management methods:
    #--------------------------------------------------------------------------

    def cleanup_connection_file(self):
        """cleanup connection file *if we wrote it*

        Will not raise if the connection file was already removed somehow.
        """
        if self._connection_file_written:
            # cleanup connection files on full shutdown of kernel we started
            self._connection_file_written = False
            try:
                os.remove(self.connection_file)
            except OSError:
                pass

    def load_connection_file(self):
        """load connection info from JSON dict in self.connection_file"""
        with open(self.connection_file) as f:
            cfg = json.loads(f.read())

        self.ip = cfg['ip']
        self.shell_port = cfg['shell_port']
        self.stdin_port = cfg['stdin_port']
        self.iopub_port = cfg['iopub_port']
        self.hb_port = cfg['hb_port']
        self.session.key = str_to_bytes(cfg['key'])

    def write_connection_file(self):
        """write connection info to JSON dict in self.connection_file"""
        if self._connection_file_written:
            return
        self.connection_file,cfg = write_connection_file(self.connection_file,
            ip=self.ip, key=self.session.key,
            stdin_port=self.stdin_port, iopub_port=self.iopub_port,
            shell_port=self.shell_port, hb_port=self.hb_port)
        # write_connection_file also sets default ports:
        self.shell_port = cfg['shell_port']
        self.stdin_port = cfg['stdin_port']
        self.iopub_port = cfg['iopub_port']
        self.hb_port = cfg['hb_port']

        self._connection_file_written = True

    def start_kernel(self, **kw):
        """Starts a kernel process and configures the manager to use it.

        If random ports (port=0) are being used, this method must be called
        before the channels are created.

        Parameters:
        -----------
        launcher : callable, optional (default None)
             A custom function for launching the kernel process (generally a
             wrapper around ``entry_point.base_launch_kernel``). In most cases,
             it should not be necessary to use this parameter.

        **kw : optional
             See respective options for IPython and Python kernels.
        """
        if self.ip not in LOCAL_IPS:
            raise RuntimeError("Can only launch a kernel on a local interface. "
                               "Make sure that the '*_address' attributes are "
                               "configured properly. "
                               "Currently valid addresses are: %s"%LOCAL_IPS
                               )

        # write connection file / get default ports
        self.write_connection_file()

        self._launch_args = kw.copy()
        launch_kernel = kw.pop('launcher', None)
        if launch_kernel is None:
            from ipkernel import launch_kernel
        self.kernel = launch_kernel(fname=self.connection_file, **kw)

    def shutdown_kernel(self, restart=False):
        """ Attempts to the stop the kernel process cleanly. If the kernel
        cannot be stopped, it is killed, if possible.
        """
        # FIXME: Shutdown does not work on Windows due to ZMQ errors!
        if sys.platform == 'win32':
            self.kill_kernel()
            return

        # Pause the heart beat channel if it exists.
        if self._hb_channel is not None:
            self._hb_channel.pause()

        # Don't send any additional kernel kill messages immediately, to give
        # the kernel a chance to properly execute shutdown actions. Wait for at
        # most 1s, checking every 0.1s.
        self.shell_channel.shutdown(restart=restart)
        for i in range(10):
            if self.is_alive:
                time.sleep(0.1)
            else:
                break
        else:
            # OK, we've waited long enough.
            if self.has_kernel:
                self.kill_kernel()

        if not restart and self._connection_file_written:
            # cleanup connection files on full shutdown of kernel we started
            self._connection_file_written = False
            try:
                os.remove(self.connection_file)
            except IOError:
                pass

    def restart_kernel(self, now=False, **kw):
        """Restarts a kernel with the arguments that were used to launch it.

        If the old kernel was launched with random ports, the same ports will be
        used for the new kernel.

        Parameters
        ----------
        now : bool, optional
            If True, the kernel is forcefully restarted *immediately*, without
            having a chance to do any cleanup action.  Otherwise the kernel is
            given 1s to clean up before a forceful restart is issued.

            In all cases the kernel is restarted, the only difference is whether
            it is given a chance to perform a clean shutdown or not.

        **kw : optional
            Any options specified here will replace those used to launch the
            kernel.
        """
        if self._launch_args is None:
            raise RuntimeError("Cannot restart the kernel. "
                               "No previous call to 'start_kernel'.")
        else:
            # Stop currently running kernel.
            if self.has_kernel:
                if now:
                    self.kill_kernel()
                else:
                    self.shutdown_kernel(restart=True)

            # Start new kernel.
            self._launch_args.update(kw)
            self.start_kernel(**self._launch_args)

            # FIXME: Messages get dropped in Windows due to probable ZMQ bug
            # unless there is some delay here.
            if sys.platform == 'win32':
                time.sleep(0.2)

    @property
    def has_kernel(self):
        """Returns whether a kernel process has been specified for the kernel
        manager.
        """
        return self.kernel is not None

    def kill_kernel(self):
        """ Kill the running kernel. """
        if self.has_kernel:
            # Pause the heart beat channel if it exists.
            if self._hb_channel is not None:
                self._hb_channel.pause()

            # Attempt to kill the kernel.
            try:
                self.kernel.kill()
            except OSError, e:
                # In Windows, we will get an Access Denied error if the process
                # has already terminated. Ignore it.
                if sys.platform == 'win32':
                    if e.winerror != 5:
                        raise
                # On Unix, we may get an ESRCH error if the process has already
                # terminated. Ignore it.
                else:
                    from errno import ESRCH
                    if e.errno != ESRCH:
                        raise
            self.kernel = None
        else:
            raise RuntimeError("Cannot kill kernel. No kernel is running!")

    def interrupt_kernel(self):
        """ Interrupts the kernel. Unlike ``signal_kernel``, this operation is
        well supported on all platforms.
        """
        if self.has_kernel:
            if sys.platform == 'win32':
                from parentpoller import ParentPollerWindows as Poller
                Poller.send_interrupt(self.kernel.win32_interrupt_event)
            else:
                self.kernel.send_signal(signal.SIGINT)
        else:
            raise RuntimeError("Cannot interrupt kernel. No kernel is running!")

    def signal_kernel(self, signum):
        """ Sends a signal to the kernel. Note that since only SIGTERM is
        supported on Windows, this function is only useful on Unix systems.
        """
        if self.has_kernel:
            self.kernel.send_signal(signum)
        else:
            raise RuntimeError("Cannot signal kernel. No kernel is running!")

    @property
    def is_alive(self):
        """Is the kernel process still running?"""
        if self.has_kernel:
            if self.kernel.poll() is None:
                return True
            else:
                return False
        elif self._hb_channel is not None:
            # We didn't start the kernel with this KernelManager so we
            # use the heartbeat.
            return self._hb_channel.is_beating()
        else:
            # no heartbeat and not local, we can't tell if it's running,
            # so naively return True
            return True

    #--------------------------------------------------------------------------
    # Channels used for communication with the kernel:
    #--------------------------------------------------------------------------

    @property
    def shell_channel(self):
        """Get the REQ socket channel object to make requests of the kernel."""
        if self._shell_channel is None:
            self._shell_channel = self.shell_channel_class(self.context,
                                                         self.session,
                                                         (self.ip, self.shell_port))
        return self._shell_channel

    @property
    def sub_channel(self):
        """Get the SUB socket channel object."""
        if self._sub_channel is None:
            self._sub_channel = self.sub_channel_class(self.context,
                                                       self.session,
                                                       (self.ip, self.iopub_port))
        return self._sub_channel

    @property
    def stdin_channel(self):
        """Get the REP socket channel object to handle stdin (raw_input)."""
        if self._stdin_channel is None:
            self._stdin_channel = self.stdin_channel_class(self.context,
                                                       self.session,
                                                       (self.ip, self.stdin_port))
        return self._stdin_channel

    @property
    def hb_channel(self):
        """Get the heartbeat socket channel object to check that the
        kernel is alive."""
        if self._hb_channel is None:
            self._hb_channel = self.hb_channel_class(self.context,
                                                       self.session,
                                                       (self.ip, self.hb_port))
        return self._hb_channel


"""The IPython ZMQ-based parallel computing interface.

Authors:

* MinRK
"""
#-----------------------------------------------------------------------------
#  Copyright (C) 2011 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 warnings

import zmq

from IPython.config.configurable import MultipleInstanceError
from IPython.zmq import check_for_zmq

if os.name == 'nt':
    min_pyzmq = '2.1.7'
else:
    min_pyzmq = '2.1.4'

check_for_zmq(min_pyzmq, 'IPython.parallel')

from IPython.utils.pickleutil import Reference

from .client.asyncresult import *
from .client.client import Client
from .client.remotefunction import *
from .client.view import *
from .controller.dependency import *
from .error import *
from .util import interactive

#-----------------------------------------------------------------------------
# Functions
#-----------------------------------------------------------------------------

def bind_kernel(**kwargs):
    """Bind an Engine's Kernel to be used as a full IPython kernel.

    This allows a running Engine to be used simultaneously as a full IPython kernel
    with the QtConsole or other frontends.

    This function returns immediately.
    """
    from IPython.zmq.ipkernel import IPKernelApp
    from IPython.parallel.apps.ipengineapp import IPEngineApp

    # first check for IPKernelApp, in which case this should be a no-op
    # because there is already a bound kernel
    if IPKernelApp.initialized() and isinstance(IPKernelApp._instance, IPKernelApp):
        return

    if IPEngineApp.initialized():
        try:
            app = IPEngineApp.instance()
        except MultipleInstanceError:
            pass
        else:
            return app.bind_kernel(**kwargs)

    raise RuntimeError("bind_kernel be called from an IPEngineApp instance")


# Copyright 2015 Rackspace
# All Rights Reserved.
#
#    Licensed under the Apache License, Version 2.0 (the "License");
#    you may not use this file except in compliance with the
#    License. You may obtain a copy of the License at
#
#         http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing,
#    software distributed under the License is distributed on an "AS
#    IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
#    express or implied. See the License for the specific language
#    governing permissions and limitations under the License.

from __future__ import print_function

import abc
import inspect
import os
import sys
import traceback

import six

from timid import entry
from timid import utils

NAMESPACE_EXTENSIONS = 'timid.extensions'

@six.add_metaclass(abc.ABCMeta)
class Extension(object):
    """
    Represent an extension.  A timid extension provides functionality
    beyond that provided by actions or modifiers, in that it is able
    to add arguments to the CLI interface, provide alternate
    reporters, insert steps, etc.
    """

    @classmethod
    def prepare(cls, parser):
        """
        Called to prepare the extension.  The extension is prepared during
        argument parser preparation.  An extension implementing this
        method is able to add command line arguments specific for that
        extension.  Note that this is a class method; the extension
        will not be instantiated prior to calling this method, nor
        should this method attempt to initialize the extension.

        :param parser: The argument parser, an instance of
                       ``argparse.ArgumentParser``.
        """

        pass  # pragma: no cover

    @classmethod
    def activate(cls, ctxt, args):
        """
        Called to determine whether to activate the extension.  This call
        is made after processing command line arguments, and must
        return either ``None`` or an initialized instance of the
        extension.  Note that this is a class method.

        :param ctxt: An instance of ``timid.context.Context``.
        :param args: An instance of ``argparse.Namespace`` containing
                     the result of processing command line arguments.

        :returns: An instance of the extension class if the extension
                  has been activated, ``None`` if it has not.  If this
                  method returns ``None``, no further extension
                  methods will be called.
        """

        return None

    def read_steps(self, ctxt, steps):
        """
        Called after reading steps, prior to adding them to the list of
        test steps.  This allows an extension to alter the list (in
        place).

        :param ctxt: An instance of ``timid.context.Context``.
        :param steps: A list of ``timid.steps.Step`` instances.
        """

        pass  # pragma: no cover

    def pre_step(self, ctxt, step, idx):
        """
        Called prior to executing a step.

        :param ctxt: An instance of ``timid.context.Context``.
        :param step: An instance of ``timid.steps.Step`` describing
                     the step to be executed.
        :param idx: The index of the step in the list of steps.

        :returns: A ``True`` value if the step is to be skipped.  Any
                  ``False`` value (including ``None``) will result in
                  the step being executed as normal.
        """

        return None

    def post_step(self, ctxt, step, idx, result):
        """
        Called after executing a step.

        :param ctxt: An instance of ``timid.context.Context``.
        :param step: An instance of ``timid.steps.Step`` describing
                     the step that was executed.
        :param idx: The index of the step in the list of steps.
        :param result: An instance of ``timid.steps.StepResult``
                       describing the result of executing the step.
                       May be altered by the extension, e.g., to set
                       the ``ignore`` attribute.
        """

        pass  # pragma: no cover

    def finalize(self, ctxt, result):
        """
        Called at the end of processing.  This call allows the extension
        to emit any additional data, such as timing information, prior
        to ``timid``'s exit.  The extension may also alter the return
        value.

        :param ctxt: An instance of ``timid.context.Context``.
        :param result: The return value of the basic ``timid`` call,
                       or an ``Exception`` instance if an exception
                       was raised.  Without the extension, this would
                       be passed directly to ``sys.exit()``.

        :returns: Should return ``result`` unless the extension wishes
                  to change the return value.
        """

        return result

    @abc.abstractproperty
    def priority(self):
        """
        An integer value.  This provides a primitive means of controlling
        the order in which extensions will be called.  The higher the
        number, the later the modifier will be applied.

        This must be a class attribute, not a property.
        """

        pass  # pragma: no cover

class ExtensionDebugger(object):
    """
    A context manager to facilitate debugging of extensions.  Under
    normal circumstances, exceptions raised by an extension are
    ignored, but a developer working on an extension can set the
    environment variable ``TIMID_EXTENSION_DEBUG`` to enable debugging
    output.  If the environment variable is just present and set to a
    non-zero integer value, basic debugging is enabled that will emit
    a stack trace for any exception generated by an extension.  Higher
    integer values of ``TIMID_EXTENSION_DEBUG`` will emit further
    debugging information for each extension method call.
    """

    def __init__(self, method):
        """
        Initialize an ``ExtensionDebugger`` instance.

        :param where: A string indicating the name of the extension
                      method that will be called.
        """

        # Save the method and initialize the ext_cls tracker
        self.method = method
        self.ext_cls = None

        # Are we enabling debugging?
        debug = os.environ.get('TIMID_EXTENSION_DEBUG')
        if debug is None:
            self._debug = 0
        else:
            try:
                # The max() ensures -1 becomes 0
                self._debug = max(0, int(debug))
            except ValueError:
                # Not an integer value, just set it to 1
                self._debug = 1

        # If we're in level 2 debugging, log what's about to be called
        self.debug(2, 'Calling extension method "%s()"' % self.method)

    def __enter__(self):
        """
        Called upon entry to the context manager.

        :returns: The ``ExtensionDebugger`` instance.
        """

        return self

    def __exit__(self, exc_type, exc_value, exc_tb):
        """
        Called upon exit from the context manager.  Implements the
        debugging output.

        :param exc_type: The exception type.  If no exception was
                         generated, will be ``None``.
        :param exc_value: The exception value.  If no exception was
                          generated, will be ``None``.
        :param exc_tb: The exception traceback.  If no exception was
                       generated, will be ``None``.

        :returns: A ``True`` value if any exception should be ignored,
                  ``False`` otherwise.  Will return ``True`` for all
                  ``Exception`` subclasses unless debugging is
                  enabled.
        """

        # Are we in debugging mode?  Was there an exception?
        if self._debug >= 1 and exc_type is not None:
            # Emit a traceback
            traceback.print_exception(exc_type, exc_value, exc_tb,
                                      file=sys.stderr)

            # Exit with an informative message
            sys.exit('Extension failure calling "%s()" for extension "%s.%s"' %
                     (self.method, self.ext_cls.__module__,
                      self.ext_cls.__name__))

            # Just in case; shouldn't ever be reached except in the
            # test suite
            return False

        # Clear the extension class
        self.ext_cls = None
        return exc_type and issubclass(exc_type, Exception)

    def __call__(self, ext):
        """
        Called to save the extension class that is being processed.

        :param ext: A subclass of ``Extension``, or an instance of a
                    subclass of ``Extension``.  This is the extension
                    that is about to be called.

        :returns: The ``ExtensionDebugger`` instance, for convenience.
        """

        # Save the extension class
        self.ext_cls = ext if inspect.isclass(ext) else ext.__class__

        # If the highest level of debugging is set, log which
        # extension we're about to call
        self.debug(3, 'Calling extension "%s.%s" method "%s()"' %
                   (self.ext_cls.__module__, self.ext_cls.__name__,
                    self.method))

        return self

    def debug(self, level, message):
        """
        Emit a debugging message depending on the debugging level.

        :param level: The debugging level.
        :param message: The message to emit.
        """

        if self._debug >= level:
            print(message, file=sys.stderr)

class ExtensionSet(object):
    """
    Maintain a list of activated extensions.  This also provides
    convenience methods for invoking the extension methods correctly.
    """

    # An ordered cache of extension classes
    _extension_classes = None

    @classmethod
    def _get_extension_classes(cls):
        """
        Retrieve the extension classes in priority order.

        :returns: A list of extension classes, in proper priority
                  order.
        """

        if cls._extension_classes is None:
            exts = {}

            # Iterate over the entrypoints
            for ext in entry.points[NAMESPACE_EXTENSIONS]:
                exts.setdefault(ext.priority, [])
                exts[ext.priority].append(ext)

            # Save the list of extension classes
            cls._extension_classes = list(utils.iter_prio_dict(exts))

        return cls._extension_classes

    @classmethod
    def prepare(cls, parser):
        """
        Prepare all the extensions.  Extensions are prepared during
        argument parser preparation.  An extension implementing the
        ``prepare()`` method is able to add command line arguments
        specific for that extension.

        :param parser: The argument parser, an instance of
                       ``argparse.ArgumentParser``.
        """

        debugger = ExtensionDebugger('prepare')

        for ext in cls._get_extension_classes():
            with debugger(ext):
                ext.prepare(parser)

    @classmethod
    def activate(cls, ctxt, args):
        """
        Initialize the extensions.  This loops over each extension
        invoking its ``activate()`` method; those extensions that
        return an object are considered "activated" and will be called
        at later phases of extension processing.

        :param ctxt: An instance of ``timid.context.Context``.
        :param args: An instance of ``argparse.Namespace`` containing
                     the result of processing command line arguments.

        :returns: An instance of ``ExtensionSet``.
        """

        debugger = ExtensionDebugger('activate')

        exts = []
        for ext in cls._get_extension_classes():
            # Not using debugger as a context manager here, because we
            # want to know about the exception even if we're ignoring
            # it...but we need to notify which extension is being
            # processed!
            debugger(ext)

            try:
                # Check if the extension is being activated
                obj = ext.activate(ctxt, args)
            except Exception:
                # Hmmm, failed to activate; handle the error
                exc_info = sys.exc_info()
                if not debugger.__exit__(*exc_info):
                    six.reraise(*exc_info)
            else:
                # OK, if the extension is activated, use it
                if obj is not None:
                    exts.append(obj)
                    debugger.debug(2, 'Activating extension "%s.%s"' %
                                   (ext.__module__, ext.__name__))

        # Initialize and return the ExtensionSet
        return cls(exts)

    def __init__(self, exts=None):
        """
        Initialize an ``ExtensionSet`` instance.

        :param exts: A list of extensions that have been activated.
        """

        self.exts = exts or []

    def read_steps(self, ctxt, steps):
        """
        Called after reading steps, prior to adding them to the list of
        test steps.  Extensions are able to alter the list (in place).

        :param ctxt: An instance of ``timid.context.Context``.
        :param steps: A list of ``timid.steps.Step`` instances.

        :returns: The ``steps`` parameter, for convenience.
        """

        debugger = ExtensionDebugger('read_steps')

        for ext in self.exts:
            with debugger(ext):
                ext.read_steps(ctxt, steps)

        # Convenience return
        return steps

    def pre_step(self, ctxt, step, idx):
        """
        Called prior to executing a step.

        :param ctxt: An instance of ``timid.context.Context``.
        :param step: An instance of ``timid.steps.Step`` describing
                     the step to be executed.
        :param idx: The index of the step in the list of steps.

        :returns: A ``True`` value if the step is to be skipped,
                  ``False`` otherwise.
        """

        debugger = ExtensionDebugger('pre_step')

        for ext in self.exts:
            with debugger(ext):
                if ext.pre_step(ctxt, step, idx):
                    # Step must be skipped
                    debugger.debug(3, 'Skipping step %d' % idx)
                    return True

        return False

    def post_step(self, ctxt, step, idx, result):
        """
        Called after executing a step.

        :param ctxt: An instance of ``timid.context.Context``.
        :param step: An instance of ``timid.steps.Step`` describing
                     the step that was executed.
        :param idx: The index of the step in the list of steps.
        :param result: An instance of ``timid.steps.StepResult``
                       describing the result of executing the step.
                       May be altered by the extension, e.g., to set
                       the ``ignore`` attribute.

        :returns: The ``result`` parameter, for convenience.
        """

        debugger = ExtensionDebugger('post_step')

        for ext in self.exts:
            with debugger(ext):
                ext.post_step(ctxt, step, idx, result)

        # Convenience return
        return result

    def finalize(self, ctxt, result):
        """
        Called at the end of processing.  This call allows extensions to
        emit any additional data, such as timing information, prior to
        ``timid``'s exit.  Extensions may also alter the return value.

        :param ctxt: An instance of ``timid.context.Context``.
        :param result: The return value of the basic ``timid`` call,
                       or an ``Exception`` instance if an exception
                       was raised.  Without the extension, this would
                       be passed directly to ``sys.exit()``.

        :returns: The final result.
        """

        debugger = ExtensionDebugger('finalize')

        for ext in self.exts:
            with debugger(ext):
                result = ext.finalize(ctxt, result)

        return result


"""setuptools.command.bdist_egg

Build .egg distributions"""

# This module should be kept compatible with Python 2.3
import sys, os, marshal
from setuptools import Command
from distutils.dir_util import remove_tree, mkpath
try:
    from distutils.sysconfig import get_python_version, get_python_lib
except ImportError:
    from sysconfig import get_python_version
    from distutils.sysconfig import get_python_lib

from distutils import log
from distutils.errors import DistutilsSetupError
from pkg_resources import get_build_platform, Distribution, ensure_directory
from pkg_resources import EntryPoint
from types import CodeType
from setuptools.extension import Library

def strip_module(filename):
    if '.' in filename:
        filename = os.path.splitext(filename)[0]
    if filename.endswith('module'):
        filename = filename[:-6]
    return filename

def write_stub(resource, pyfile):
    f = open(pyfile,'w')
    f.write('\n'.join([
        "def __bootstrap__():",
        "   global __bootstrap__, __loader__, __file__",
        "   import sys, pkg_resources, imp",
        "   __file__ = pkg_resources.resource_filename(__name__,%r)"
            % resource,
        "   __loader__ = None; del __bootstrap__, __loader__",
        "   imp.load_dynamic(__name__,__file__)",
        "__bootstrap__()",
        "" # terminal \n
    ]))
    f.close()

# stub __init__.py for packages distributed without one
NS_PKG_STUB = '__import__("pkg_resources").declare_namespace(__name__)'

class bdist_egg(Command):

    description = "create an \"egg\" distribution"

    user_options = [
        ('bdist-dir=', 'b',
            "temporary directory for creating the distribution"),
        ('plat-name=', 'p',
                     "platform name to embed in generated filenames "
                     "(default: %s)" % get_build_platform()),
        ('exclude-source-files', None,
                     "remove all .py files from the generated egg"),
        ('keep-temp', 'k',
                     "keep the pseudo-installation tree around after " +
                     "creating the distribution archive"),
        ('dist-dir=', 'd',
                     "directory to put final built distributions in"),
        ('skip-build', None,
                     "skip rebuilding everything (for testing/debugging)"),
    ]

    boolean_options = [
        'keep-temp', 'skip-build', 'exclude-source-files'
    ]

    def initialize_options (self):
        self.bdist_dir = None
        self.plat_name = None
        self.keep_temp = 0
        self.dist_dir = None
        self.skip_build = 0
        self.egg_output = None
        self.exclude_source_files = None

    def finalize_options(self):
        ei_cmd = self.ei_cmd = self.get_finalized_command("egg_info")
        self.egg_info = ei_cmd.egg_info

        if self.bdist_dir is None:
            bdist_base = self.get_finalized_command('bdist').bdist_base
            self.bdist_dir = os.path.join(bdist_base, 'egg')

        if self.plat_name is None:
            self.plat_name = get_build_platform()

        self.set_undefined_options('bdist',('dist_dir', 'dist_dir'))

        if self.egg_output is None:

            # Compute filename of the output egg
            basename = Distribution(
                None, None, ei_cmd.egg_name, ei_cmd.egg_version,
                get_python_version(),
                self.distribution.has_ext_modules() and self.plat_name
            ).egg_name()

            self.egg_output = os.path.join(self.dist_dir, basename+'.egg')

    def do_install_data(self):
        # Hack for packages that install data to install's --install-lib
        self.get_finalized_command('install').install_lib = self.bdist_dir

        site_packages = os.path.normcase(os.path.realpath(get_python_lib()))
        old, self.distribution.data_files = self.distribution.data_files,[]

        for item in old:
            if isinstance(item,tuple) and len(item)==2:
                if os.path.isabs(item[0]):
                    realpath = os.path.realpath(item[0])
                    normalized = os.path.normcase(realpath)
                    if normalized==site_packages or normalized.startswith(
                        site_packages+os.sep
                    ):
                        item = realpath[len(site_packages)+1:], item[1]
                    # XXX else: raise ???
            self.distribution.data_files.append(item)

        try:
            log.info("installing package data to %s" % self.bdist_dir)
            self.call_command('install_data', force=0, root=None)
        finally:
            self.distribution.data_files = old

    def get_outputs(self):
        return [self.egg_output]

    def call_command(self,cmdname,**kw):
        """Invoke reinitialized command `cmdname` with keyword args"""
        for dirname in INSTALL_DIRECTORY_ATTRS:
            kw.setdefault(dirname,self.bdist_dir)
        kw.setdefault('skip_build',self.skip_build)
        kw.setdefault('dry_run', self.dry_run)
        cmd = self.reinitialize_command(cmdname, **kw)
        self.run_command(cmdname)
        return cmd

    def run(self):
        # Generate metadata first
        self.run_command("egg_info")

        # We run install_lib before install_data, because some data hacks
        # pull their data path from the install_lib command.
        log.info("installing library code to %s" % self.bdist_dir)
        instcmd = self.get_finalized_command('install')
        old_root = instcmd.root; instcmd.root = None
        cmd = self.call_command('install_lib', warn_dir=0)
        instcmd.root = old_root

        all_outputs, ext_outputs = self.get_ext_outputs()
        self.stubs = []
        to_compile = []
        for (p,ext_name) in enumerate(ext_outputs):
            filename,ext = os.path.splitext(ext_name)
            pyfile = os.path.join(self.bdist_dir, strip_module(filename)+'.py')
            self.stubs.append(pyfile)
            log.info("creating stub loader for %s" % ext_name)
            if not self.dry_run:
                write_stub(os.path.basename(ext_name), pyfile)
            to_compile.append(pyfile)
            ext_outputs[p] = ext_name.replace(os.sep,'/')

        to_compile.extend(self.make_init_files())
        if to_compile:
            cmd.byte_compile(to_compile)

        if self.distribution.data_files:
            self.do_install_data()

        # Make the EGG-INFO directory
        archive_root = self.bdist_dir
        egg_info = os.path.join(archive_root,'EGG-INFO')
        self.mkpath(egg_info)
        if self.distribution.scripts:
            script_dir = os.path.join(egg_info, 'scripts')
            log.info("installing scripts to %s" % script_dir)
            self.call_command('install_scripts',install_dir=script_dir,no_ep=1)

        self.copy_metadata_to(egg_info)
        native_libs = os.path.join(egg_info, "native_libs.txt")
        if all_outputs:
            log.info("writing %s" % native_libs)
            if not self.dry_run:
                ensure_directory(native_libs)
                libs_file = open(native_libs, 'wt')
                libs_file.write('\n'.join(all_outputs))
                libs_file.write('\n')
                libs_file.close()
        elif os.path.isfile(native_libs):
            log.info("removing %s" % native_libs)
            if not self.dry_run:
                os.unlink(native_libs)

        write_safety_flag(
            os.path.join(archive_root,'EGG-INFO'), self.zip_safe()
        )

        if os.path.exists(os.path.join(self.egg_info,'depends.txt')):
            log.warn(
                "WARNING: 'depends.txt' will not be used by setuptools 0.6!\n"
                "Use the install_requires/extras_require setup() args instead."
            )

        if self.exclude_source_files:
            self.zap_pyfiles()

        # Make the archive
        make_zipfile(self.egg_output, archive_root, verbose=self.verbose,
                          dry_run=self.dry_run, mode=self.gen_header())
        if not self.keep_temp:
            remove_tree(self.bdist_dir, dry_run=self.dry_run)

        # Add to 'Distribution.dist_files' so that the "upload" command works
        getattr(self.distribution,'dist_files',[]).append(
            ('bdist_egg',get_python_version(),self.egg_output))

    def zap_pyfiles(self):
        log.info("Removing .py files from temporary directory")
        for base,dirs,files in walk_egg(self.bdist_dir):
            for name in files:
                if name.endswith('.py'):
                    path = os.path.join(base,name)
                    log.debug("Deleting %s", path)
                    os.unlink(path)

    def zip_safe(self):
        safe = getattr(self.distribution,'zip_safe',None)
        if safe is not None:
            return safe
        log.warn("zip_safe flag not set; analyzing archive contents...")
        return analyze_egg(self.bdist_dir, self.stubs)

    def make_init_files(self):
        """Create missing package __init__ files"""
        init_files = []
        for base,dirs,files in walk_egg(self.bdist_dir):
            if base==self.bdist_dir:
                # don't put an __init__ in the root
                continue
            for name in files:
                if name.endswith('.py'):
                    if '__init__.py' not in files:
                        pkg = base[len(self.bdist_dir)+1:].replace(os.sep,'.')
                        if self.distribution.has_contents_for(pkg):
                            log.warn("Creating missing __init__.py for %s",pkg)
                            filename = os.path.join(base,'__init__.py')
                            if not self.dry_run:
                                f = open(filename,'w'); f.write(NS_PKG_STUB)
                                f.close()
                            init_files.append(filename)
                    break
            else:
                # not a package, don't traverse to subdirectories
                dirs[:] = []

        return init_files

    def gen_header(self):
        epm = EntryPoint.parse_map(self.distribution.entry_points or '')
        ep = epm.get('setuptools.installation',{}).get('eggsecutable')
        if ep is None:
            return 'w'  # not an eggsecutable, do it the usual way.

        if not ep.attrs or ep.extras:
            raise DistutilsSetupError(
                "eggsecutable entry point (%r) cannot have 'extras' "
                "or refer to a module" % (ep,)
            )

        pyver = sys.version[:3]
        pkg = ep.module_name
        full = '.'.join(ep.attrs)
        base = ep.attrs[0]
        basename = os.path.basename(self.egg_output)

        header = (
            "#!/bin/sh\n"
            'if [ `basename $0` = "%(basename)s" ]\n'
            'then exec python%(pyver)s -c "'
            "import sys, os; sys.path.insert(0, os.path.abspath('$0')); "
            "from %(pkg)s import %(base)s; sys.exit(%(full)s())"
            '" "$@"\n'
            'else\n'
            '  echo $0 is not the correct name for this egg file.\n'
            '  echo Please rename it back to %(basename)s and try again.\n'
            '  exec false\n'
            'fi\n'

        ) % locals()

        if not self.dry_run:
            mkpath(os.path.dirname(self.egg_output), dry_run=self.dry_run)
            f = open(self.egg_output, 'w')
            f.write(header)
            f.close()
        return 'a'

    def copy_metadata_to(self, target_dir):
        "Copy metadata (egg info) to the target_dir"
        # normalize the path (so that a forward-slash in egg_info will
        # match using startswith below)
        norm_egg_info = os.path.normpath(self.egg_info)
        prefix = os.path.join(norm_egg_info,'')
        for path in self.ei_cmd.filelist.files:
            if path.startswith(prefix):
                target = os.path.join(target_dir, path[len(prefix):])
                ensure_directory(target)
                self.copy_file(path, target)

    def get_ext_outputs(self):
        """Get a list of relative paths to C extensions in the output distro"""

        all_outputs = []
        ext_outputs = []

        paths = {self.bdist_dir:''}
        for base, dirs, files in os.walk(self.bdist_dir):
            for filename in files:
                if os.path.splitext(filename)[1].lower() in NATIVE_EXTENSIONS:
                    all_outputs.append(paths[base]+filename)
            for filename in dirs:
                paths[os.path.join(base,filename)] = paths[base]+filename+'/'

        if self.distribution.has_ext_modules():
            build_cmd = self.get_finalized_command('build_ext')
            for ext in build_cmd.extensions:
                if isinstance(ext,Library):
                    continue
                fullname = build_cmd.get_ext_fullname(ext.name)
                filename = build_cmd.get_ext_filename(fullname)
                if not os.path.basename(filename).startswith('dl-'):
                    if os.path.exists(os.path.join(self.bdist_dir,filename)):
                        ext_outputs.append(filename)

        return all_outputs, ext_outputs

NATIVE_EXTENSIONS = dict.fromkeys('.dll .so .dylib .pyd'.split())

def walk_egg(egg_dir):
    """Walk an unpacked egg's contents, skipping the metadata directory"""
    walker = os.walk(egg_dir)
    base,dirs,files = walker.next()
    if 'EGG-INFO' in dirs:
        dirs.remove('EGG-INFO')
    yield base,dirs,files
    for bdf in walker:
        yield bdf

def analyze_egg(egg_dir, stubs):
    # check for existing flag in EGG-INFO
    for flag,fn in safety_flags.items():
        if os.path.exists(os.path.join(egg_dir,'EGG-INFO',fn)):
            return flag
    if not can_scan(): return False
    safe = True
    for base, dirs, files in walk_egg(egg_dir):
        for name in files:
            if name.endswith('.py') or name.endswith('.pyw'):
                continue
            elif name.endswith('.pyc') or name.endswith('.pyo'):
                # always scan, even if we already know we're not safe
                safe = scan_module(egg_dir, base, name, stubs) and safe
    return safe

def write_safety_flag(egg_dir, safe):
    # Write or remove zip safety flag file(s)
    for flag,fn in safety_flags.items():
        fn = os.path.join(egg_dir, fn)
        if os.path.exists(fn):
            if safe is None or bool(safe)<>flag:
                os.unlink(fn)
        elif safe is not None and bool(safe)==flag:
            f=open(fn,'wt'); f.write('\n'); f.close()

safety_flags = {
    True: 'zip-safe',
    False: 'not-zip-safe',
}

def scan_module(egg_dir, base, name, stubs):
    """Check whether module possibly uses unsafe-for-zipfile stuff"""

    filename = os.path.join(base,name)
    if filename[:-1] in stubs:
        return True     # Extension module
    pkg = base[len(egg_dir)+1:].replace(os.sep,'.')
    module = pkg+(pkg and '.' or '')+os.path.splitext(name)[0]
    if sys.version_info < (3, 3):
        skip = 8   # skip magic & date
    else:
        skip = 12  # skip magic & date & file size
    f = open(filename,'rb'); f.read(skip)
    code = marshal.load(f); f.close()
    safe = True
    symbols = dict.fromkeys(iter_symbols(code))
    for bad in ['__file__', '__path__']:
        if bad in symbols:
            log.warn("%s: module references %s", module, bad)
            safe = False
    if 'inspect' in symbols:
        for bad in [
            'getsource', 'getabsfile', 'getsourcefile', 'getfile'
            'getsourcelines', 'findsource', 'getcomments', 'getframeinfo',
            'getinnerframes', 'getouterframes', 'stack', 'trace'
        ]:
            if bad in symbols:
                log.warn("%s: module MAY be using inspect.%s", module, bad)
                safe = False
    if '__name__' in symbols and '__main__' in symbols and '.' not in module:
        if sys.version[:3]=="2.4":  # -m works w/zipfiles in 2.5
            log.warn("%s: top-level module may be 'python -m' script", module)
            safe = False
    return safe

def iter_symbols(code):
    """Yield names and strings used by `code` and its nested code objects"""
    for name in code.co_names: yield name
    for const in code.co_consts:
        if isinstance(const,basestring):
            yield const
        elif isinstance(const,CodeType):
            for name in iter_symbols(const):
                yield name

def can_scan():
    if not sys.platform.startswith('java') and sys.platform != 'cli':
        # CPython, PyPy, etc.
        return True
    log.warn("Unable to analyze compiled code on this platform.")
    log.warn("Please ask the author to include a 'zip_safe'"
             " setting (either True or False) in the package's setup.py")

# Attribute names of options for commands that might need to be convinced to
# install to the egg build directory

INSTALL_DIRECTORY_ATTRS = [
    'install_lib', 'install_dir', 'install_data', 'install_base'
]

def make_zipfile(zip_filename, base_dir, verbose=0, dry_run=0, compress=None,
    mode='w'
):
    """Create a zip file from all the files under 'base_dir'.  The output
    zip file will be named 'base_dir' + ".zip".  Uses either the "zipfile"
    Python module (if available) or the InfoZIP "zip" utility (if installed
    and found on the default search path).  If neither tool is available,
    raises DistutilsExecError.  Returns the name of the output zip file.
    """
    import zipfile
    mkpath(os.path.dirname(zip_filename), dry_run=dry_run)
    log.info("creating '%s' and adding '%s' to it", zip_filename, base_dir)

    def visit(z, dirname, names):
        for name in names:
            path = os.path.normpath(os.path.join(dirname, name))
            if os.path.isfile(path):
                p = path[len(base_dir)+1:]
                if not dry_run:
                    z.write(path, p)
                log.debug("adding '%s'" % p)

    if compress is None:
        compress = (sys.version>="2.4") # avoid 2.3 zipimport bug when 64 bits

    compression = [zipfile.ZIP_STORED, zipfile.ZIP_DEFLATED][bool(compress)]
    if not dry_run:
        z = zipfile.ZipFile(zip_filename, mode, compression=compression)
        for dirname, dirs, files in os.walk(base_dir):
            visit(z, dirname, files)
        z.close()
    else:
        for dirname, dirs, files in os.walk(base_dir):
            visit(None, dirname, files)
    return zip_filename
#