Mercurial > ~astiob > upreckon > hgweb
view files.py @ 76:0e5ae28e0b2b
Points are now weighted on a test context basis
In particular, this has allowed for simple extensions to the format
of testconf to award points to whole test groups without at the same time
compromising the future ability of giving partial score for correct
but slow solutions. Specifically, the groupweight configuration variable
has been added and normally has the format {groupindex: points} where
groupindex is the group's index in the tests configuration variable.
The backwards incompatible change is that test contexts are no longer
guaranteed to learn the score awarded or the maximum possible score
for every test case and may instead be notified about them in batches.
In other news, the pointmap and groupweight configuration variables can
(now) be given as sequences in addition to mappings. (Technically,
the distinction currently made is dict versus everything else.) Items
of a sequence pointmap/groupweight correspond directly to the test cases/
groups defined in the tests configuration variable; in particular,
when groups are used, tests=[1],[2,3];pointmap={1:1,2:2,3:3} can now be
written as pointmap=tests=[1],[2,3]. Missing items are handled in the same
way in which they are handled when the variable is a mapping. Note
that the items of groupweight correspond to whole test groups rather
than individual test cases.
In other news again, the wording of problem total lines has been changed
from '<unweighted> points; weighted score: <weighted>' to '<weighted>
points (<unweighted> before weighting)', and group total lines now
properly report fractional numbers of points (this is a bug fix).
author | Oleg Oshmyan <chortos@inbox.lv> |
---|---|
date | Sat, 08 Jan 2011 16:03:35 +0200 |
parents | 1914ae9cfdce |
children | cd347cfca272 |
line wrap: on
line source
#! /usr/bin/env python # Copyright (c) 2010 Chortos-2 <chortos@inbox.lv> """File access routines and classes with support for archives.""" from __future__ import division, with_statement try: from compat import * except ImportError: import __main__ __main__.import_error(sys.exc_info()[1]) import contextlib, os, shutil, sys # You don't need to know about anything else. __all__ = 'File', # In these two variables, use full stops no matter what os.extsep is; # all full stops will be converted to os.extsep on the fly archives = 'tests.tar', 'tests.zip', 'tests.tgz', 'tests.tar.gz', 'tests.tbz2', 'tests.tar.bz2' formats = {} class Archive(object): __slots__ = 'file' if ABCMeta: __metaclass__ = ABCMeta def __new__(cls, path): """ Create a new instance of the archive class corresponding to the file name in the given path. """ if cls is not Archive: return object.__new__(cls) else: # Do this by hand rather than through os.path.splitext # because we support multi-dotted file name extensions ext = path.partition(os.path.extsep)[2] while ext: if ext in formats: return formats[ext](path) ext = ext.partition(os.path.extsep)[2] raise LookupError("unsupported archive file name extension in file name '%s'" % filename) @abstractmethod def __init__(self, path): raise NotImplementedError @abstractmethod def extract(self, name, target): raise NotImplementedError def __del__(self): del self.file try: import tarfile except ImportError: TarArchive = None else: class TarArchive(Archive): __slots__ = '__namelist' def __init__(self, path): self.file = tarfile.open(path) def extract(self, name, target): member = self.file.getmember(name) member.name = target self.file.extract(member) # TODO: somehow automagically emulate universal line break support def open(self, name): return self.file.extractfile(name) def exists(self, queried_name): if not hasattr(self, '__namelist'): names = set() for name in self.file.getnames(): cutname = name while cutname: names.add(cutname) cutname = cutname.rpartition('/')[0] self.__namelist = frozenset(names) return queried_name in self.__namelist def __enter__(self): if hasattr(self.file, '__enter__'): self.file.__enter__() return self def __exit__(self, exc_type, exc_value, traceback): if hasattr(self.file, '__exit__'): return self.file.__exit__(exc_type, exc_value, traceback) elif exc_type is None: self.file.close() else: # This code was shamelessly copied from tarfile.py of Python 2.7 if not self.file._extfileobj: self.file.fileobj.close() self.file.closed = True formats['tar'] = formats['tgz'] = formats['tar.gz'] = formats['tbz2'] = formats['tar.bz2'] = TarArchive try: import zipfile except ImportError: ZipArchive = None else: class ZipArchive(Archive): __slots__ = '__namelist' def __init__(self, path): self.file = zipfile.ZipFile(path) def extract(self, name, target): if os.path.isabs(target): # To my knowledge, this is as portable as it gets path = os.path.join(os.path.splitdrive(target)[0], os.path.sep) else: path = None member = self.file.getinfo(name) member.filename = os.path.relpath(target, path) # FIXME: 2.5 lacks ZipFile.extract self.file.extract(member, path) def open(self, name): return self.file.open(name, 'rU') def exists(self, queried_name): if not hasattr(self, '__namelist'): names = set() for name in self.file.namelist(): cutname = name while cutname: names.add(cutname) cutname = cutname.rpartition('/')[0] self.__namelist = frozenset(names) return queried_name in self.__namelist def __enter__(self): if hasattr(self.file, '__enter__'): self.file.__enter__() return self def __exit__(self, exc_type, exc_value, traceback): if hasattr(self.file, '__exit__'): return self.file.__exit__(exc_type, exc_value, traceback) else: return self.file.close() formats['zip'] = ZipArchive # Remove unsupported archive formats and replace full stops # with the platform-dependent file name extension separator def issupported(filename, formats=formats): ext = filename.partition('.')[2] while ext: if ext in formats: return True ext = ext.partition('.')[2] return False archives = [filename.replace('.', os.path.extsep) for filename in filter(issupported, archives)] formats = dict((item[0].replace('.', os.path.extsep), item[1]) for item in items(formats)) open_archives = {} def open_archive(path): if path in open_archives: return open_archives[path] else: open_archives[path] = archive = Archive(path) return archive class File(object): __slots__ = 'virtual_path', 'real_path', 'full_real_path', 'archive' def __init__(self, virtpath, allow_root=False, msg='test data'): self.virtual_path = virtpath self.archive = None if not self.realize_path('', tuple(comp.replace('.', os.path.extsep) for comp in virtpath.split('/')), allow_root): raise IOError("%s file '%s' could not be found" % (msg, virtpath)) def realize_path(self, root, virtpath, allow_root=False, hastests=False): if root and not os.path.exists(root): return False if len(virtpath) > 1: if self.realize_path(os.path.join(root, virtpath[0]), virtpath[1:], allow_root, hastests): return True elif not hastests: if self.realize_path(os.path.join(root, 'tests'), virtpath, allow_root, True): return True for archive in archives: path = os.path.join(root, archive) if os.path.exists(path): if self.realize_path_archive(open_archive(path), '', virtpath, path): return True if self.realize_path(root, virtpath[1:], allow_root, hastests): return True else: if not hastests: path = os.path.join(root, 'tests', virtpath[0]) if os.path.exists(path): self.full_real_path = self.real_path = path return True for archive in archives: path = os.path.join(root, archive) if os.path.exists(path): if self.realize_path_archive(open_archive(path), '', virtpath, path): return True if hastests or allow_root: path = os.path.join(root, virtpath[0]) if os.path.exists(path): self.full_real_path = self.real_path = path return True return False def realize_path_archive(self, archive, root, virtpath, archpath): if root and not archive.exists(root): return False if root: path = ''.join((root, '/', virtpath[0])) else: path = virtpath[0] if len(virtpath) > 1: if self.realize_path_archive(archive, path, virtpath[1:], archpath): return True elif self.realize_path_archive(archive, root, virtpath[1:], archpath): return True else: if archive.exists(path): self.archive = archive self.real_path = path self.full_real_path = os.path.join(archpath, *path.split('/')) return True return False def open(self): if self.archive: file = self.archive.open(self.real_path) if hasattr(file, '__exit__'): return file else: return contextlib.closing(file) else: return open(self.real_path, 'rU') def copy(self, target): if self.archive: self.archive.extract(self.real_path, target) else: shutil.copy(self.real_path, target)