# HG changeset patch # User Paul Boddie # Date 1102543366 -3600 # Node ID f08b72e9f0605ed2250f32bfe99505269a695c9c # Parent 1c1018b5307baf351f019c7e7faa9ff42a8fc441 Added .jar archive support to the import hook. Fixed accidental attempts to import classes as modules. diff -r 1c1018b5307b -r f08b72e9f060 classhook.py --- a/classhook.py Wed Dec 08 22:49:44 2004 +0100 +++ b/classhook.py Wed Dec 08 23:02:46 2004 +0100 @@ -1,13 +1,16 @@ #!/usr/bin/env python -import ihooks -import os, glob -from imp import PY_SOURCE, PKG_DIRECTORY, C_BUILTIN -import classfile, bytecode -import new +import ihooks # for the import machinery +import os, glob # for getting suitably-named files +from imp import PY_SOURCE, PKG_DIRECTORY, C_BUILTIN # import machinery magic +import classfile, bytecode # Java class support +import zipfile # for Java archive inspection + +# NOTE: Arbitrary constants pulled from thin air. JAVA_PACKAGE = 20041113 JAVA_CLASS = 20041114 +JAVA_ARCHIVE = 20041115 class ClassHooks(ihooks.Hooks): @@ -17,7 +20,86 @@ "Return the recognised suffixes." - return ihooks.Hooks.get_suffixes(self) + [("", "", JAVA_PACKAGE), (os.extsep + "class", "r", JAVA_CLASS)] + return [("", "", JAVA_PACKAGE), (os.extsep + "jar", "r", JAVA_ARCHIVE)] + ihooks.Hooks.get_suffixes(self) + + def path_isdir(self, x, archive=None): + + "Return whether 'x' is a directory in the given 'archive'." + + if archive is None: + return ihooks.Hooks.path_isdir(self, x) + + return self._get_dirname(x) in archive.namelist() + + def _get_dirname(self, x): + + """ + Return the directory name for 'x'. + In zip files, the presence of "/" seems to indicate a directory. + """ + + if x.endswith("/"): + return x + else: + return x + "/" + + def listdir(self, x, archive=None): + + "Return the contents of the directory 'x' in the given 'archive'." + + if archive is None: + return ihooks.Hooks.listdir(self, x) + + x = self._get_dirname(x) + l = [] + for path in archive.namelist(): + + # Find out if the path is within the given directory. + + if path != x and path.startswith(x): + + # Get the path below the given directory. + + subpath = path[len(x):] + + # Find out whether the path is an object in the current directory. + + if subpath.count("/") == 0 or subpath.count("/") == 1 and subpath.endswith("/"): + l.append(subpath) + + return l + + def matching(self, dir, extension, archive=None): + + """ + Return the matching files in the given directory 'dir' having the given + 'extension' within the given 'archive'. Produce a list containing full + paths as opposed to simple filenames. + """ + + if archive is None: + return glob.glob(self.path_join(dir, "*" + extension)) + + dir = self._get_dirname(dir) + l = [] + for path in self.listdir(dir, archive): + if path.endswith(extension): + l.append(self.path_join(dir, path)) + return l + + def read(self, filename, archive=None): + + """ + Return the contents of the file with the given 'filename' in the given + 'archive'. + """ + + if archive is None: + f = open(filename, "rb") + s = f.read() + f.close() + return s + return archive.read(filename) class ClassLoader(ihooks.ModuleLoader): @@ -36,39 +118,80 @@ if result is not None: return result + # An archive may be opened. + + archive = None + # Provide a special name for the current directory. if name == "__this__": path = "." + + # Where no directory is given, return failure immediately. + elif dir is None: return None + + # Detect archives. + else: - path = os.path.join(dir, name) + archive, archive_path, path = self._get_archive_and_path(dir, name) - #print "Processing name", name, "in", dir, "producing", path + print "Processing name", name, "in", dir, "producing", path, "within archive", archive - if self._find_module_at_path(path): - return (None, path, ("", "", JAVA_PACKAGE)) + if self._find_module_at_path(path, archive): + if archive is not None: + return (archive, archive_path + ":" + path, (os.extsep + "jar", "r", JAVA_ARCHIVE)) + else: + return (None, path, ("", "", JAVA_PACKAGE)) else: return None - def _find_module_at_path(self, path): - if os.path.isdir(path): + def _get_archive_and_path(self, dir, name): + parts = dir.split(":") + archive_path = parts[0] + + # Archives may include an internal path, but will in any case have + # a primary part ending in .jar. + + if archive_path.endswith(os.extsep + "jar"): + archive = zipfile.ZipFile(archive_path, "r") + path = self.hooks.path_join(":".join(parts[1:]), name) + + # Otherwise, produce a filesystem-based path. + + else: + archive = None + path = self.hooks.path_join(dir, name) + + return archive, archive_path, path + + def _get_path_in_archive(self, path): + parts = path.split(":") + if len(parts) == 1: + return parts[0] + else: + return ":".join(parts[1:]) + + def _find_module_at_path(self, path, archive): + if self.hooks.path_isdir(path, archive): + print "Looking in", path, "using archive", archive # Look for classes in the directory. - if len(glob.glob(os.path.join(path, "*" + os.extsep + "class"))) != 0: + if len(self.hooks.matching(path, os.extsep + "class", archive)) != 0: return 1 # Otherwise permit importing where directories containing classes exist. - for filename in os.listdir(path): - pathname = os.path.join(path, filename) - result = self._find_module_at_path(pathname) + print "Filenames are", self.hooks.listdir(path, archive) + for filename in self.hooks.listdir(path, archive): + pathname = self.hooks.path_join(path, filename) + result = self._find_module_at_path(pathname, archive) if result is not None: return result - return None + return 0 def load_module(self, name, stuff): @@ -81,12 +204,12 @@ # Just go into the directory and find the class files. - file, filename, info = stuff + archive, filename, info = stuff suffix, mode, datatype = info - if datatype != JAVA_PACKAGE: + if datatype not in (JAVA_PACKAGE, JAVA_ARCHIVE): return ihooks.ModuleLoader.load_module(self, name, stuff) - print "Loading", file, filename, info + print "Loading", archive, filename, info # Set up the module. @@ -103,14 +226,17 @@ class_files = [] classes = [] + # Get the real filename. + + filename = self._get_path_in_archive(filename) + print "Real filename", filename + # Load the class files. class_files = {} - for class_filename in glob.glob(os.path.join(filename, "*" + os.extsep + "class")): + for class_filename in self.hooks.matching(filename, os.extsep + "class", archive): print "Loading class", class_filename - f = open(class_filename, "rb") - s = f.read() - f.close() + s = self.hooks.read(class_filename, archive) class_file = classfile.ClassFile(s) class_files[str(class_file.this_class.get_name())] = class_file @@ -174,7 +300,6 @@ return module -importer = ihooks.ModuleImporter(loader=ClassLoader(hooks=ClassHooks())) -importer.install() +ihooks.ModuleImporter(loader=ClassLoader(hooks=ClassHooks())).install() # vim: tabstop=4 expandtab shiftwidth=4