Mercurial > ~astiob > upreckon > hgweb
view upreckon/compat.py @ 205:166a23999bf7
Added confvar okexitcodemask; changed the validator protocol
Callable validators now return three-tuples (number granted, bool correct,
str comment) instead of two-tuples (number granted, str comment). They are
still allowed to return single numbers.
Callable validators must now explicitly raise
upreckon.exceptions.WrongAnswer if they want the verdict to be Wrong
Answer rather than Partly Correct.
okexitcodemask specifies a bitmask ANDed with the exit code of the
external validator to get a boolean flag showing whether the answer is to
be marked as 'OK' rather than 'partly correct'. The bits covered by the
bitmask are reset to zeroes before devising the number of points granted
from the resulting number.
author | Oleg Oshmyan <chortos@inbox.lv> |
---|---|
date | Wed, 17 Aug 2011 20:44:54 +0300 |
parents | e0b2fbd7ebe0 |
children |
line wrap: on
line source
# Copyright (c) 2010-2011 Chortos-2 <chortos@inbox.lv> # A compatibility layer for Python 2.5+. This is what lets Upreckon # 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])