Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Merge branch 'devel'

  • Loading branch information...
commit 8731f992386170ab5045af64fa03d6a62986f30f 2 parents ff25668 + dcf942c
@dvarrazzo authored
View
33 CHANGES
@@ -1,8 +1,21 @@
+.. _changes:
+
+PGXN Client changes log
+-----------------------
+
+pgxnclient 1.0.2
+================
+
+- Correctly handle PostgreSQL identifiers to be quoted (ticket #10).
+- Don't crash with a traceback if some external command is not found
+ (ticket #11).
+
+
pgxnclient 1.0.1
================
- Fixed simplejson dependency on Python 2.6 (ticket #8).
-- Added 'pgxn help CMD' as synonim for 'pgxn CMD --help' (ticket #7).
+- Added ``pgxn help CMD`` as synonim for ``pgxn CMD --help`` (ticket #7).
- Fixed a few compatibility problems with Python 3.
@@ -11,18 +24,18 @@ pgxnclient 1.0
- Extensions to load/unload from a distribution can be specified on the
command line.
-- 'pgxn help --libexec' returns a single variable, possibly independent
+- ``pgxn help --libexec`` returns a single directory, possibly independent
from the client version.
pgxnclient 0.3
==============
-- 'pgxn' script converted into a generic dispatcher in order to allow
+- ``pgxn`` script converted into a generic dispatcher in order to allow
additional commands to be implemented in external scripts and in any
language.
- commands accept extension names too, not only specs.
-- Added 'help' command to get informations about program and commands.
+- Added ``help`` command to get information about program and commands.
pgxnclient 0.2.1
@@ -36,16 +49,16 @@ pgxnclient 0.2.1
pgxnclient 0.2
==============
-- Dropped list command (use info --versions instead).
+- Dropped ``list`` command (use ``info --versions`` instead).
- Skip extension load/unload if the provided file is not sql.
pgxnclient 0.1a4
================
-- The spec can point to a local file/directory for install
-- Read the sha1 from the META.json as it may be different from the one
- in the dist.json
+- The spec can point to a local file/directory for install.
+- Read the sha1 from the ``META.json`` as it may be different from the one
+ in the ``dist.json``.
- Run sudo in the installation phase of the install command.
@@ -53,13 +66,13 @@ pgxn.client 0.1a3
=================
- Fixed executable mode for scripts unpacked from the zip files.
-- Added 'list' and 'info' commands.
+- Added ``list`` and ``info`` commands.
pgxn.client 0.1a2
=================
-- Added database connection parameters for the 'check' command.
+- Added database connection parameters for the ``check`` command.
pgxn.client 0.1a1
View
1  docs/changes.rst
View
6 docs/ext.rst
@@ -11,7 +11,7 @@ In order to add new builtin commands, add a Python module into the
``pgxnclient/commands`` containing your command or a set of logically-related
commands. The commands are implemented by subclassing the `!Command` class.
Your commands will benefit of all the infrastructure available for the other
-commands. For up-to-date informations take a look at the implementation of
+commands. For up-to-date information take a look at the implementation of
builtin simple commands, such as the ones in ``info.py``.
If you are not into Python and want to add commands written in other
@@ -19,8 +19,8 @@ languages, you can provide a link (either soft or hard) to your command under
one of the ``libexec`` directories. The exact location of the directories
depends on the client installation: distribution packagers may decide to move
them according to their own policies. The location of one of the directories,
-which can be considered the "public" one, can alway be known using the command
-``pgxn help --libexec``. Note that this directory may not exists: in this case
+which can be considered the "public" one, can always be known using the command
+``pgxn help --libexec``. Note that this directory may not exist: in this case
the command being installed is responsible to create it. Links are also looked
for in the :envvar:`PATH` directories.
View
9 docs/index.rst
@@ -41,11 +41,18 @@ Contents:
usage
ext
+.. toctree::
+ :hidden:
+
+ changes
+
+
Indices and tables
==================
-* :ref:`genindex`
+* :ref:`Changes Log <changes>`
* :ref:`search`
+* :ref:`genindex`
..
* :ref:`modindex`
View
8 docs/usage.rst
@@ -457,7 +457,7 @@ for all the known mirrors using the ``--detailed`` option.
``pgxn help``
-------------
-Display help and other program informations.
+Display help and other program information.
Usage:
@@ -466,12 +466,12 @@ Usage:
pgxn help [--help] [--all | --libexec | *CMD*]
-Without options show the same informations obtained by ``pgxn --help``, which
+Without options show the same information obtained by ``pgxn --help``, which
includes a list of builtin commands. With the ``--all`` option print the
complete list of commands installed in the system.
-The option ``--libexec`` prints the list of directories containing
-the command scripts: see :ref:`extending` for more informations.
+The option ``--libexec`` prints the full path of the directory containing
+the external commands scripts: see :ref:`extending` for more information.
:samp:`pgxn help {CMD}` is an alias for :samp:`pgxn {CMD} --help`.
View
2  pgxnclient/__init__.py
@@ -6,7 +6,7 @@
# This file is part of the PGXN client
-__version__ = '1.0.1'
+__version__ = '1.0.2'
# Paths where to find the command executables.
# If relative, it's from the `pgxnclient` package directory.
View
22 pgxnclient/commands/__init__.py
@@ -22,7 +22,7 @@
from pgxnclient import Spec, SemVer
from pgxnclient.api import Api
from pgxnclient.i18n import _, gettext
-from pgxnclient.errors import NotFound, PgxnClientException, ResourceNotFound, UserAbort
+from pgxnclient.errors import NotFound, PgxnClientException, ProcessError, ResourceNotFound, UserAbort
logger = logging.getLogger('pgxnclient.commands')
@@ -168,7 +168,7 @@ def __make_subparser(self, parser, subparsers,
default = 'http://api.pgxn.org/',
help = _("the mirror to interact with [default: %(default)s]"))
glb.add_argument("--verbose", action='store_true',
- help = _("print more informations"))
+ help = _("print more information"))
glb.add_argument("--yes", action='store_true',
help = _("assume affirmative answer to all questions"))
@@ -204,14 +204,20 @@ def confirm(self, prompt):
else:
prompt = _("Please answer yes or no")
- def popen(self, *args, **kwargs):
+ def popen(self, cmd, *args, **kwargs):
"""
Excecute subprocess.Popen.
Commands should use this method instead of importing subprocess.Popen:
this allows replacement with a mock in the test suite.
"""
- return Popen(*args, **kwargs)
+ try:
+ return Popen(cmd, *args, **kwargs)
+ except OSError, e:
+ if not isinstance(cmd, basestring):
+ cmd = ' '.join(cmd)
+ msg = _("%s running command: %s") % (e, cmd)
+ raise ProcessError(msg)
from pgxnclient.errors import BadSpecError
@@ -457,8 +463,8 @@ def call_pg_config(self, what, _cache={}):
p = self.popen(cmdline, stdout=PIPE, shell=True)
out, err = p.communicate()
if p.returncode:
- raise PgxnClientException(
- "%s returned %s" % (cmdline, p.returncode))
+ raise ProcessError(_("command returned %s: %s")
+ % (p.returncode, cmdline))
out = out.rstrip().decode('utf-8')
rv = _cache[what] = out
@@ -510,8 +516,8 @@ def run_make(self, cmd, dir, env=None, sudo=None):
p = self.popen(cmdline, cwd=dir, shell=False, env=env, close_fds=True)
p.communicate()
if p.returncode:
- raise PgxnClientException(
- _("command returned %s") % p.returncode)
+ raise ProcessError(_("command returned %s: %s")
+ % (p.returncode, ' '.join(cmdline)))
class WithSudo(object):
View
2  pgxnclient/commands/help.py
@@ -14,7 +14,7 @@
class Help(Command):
name = 'help'
- description = N_("display help and other program informations")
+ description = N_("display help and other program information")
@classmethod
def customize_parser(self, parser, subparsers, **kwargs):
View
4 pgxnclient/commands/info.py
@@ -17,7 +17,7 @@
class Mirror(Command):
name = 'mirror'
- description = N_("return informations about the available mirrors")
+ description = N_("return information about the available mirrors")
@classmethod
def customize_parser(self, parser, subparsers, **kwargs):
@@ -121,7 +121,7 @@ def clean_excerpt(self, excerpt):
class Info(WithSpec, Command):
name = 'info'
- description = N_("print informations about a distribution")
+ description = N_("print information about a distribution")
@classmethod
def customize_parser(self, parser, subparsers, **kwargs):
View
3  pgxnclient/errors.py
@@ -22,6 +22,9 @@ class UserAbort(PgxnClientException):
class BadSpecError(PgxnClientException):
"""A bad package specification."""
+class ProcessError(PgxnClientException):
+ """An error raised calling an external program."""
+
class NotFound(PgxnException):
"""Something requested by the user not found on PGXN"""
View
2  pgxnclient/libexec/README
@@ -1,6 +1,6 @@
This directory contains the PGXN Client builtin commands. If you want to
extend the client with your own command, please use the directory returned by
-``pgxn help --libexec``. See the documentation for further informations about
+``pgxn help --libexec``. See the documentation for further information about
how to create new commands.
Note that setuptools doesn't do a perfect job and replaces the links with the
View
8 pgxnclient/tests/test_commands.py
@@ -69,6 +69,14 @@ def test_info_case_insensitive(self):
"""))
+class CommandTestCase(unittest.TestCase):
+ def test_popen_raises(self):
+ from pgxnclient.commands import Command
+ c = Command([])
+ self.assertRaises(PgxnClientException,
+ c.popen, "this-script-doesnt-exist")
+
+
class DownloadTestCase(unittest.TestCase):
@patch('pgxnclient.api.get_file')
def test_download_latest(self, mock):
View
22 pgxnclient/tests/test_label.py
@@ -1,6 +1,6 @@
from pgxnclient.tests import unittest
-from pgxnclient import Label, Term
+from pgxnclient import Label, Term, Identifier
class LabelTestCase(unittest.TestCase):
def test_ok(self):
@@ -72,5 +72,25 @@ def ar(s):
ar(s)
+class TestIdentifier(unittest.TestCase):
+ def test_nonblank(self):
+ self.assertRaises(ValueError, Identifier, "")
+
+ def test_unquoted(self):
+ for s in [
+ 'x',
+ 'xxxxx',
+ 'abcxyz_0189',
+ 'ABCXYZ_0189', ]:
+ self.assertEqual(Identifier(s), s)
+
+ def test_quoted(self):
+ for s, q in [
+ ('x-y', '"x-y"'),
+ (' ', '" "'),
+ ('x"y', '"x""y"')]:
+ self.assertEqual(Identifier(s), q)
+
+
if __name__ == '__main__':
unittest.main()
View
8 pgxnclient/utils/strings.py
@@ -85,12 +85,15 @@ class Identifier(CIStr):
A string modeling a PostgreSQL identifier.
"""
def __new__(cls, value):
+ if not value:
+ raise ValueError("PostgreSQL identifiers cannot be blank")
if not Identifier._re_chk.match(value):
- raise ValueError(_("bad identifier: '%s'") % value)
+ value = '"%s"' % value.replace('"', '""')
+ # TODO: identifier are actually case sensitive if quoted
return CIStr.__new__(cls, value)
_re_chk = re.compile(
- r'^[a-z_][a-z0-9_\$]{0,62}',
+ r'^[a-z_][a-z0-9_\$]*$',
re.IGNORECASE)
@classmethod
@@ -101,5 +104,6 @@ def parse_arg(self, s):
try:
return Identifier(s)
except ValueError, e:
+ # shouldn't happen anymore as we quote invalid identifiers
raise ArgumentTypeError(e)
Please sign in to comment.
Something went wrong with that request. Please try again.