1###
2# Copyright (c) 2005-2009, Jeremiah Fincher
3# Copyright (c) 2009-2010, James McCoy
4# All rights reserved.
5#
6# Redistribution and use in source and binary forms, with or without
7# modification, are permitted provided that the following conditions are met:
8#
9#   * Redistributions of source code must retain the above copyright notice,
10#     this list of conditions, and the following disclaimer.
11#   * Redistributions in binary form must reproduce the above copyright notice,
12#     this list of conditions, and the following disclaimer in the
13#     documentation and/or other materials provided with the distribution.
14#   * Neither the name of the author of this software nor the name of
15#     contributors to this software may be used to endorse or promote products
16#     derived from this software without specific prior written consent.
17#
18# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21# ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
22# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28# POSSIBILITY OF SUCH DAMAGE.
29###
30
31import sys
32import types
33import fnmatch
34import threading
35
36def universalImport(*names):
37    """Attempt to import the given modules, in order, returning the first
38    successfully imported module.  ImportError will be raised, as usual, if
39    no imports succeed.  To emulate ``from ModuleA import ModuleB'', pass the
40    string 'ModuleA.ModuleB'"""
41    f = sys._getframe(1)
42    for name in names:
43        try:
44            # __import__ didn't gain keyword arguments until 2.5
45            ret = __import__(name, f.f_globals)
46        except ImportError:
47            continue
48        else:
49            if '.' in name:
50                parts = name.split('.')[1:]
51                while parts:
52                    ret = getattr(ret, parts[0])
53                    del parts[0]
54            return ret
55    raise ImportError(','.join(names))
56
57def changeFunctionName(f, name, doc=None):
58    if doc is None:
59        doc = f.__doc__
60    if hasattr(f, '__closure__'):
61        closure = f.__closure__
62    else:
63        # Pypy
64        closure = f.func_closure
65    newf = types.FunctionType(f.__code__, f.__globals__, name,
66                              f.__defaults__, closure)
67    newf.__doc__ = doc
68    return newf
69
70class Object(object):
71    def __ne__(self, other):
72        return not self == other
73
74class MetaSynchronized(type):
75    METHODS = '__synchronized__'
76    LOCK = '_MetaSynchronized_rlock'
77    def __new__(cls, name, bases, dict):
78        sync = set()
79        for base in bases:
80            if hasattr(base, MetaSynchronized.METHODS):
81                sync.update(getattr(base, MetaSynchronized.METHODS))
82        if MetaSynchronized.METHODS in dict:
83            sync.update(dict[MetaSynchronized.METHODS])
84        if sync:
85            def synchronized(f):
86                def g(self, *args, **kwargs):
87                    lock = getattr(self, MetaSynchronized.LOCK)
88                    lock.acquire()
89                    try:
90                        f(self, *args, **kwargs)
91                    finally:
92                        lock.release()
93                return changeFunctionName(g, f.__name__, f.__doc__)
94            for attr in sync:
95                if attr in dict:
96                    dict[attr] = synchronized(dict[attr])
97            original__init__ = dict.get('__init__')
98            def __init__(self, *args, **kwargs):
99                if not hasattr(self, MetaSynchronized.LOCK):
100                    setattr(self, MetaSynchronized.LOCK, threading.RLock())
101                if original__init__:
102                    original__init__(self, *args, **kwargs)
103                else:
104                    # newclass is defined below.
105                    super(newclass, self).__init__(*args, **kwargs)
106            dict['__init__'] = __init__
107        newclass = super(MetaSynchronized, cls).__new__(cls, name, bases, dict)
108        return newclass
109Synchronized = MetaSynchronized('Synchronized', (), {})
110
111def glob2re(g):
112    pattern = fnmatch.translate(g)
113    if pattern.startswith('(?s:') and pattern.endswith(')\\Z'):
114        # Python >= 3.6
115        return pattern[4:-3] + '\\Z'
116    elif pattern.endswith('\\Z(?ms)'):
117        # Python >= 2.6 and < 3.6
118        #
119        # Translate glob to regular expression, trimming the "match EOL"
120        # portion of the regular expression.
121        # Some Python versions use \Z(?ms) per
122        # https://bugs.python.org/issue6665
123        return pattern[:-7]
124    else:
125        assert False, 'Python < 2.6, or unknown behavior of fnmatch.translate.'
126
127
128_debug_software_name = 'Limnoria'
129_debug_software_version = None
130# From http://code.activestate.com/recipes/52215-get-more-information-from-tracebacks/
131def collect_extra_debug_data():
132    """
133    Print the usual traceback information, followed by a listing of all the
134    local variables in each frame.
135    """
136    data = ''
137    try:
138        tb = sys.exc_info()[2]
139        stack = []
140
141        while tb:
142            stack.append(tb.tb_frame)
143            tb = tb.tb_next
144    finally:
145        del tb
146
147    if _debug_software_version:
148        data += '%s version: %s\n\n' % \
149                (_debug_software_name, _debug_software_version)
150    else:
151        data += '(Cannot get %s version.)\n\n' % _debug_software_name
152
153    data += 'Locals by frame, innermost last:\n'
154    for frame in stack:
155        data += '\n\n'
156        data += ('Frame %s in %s at line %s\n' % (frame.f_code.co_name,
157                                             frame.f_code.co_filename,
158                                             frame.f_lineno))
159        frame_locals = frame.f_locals
160        for inspected in ('self', 'cls'):
161            if inspected in frame_locals:
162                try:
163                    attribute_names = dir(frame_locals[inspected])
164                except Exception: # For Python 2 and Pypy
165                    try:
166                        attribute_names = list(
167                            frame_locals[inspected].__dict__)
168                    except Exception:
169                        attribute_names = []
170                for attr_name in attribute_names:
171                    try:
172                        v = getattr(frame_locals[inspected], attr_name)
173                    except Exception:
174                        v = '<ERROR WHILE GETTING VALUE>'
175                    frame_locals['%s.%s' % (inspected, attr_name)] = v
176        for key, value in frame_locals.items():
177            if key == '__builtins__':
178                # This is flooding
179                continue
180            data += ('\t%20s = ' % key)
181            #We have to be careful not to cause a new error in our error
182            #printer! Calling str() on an unknown object could cause an
183            #error we don't want.
184            try:
185                data += repr(value) + '\n'
186            except Exception:
187                data += '<ERROR WHILE PRINTING VALUE>\n'
188    data += '\n'
189    data += '+-----------------------+\n'
190    data += '| End of locals display |\n'
191    data += '+-----------------------+\n'
192    data += '\n'
193    return data
194
195# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=78:
196