Mercurial > ~astiob > upreckon > hgweb
diff upreckon/compat.py @ 146:d5b6708c1955
Distutils support, reorganization and cleaning up
* Removed command-line options -t and -u.
* Reorganized code:
o all modules are now in package upreckon;
o TestCaseNotPassed and its descendants now live in a separate
module exceptions;
o load_problem now lives in module problem.
* Commented out mentions of command-line option -c in --help.
* Added a distutils-based setup.py.
author | Oleg Oshmyan <chortos@inbox.lv> |
---|---|
date | Sat, 28 May 2011 14:24:25 +0100 |
parents | compat.py@92f76baebcc6 |
children | e0b2fbd7ebe0 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/upreckon/compat.py Sat May 28 14:24:25 2011 +0100 @@ -0,0 +1,235 @@ +# Copyright (c) 2010-2011 Chortos-2 <chortos@inbox.lv> + +# A compatibility layer for Python 2.5+. This is what lets test.py +# run on all versions of Python starting with 2.5, including Python 3. + +# A few notes regarding some compatibility-driven peculiarities +# in the use of the language that can be seen in all modules: +# +# * Except statements never specify target; instead, when needed, +# the exception is taken from sys.exc_info(). Blame the incompatible +# syntaxes of the except clause in Python 2.5 and Python 3 +# and the lack of preprocessor macros in Python of any version ;P. +# +# * Keyword-only parameters are never used, even for parameters +# that should never be given in as arguments. The reason is +# the laziness of some Python developers who have failed to finish +# implementing them in Python 2 even though they had several years +# of time and multiple version releases to sneak them in. +# +# * Abstract classes are only implemented for Python 2.6 and 2.7. +# ABC's require the abc module and the specification of metaclasses, +# but in Python 2.5, the abc module does not exist, while in Python 3, +# metaclasses are specified using a syntax totally incompatible +# with Python 2 and not usable conditionally via exec() and such +# because it is a detail of the syntax of the class statement itself. + +# Some code was adapted from Python 2.7.1 and its documentation. +# This code is clearly marked as such in preceding comments and is +# covered by copyright as follows: +# +# Copyright (c) 2001-2010 Python Software Foundation; all rights reserved. +# +# The code is used according to the PSF License Agreement +# for Python 2.7.1, whose full text is available from your local +# installation of Python (enter 'license()' in the interactive +# interpreter) or from the Web at the following URL: +# +# http://docs.python.org/2.7.1/license.html#terms-and-conditions-for-accessing-or-otherwise-using-python + +try: + import builtins +except ImportError: + import __builtin__ as builtins + +pseudobuiltins = ('say', 'basestring', 'range', 'map', 'zip', 'filter', 'next', + 'items', 'keys', 'values', 'zip_longest', 'callable', 'ceil') +__all__ = pseudobuiltins + ('ABCMeta', 'abstractmethod', 'CompatBuiltins') + +try: + # Python 3 + exec('say = print') +except SyntaxError: + try: + # Python 2.6/2.7 + # An alternative is exec('from __future__ import print_function; say = print'); + # if problems arise with the current line, one should try replacing it + # with this one with the future import before abandoning the idea altogether + say = getattr(builtins, 'print') + except Exception: + # Python 2.5 + import sys + # This should fully emulate the print function of Python 2.6 in Python 2.3+ + # The error messages are taken from Python 2.6 + # The name bindings at the bottom of this file are in effect + def saytypeerror(value, name): + return TypeError(' '.join((name, 'must be None, str or unicode, not', type(value).__name__))) + def say(*values, **kwargs): + sep = kwargs.pop('sep' , None) + end = kwargs.pop('end' , None) + file = kwargs.pop('file', None) + if kwargs: raise TypeError("'%s' is an invalid keyword argument for this function" % kwargs.popitem()[0]) + if sep is None: sep = ' ' + if end is None: end = '\n' + if file is None: file = sys.stdout + if not isinstance(sep, basestring): raise saytypeerror(sep, 'sep') + if not isinstance(end, basestring): raise saytypeerror(end, 'end') + file.write(sep.join(map(str, values)) + end) + +try: + from os.path import relpath +except ImportError: + # Python 2.5 + import os.path as _path + + # Adapted from Python 2.7.1 + + if hasattr(_path, 'splitunc'): + def _abspath_split(path): + abs = _path.abspath(_path.normpath(path)) + prefix, rest = _path.splitunc(abs) + is_unc = bool(prefix) + if not is_unc: + prefix, rest = _path.splitdrive(abs) + return is_unc, prefix, [x for x in rest.split(_path.sep) if x] + else: + def _abspath_split(path): + prefix, rest = _path.splitdrive(_path.abspath(_path.normpath(path))) + return False, prefix, [x for x in rest.split(_path.sep) if x] + + def relpath(path, start=_path.curdir): + """Return a relative version of a path""" + + if not path: + raise ValueError("no path specified") + + start_is_unc, start_prefix, start_list = _abspath_split(start) + path_is_unc, path_prefix, path_list = _abspath_split(path) + + if path_is_unc ^ start_is_unc: + raise ValueError("Cannot mix UNC and non-UNC paths (%s and %s)" + % (path, start)) + if path_prefix.lower() != start_prefix.lower(): + if path_is_unc: + raise ValueError("path is on UNC root %s, start on UNC root %s" + % (path_prefix, start_prefix)) + else: + raise ValueError("path is on drive %s, start on drive %s" + % (path_prefix, start_prefix)) + # Work out how much of the filepath is shared by start and path. + i = 0 + for e1, e2 in zip(start_list, path_list): + if e1.lower() != e2.lower(): + break + i += 1 + + rel_list = [_path.pardir] * (len(start_list)-i) + path_list[i:] + if not rel_list: + return _path.curdir + return _path.join(*rel_list) + + _path.relpath = relpath + +try: + from abc import ABCMeta, abstractmethod +except ImportError: + ABCMeta, abstractmethod = None, lambda x: x + +try: + basestring = basestring +except NameError: + basestring = str + +# xrange is set to support simple testconf.py's written for test.py 1.x +try: + xrange = range = xrange +except NameError: + xrange = range = range + +try: + callable = callable +except NameError: + from collections import Callable + callable = lambda obj: isinstance(obj, Callable) + +try: + next = next +except NameError: + next = lambda obj: obj.next() + +try: + from itertools import imap as map +except ImportError: + map = map + +try: + from itertools import izip as zip +except ImportError: + zip = zip + +try: + from itertools import ifilter as filter +except ImportError: + filter = filter + +try: + items = dict.iteritems +except AttributeError: + items = dict.items + +try: + keys = dict.iterkeys +except AttributeError: + keys = dict.keys + +try: + values = dict.itervalues +except AttributeError: + values = dict.values + +from math import ceil +if not isinstance(ceil(0), int): + def ceil(x): + y = int(x) + if y < x: y += 1 + return y + +try: + # Python 3 + from itertools import zip_longest +except ImportError: + try: + # Python 2.6/2.7 + from itertools import izip_longest as zip_longest + except ImportError: + # Python 2.5 + from itertools import chain, repeat + # Adapted from the documentation of itertools.izip_longest + def zip_longest(*args, **kwargs): + fillvalue = kwargs.get('fillvalue') + def sentinel(counter=([fillvalue]*(len(args)-1)).pop): + yield counter() + fillers = repeat(fillvalue) + iters = [chain(it, sentinel(), fillers) for it in args] + try: + for tup in zip(*iters): + yield tup + except IndexError: + pass + +# Automatically import * from this module into testconf.py's +class CompatBuiltins(object): + __slots__ = 'originals' + globals = globals() + def __enter__(self): + self.originals = {} + for name in pseudobuiltins: + try: + self.originals[name] = getattr(builtins, name) + except AttributeError: + pass + setattr(builtins, name, self.globals[name]) + return self + def __exit__(self, exc_type, exc_val, exc_tb): + for name in self.originals: + setattr(builtins, name, self.originals[name]) \ No newline at end of file