changeset 208:ede78fbd509a

Revamped files.File This changes (simplifies) file search path: * when searching for testconf, .../testconf.py is now given precendence over .../tests/testconf.py; * when searching inside archives, a/tests/b/c is now given precedence over a/c.
author Oleg Oshmyan <chortos@inbox.lv>
date Thu, 18 Aug 2011 02:20:24 +0300
parents 946e8c09ba12
children c03a8113685d
files upreckon/config.py upreckon/files.py upreckon/testcases.py
diffstat 3 files changed, 103 insertions(+), 67 deletions(-) [+]
line wrap: on
line diff
--- a/upreckon/config.py	Thu Aug 18 01:19:15 2011 +0300
+++ b/upreckon/config.py	Thu Aug 18 02:20:24 2011 +0300
@@ -114,7 +114,7 @@
 		sys.dont_write_bytecode = True
 	except AttributeError:
 		pass
-	metafile = files.File(problem_name + '/testconf.py', True, 'configuration')
+	metafile = files.File.from_virtual_path(problem_name + '/testconf.py', True, 'configuration')
 	module = None
 	with CompatBuiltins() as builtins:
 		if zipimport and isinstance(metafile.archive, files.ZipArchive):
@@ -225,7 +225,7 @@
 		sys.dont_write_bytecode = True
 	except AttributeError:
 		pass
-	metafile = files.File('testconf.py', True, 'configuration')
+	metafile = files.File.from_virtual_path('testconf.py', True, 'configuration')
 	module = None
 	with CompatBuiltins() as builtins:
 		if zipimport and isinstance(metafile.archive, files.ZipArchive):
--- a/upreckon/files.py	Thu Aug 18 01:19:15 2011 +0300
+++ b/upreckon/files.py	Thu Aug 18 02:20:24 2011 +0300
@@ -209,83 +209,119 @@
 		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))
+	__slots__ = ('virtual_path', 'archive',
+	             '_external_path', '_internal_path', '_has_tests')
 	
-	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
+	def __init__(self, _virtual_path='', _external_path='', _internal_path='',
+	             _archive=None, _has_tests=False):
+		self.virtual_path = _virtual_path
+		self._external_path = _external_path
+		self._internal_path = _internal_path
+		self.archive = _archive
+		self._has_tests = _has_tests
+		if not _archive:
+			try:
+				self.archive = open_archive(_external_path)
+			except Exception:
+				pass
+	
+	@property
+	def full_real_path(self):
+		intpath = self._internal_path.split('/') if self._internal_path else ()
+		return os.path.join(self._external_path,
+		                    *(filename.replace('.', os.path.extsep)
+	                          for filename in intpath))
+	
+	def exists(self):
+		if self.archive:
+			return self.archive.exists(self._internal_path)
 		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, hastests=False):
-		if root and not archive.exists(root):
-			return False
-		path = posixpath.join(root, 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
-		if not hastests:
-			if self.realize_path_archive(archive, posixpath.join(root, 'tests'), virtpath, archpath, True):
-				return True
-		return False
+			return (not self._external_path or
+			        os.path.exists(self._external_path))
 	
 	def open(self):
 		if self.archive:
-			file = self.archive.open(self.real_path)
+			file = self.archive.open(self._internal_path)
 			if hasattr(file, '__exit__'):
 				return file
 			else:
 				return contextlib.closing(file)
 		else:
-			return open(self.real_path, 'rb')
+			return open(self._external_path, 'rb')
 	
 	def copy(self, target):
 		if self.archive:
-			self.archive.extract(self.real_path, target)
+			self.archive.extract(self._internal_path, target)
+		else:
+			shutil.copy(self._external_path, target)
+	
+	def listdir(self):
+		if self.archive:
+			return self.archive.listdir(self._internal_path)
 		else:
-			shutil.copy(self.real_path, target)
+			return os.listdir(self._external_path)
+	
+	@classmethod
+	def from_virtual_path(cls, virtual_path, allow_root, msg):
+		metafile = cls()._realize_path(virtual_path.split('/'), allow_root)
+		if not metafile:
+			raise IOError("%s file with virtual path %r could not be found" %
+			              (msg, virtual_path))
+		assert metafile.virtual_path == virtual_path
+		return metafile
+	
+	def _realize_path(self, virtpath, allow_root):
+		if not self.exists():
+			return None
+		elif not virtpath:
+			if allow_root or self._has_tests or self.archive:
+				return self
+			return None
+		cand = (self + virtpath[0])._realize_path(virtpath[1:], allow_root)
+		if cand: return cand
+		if not cand and not self._has_tests:
+			for metafile in self._add_tests():
+				cand = metafile._realize_path(virtpath, allow_root)
+				if cand: return cand
+		if not cand and len(virtpath) > 1:
+			metafile = self._add_virtual(virtpath[0])
+			cand = metafile._realize_path(virtpath[1:], allow_root)
+			if cand: return cand
+	
+	def _add_tests(self):
+		assert not self._has_tests
+		metafile = self.__add__('tests', False)
+		metafile._has_tests = True
+		yield metafile
+		if not self.archive:
+			for filename in archives:
+				yield self.__add__(filename, False)
+	
+	def _add_virtual(self, filename):
+		return File(posixpath.join(self.virtual_path, filename),
+		            self._external_path,
+		            self._internal_path,
+		            self.archive,
+		            self._has_tests)
+	
+	def __add__(self, filename, add_virtual=True):
+		if not isinstance(filename, basestring):
+			return NotImplemented
+		if add_virtual:
+			virtual_path = posixpath.join(self.virtual_path, filename)
+		else:
+			virtual_path = self.virtual_path
+		if self.archive:
+			return File(virtual_path,
+			            self._external_path,
+			            posixpath.join(self._internal_path, filename),
+			            self.archive,
+			            self._has_tests)
+		else:
+			filename = filename.replace('.', os.path.extsep)
+			return File(virtual_path,
+			            os.path.join(self._external_path, filename),
+			            _has_tests=self._has_tests)
 
 class RegexpMatchFile(object):
 	__slots__ = 'virtual_path', 'real_path', 'hastests', 'archive'
--- a/upreckon/testcases.py	Thu Aug 18 01:19:15 2011 +0300
+++ b/upreckon/testcases.py	Thu Aug 18 02:20:24 2011 +0300
@@ -146,14 +146,14 @@
 	
 	def open_infile(case):
 		try:
-			case.infile = files.File('/'.join((case.problem.name, case.realinname.replace('$', case.id))))
+			case.infile = files.File.from_virtual_path('/'.join((case.problem.name, case.realinname.replace('$', case.id))), False, 'test data')
 		except IOError:
 			e = sys.exc_info()[1]
 			raise CannotReadInputFile(e)
 	
 	def open_outfile(case):
 		try:
-			case.outfile = files.File('/'.join((case.problem.name, case.realoutname.replace('$', case.id))))
+			case.outfile = files.File.from_virtual_path('/'.join((case.problem.name, case.realoutname.replace('$', case.id))), False, 'test data')
 		except IOError:
 			e = sys.exc_info()[1]
 			raise CannotReadAnswerFile(e)