Permalink
Browse files

Merge branch 'tarballs' into devel

Conflicts:
	CHANGES
	pgxnclient/commands/__init__.py
	pgxnclient/tests/test_commands.py
  • Loading branch information...
2 parents 637414c + f117d3f commit 8d18b1c43990fb63b3084c2b9ef46be3bfb8432e @dvarrazzo committed Sep 5, 2012
View
@@ -9,14 +9,19 @@ David Wheeler
He is the PGXN mastermind: a lot of helpful design discussions.
-Andrey Popp
+Peter Eisentraut
- Make selection. Helped the program not to suck on BSD!
+ First implementation of tarball support. Auto-sudo is not a good idea, I
+ got it.
Hitoshi Harada
Tricky installation corner cases.
+Andrey Popp
+
+ Make selection. Helped the program not to suck on BSD!
+
Also thank you everybody for the useful discussions on the PGXN mailing list,
bug reports, proofreading the docs and the general support to the project.
View
@@ -8,6 +8,7 @@ pgxnclient 1.2
- Packages can be downloaded, installed, loaded specifying an URL
(ticket #15).
+- Added support for ``.tar`` files (ticket #17).
- Use ``gmake`` in favour of ``make`` for platforms where the two are
distinct, such as BSD (ticket #14).
- Added ``--make`` option to select the make executable (ticket #16).
View
@@ -0,0 +1,93 @@
+"""
+pgxnclient -- archives handling
+"""
+
+# Copyright (C) 2011-2012 Daniele Varrazzo
+
+# This file is part of the PGXN client
+
+import os
+
+from pgxnclient.i18n import _
+from pgxnclient.utils import load_jsons
+from pgxnclient.errors import PgxnClientException
+
+def from_spec(spec):
+ """Return an `Archive` instance to handle the file requested by *spec*
+ """
+ assert spec.is_file()
+ return from_file(spec.filename)
+
+def from_file(filename):
+ """Return an `Archive` instance to handle the file *filename*
+ """
+ # Get the metadata from an archive file
+ if filename.endswith('.zip'):
+ from pgxnclient.zip import ZipArchive
+ return ZipArchive(filename)
+ else:
+ # Tar files have many naming variants. Let's not
+ # guess them.
+ from pgxnclient.tar import TarArchive
+ return TarArchive(filename)
+
+
+class Archive(object):
+ """Base class to handle archives."""
+ def __init__(self, filename):
+ self.filename = filename
+
+ def open(self):
+ """Open the archive for usage.
+
+ Raise PgxnClientException if the archive can't be open.
+ """
+ raise NotImplementedError
+
+ def close(self):
+ """Close the archive after usage."""
+ raise NotImplementedError
+
+ def list_files(self):
+ """Return an iterable with the list of file names in the archive."""
+ raise NotImplementedError
+
+ def read(self, fn):
+ """Return a file's data from the archive."""
+ raise NotImplementedError
+
+ def unpack(self, destdir):
+ raise NotImplementedError
+
+ def get_meta(self):
+ filename = self.filename
+
+ self.open()
+ try:
+ # Return the first file with the expected name
+ for fn in self.list_files():
+ if fn.endswith('META.json'):
+ return load_jsons(self.read(fn).decode('utf8'))
+ else:
+ raise PgxnClientException(
+ _("file 'META.json' not found in archive '%s'") % filename)
+ finally:
+ self.close()
+
+ def _find_work_directory(self, destdir):
+ """
+ Choose the directory where to work.
+
+ Because we are mostly a wrapper for pgxs, let's look for a makefile.
+ The tar should contain a single base directory, so return the first
+ dir we found containing a Makefile, alternatively just return the
+ unpacked dir
+ """
+ for dir in os.listdir(destdir):
+ for fn in ('Makefile', 'makefile', 'GNUmakefile', 'configure'):
+ if os.path.exists(os.path.join(destdir, dir, fn)):
+ return os.path.join(destdir, dir)
+
+ return destdir
+
+
@@ -22,6 +22,7 @@
from pgxnclient import __version__
from pgxnclient import network
from pgxnclient import Spec, SemVer
+from pgxnclient import archive
from pgxnclient.api import Api
from pgxnclient.i18n import _, gettext
from pgxnclient.errors import NotFound, PgxnClientException, ProcessError, ResourceNotFound, UserAbort
@@ -225,7 +226,6 @@ def popen(self, cmd, *args, **kwargs):
from pgxnclient.errors import BadSpecError
-from pgxnclient.utils.zip import get_meta_from_zip
class WithSpec(Command):
"""Mixin to implement commands taking a package specification.
@@ -390,14 +390,15 @@ def get_meta(self, spec):
return load_json(f)
elif spec.is_file():
- # Get the metadata from a zip file
- return get_meta_from_zip(spec.filename)
+ arc = archive.from_spec(spec)
+ return arc.get_meta()
elif spec.is_url():
with network.get_file(spec.url) as fin:
with temp_dir() as dir:
fn = network.download(fin, dir)
- return get_meta_from_zip(fn)
+ arc = archive.from_file(fn)
+ return arc.get_meta()
else:
assert False
@@ -423,6 +424,7 @@ def get_spec(self, **kwargs):
kwargs['_can_be_local'] = True
return super(WithSpecLocal, self).get_spec(**kwargs)
+
class WithSpecUrl(WithSpec):
"""
Mixin to implement commands that can also refer to a URL.
@@ -17,13 +17,13 @@
from subprocess import PIPE
from pgxnclient import SemVer
+from pgxnclient import archive
from pgxnclient import network
from pgxnclient.i18n import _, N_
from pgxnclient.utils import sha1, b
from pgxnclient.errors import BadChecksum, PgxnClientException, InsufficientPrivileges
from pgxnclient.commands import Command, WithDatabase, WithMake, WithPgConfig
from pgxnclient.commands import WithSpecUrl, WithSpecLocal, WithSudo
-from pgxnclient.utils.zip import unpack
from pgxnclient.utils.temp import temp_dir
from pgxnclient.utils.strings import Identifier
@@ -104,11 +104,11 @@ def _run(self, dir):
if spec.is_dir():
pdir = os.path.abspath(spec.dirname)
elif spec.is_file():
- pdir = unpack(spec.filename, dir)
+ pdir = archive.from_file(spec.filename).unpack(dir)
elif not spec.is_local():
self.opts.target = dir
fn = Download(self.opts).run()
- pdir = unpack(fn, dir)
+ pdir = archive.from_file(fn).unpack(dir)
else:
assert False
View
@@ -0,0 +1,66 @@
+"""
+pgxnclient -- tar file utilities
+"""
+
+# Copyright (C) 2011-2012 Daniele Varrazzo
+
+# This file is part of the PGXN client
+
+import os
+import tarfile
+
+from pgxnclient.i18n import _
+from pgxnclient.errors import PgxnClientException
+from pgxnclient.archive import Archive
+
+import logging
+logger = logging.getLogger('pgxnclient.tar')
+
+
+class TarArchive(Archive):
+ """Handle .tar archives"""
+ _file = None
+
+ def open(self):
+ assert not self._file, "archive already open"
+ try:
+ self._file = tarfile.open(self.filename, 'r')
+ except Exception, e:
+ raise PgxnClientException(
+ _("cannot open archive '%s': %s") % (self.filename, e))
+
+ def close(self):
+ if self._file is not None:
+ self._file.close()
+ self._file = None
+
+ def list_files(self):
+ assert self._file, "archive not open"
+ return self._file.getnames()
+
+ def read(self, fn):
+ assert self._file, "archive not open"
+ return self._file.extractfile(fn).read()
+
+ def unpack(self, destdir):
+ tarname = self.filename
+ logger.info(_("unpacking: %s"), tarname)
+ destdir = os.path.abspath(destdir)
+ self.open()
+ try:
+ for fn in self.list_files():
+ fname = os.path.abspath(os.path.join(destdir, fn))
+ if not fname.startswith(destdir):
+ raise PgxnClientException(
+ _("archive file '%s' trying to escape!") % fname)
+
+ self._file.extractall(path=destdir)
+ finally:
+ self.close()
+
+ return self._find_work_directory(destdir)
+
+
+def unpack(filename, destdir):
+ return TarArchive(filename).unpack(destdir)
+
Oops, something went wrong.

0 comments on commit 8d18b1c

Please sign in to comment.