diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2021-02-08 10:31:30 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2021-02-08 10:31:30 +0000 |
commit | d6876017a5c98db13fd92023033184e7f4558681 (patch) | |
tree | 49af4161894c7e47dfb6c4d1f2422441038dbd0c | |
parent | Adding upstream version 3.1.0. (diff) | |
download | pgcli-a66410e52b0afc69ac6f81ba5bffaa32c48ea77b.tar.xz pgcli-a66410e52b0afc69ac6f81ba5bffaa32c48ea77b.zip |
Adding debian version 3.1.0-0.1.debian/3.1.0-0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
-rw-r--r-- | debian/changelog | 99 | ||||
-rw-r--r-- | debian/control | 34 | ||||
-rw-r--r-- | debian/copyright | 41 | ||||
-rw-r--r-- | debian/gbp.conf | 2 | ||||
-rw-r--r-- | debian/links | 1 | ||||
-rw-r--r-- | debian/patches/0001-Change-dependencies-and-rename-entrypoint.patch | 33 | ||||
-rw-r--r-- | debian/patches/0002-vendorized-cli_helpers.patch | 1347 | ||||
-rw-r--r-- | debian/patches/series | 2 | ||||
-rw-r--r-- | debian/pgcli.1 | 51 | ||||
-rw-r--r-- | debian/pgcli.manpages | 1 | ||||
-rwxr-xr-x | debian/rules | 10 | ||||
-rw-r--r-- | debian/source/format | 1 | ||||
-rw-r--r-- | debian/watch | 3 |
13 files changed, 1625 insertions, 0 deletions
diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 0000000..16cedaf --- /dev/null +++ b/debian/changelog @@ -0,0 +1,99 @@ +pgcli (3.1.0-0.1) experimental; urgency=medium + + * Non-maintainer upload. + * New upstream release, using pendulum (Closes: #972598). + * Refreshing 0001-Change-dependencies-and-rename-entrypoint.patch. + + -- Daniel Baumann <daniel.baumann@progress-linux.org> Sat, 30 Jan 2021 09:59:01 +0100 + +pgcli (3.0.0-1) unstable; urgency=medium + + * New upstream release (Closes: #959973) + + -- Lennart Weller <lhw@ring0.de> Fri, 08 May 2020 10:29:31 +0200 + +pgcli (2.2.0-4) unstable; urgency=medium + + * Add dependencies on tabulate and terminaltables (Closes: #950621) + + -- Lennart Weller <lhw@ring0.de> Wed, 12 Feb 2020 16:11:13 +0100 + +pgcli (2.2.0-3) unstable; urgency=medium + + * pkg-resources hard dependency for minimal systems (Closes: #944455) + + -- Lennart Weller <lhw@ring0.de> Tue, 14 Jan 2020 11:22:17 +0100 + +pgcli (2.2.0-2) unstable; urgency=medium + + * New upstream release (Closes: #917963) + * New debian Standards-Version 4.4.1 + + -- Lennart Weller <lhw@ring0.de> Sun, 12 Jan 2020 22:31:03 +0100 + +pgcli (1.9.1-1) unstable; urgency=medium + + * New upstream release (Closes: #894321) + * Vendorized version of cli_helpers library + + -- Lennart Weller <lhw@ring0.de> Wed, 25 Apr 2018 14:36:42 +0200 + +pgcli (1.6.0-1) unstable; urgency=medium + + * New upstream release + * Fixes issues with psycopg2 (Closes: #865630) + + -- Lennart Weller <lhw@ring0.de> Fri, 30 Jun 2017 11:41:22 +0200 + +pgcli (1.4.0-1) unstable; urgency=medium + + * New upstream release + + -- Lennart Weller <lhw@ring0.de> Mon, 06 Mar 2017 15:14:26 +0100 + +pgcli (1.3.1-2) unstable; urgency=medium + + * Added pkg-ressources as dependency + + -- Lennart Weller <lhw@ring0.de> Wed, 11 Jan 2017 12:42:09 +0100 + +pgcli (1.3.1-1) unstable; urgency=medium + + * New upstrem release (Closes: #844394) + + -- Lennart Weller <lhw@ring0.de> Tue, 15 Nov 2016 14:54:59 +0100 + +pgcli (1.1.0-1) unstable; urgency=low + + * New upstream release + + -- Lennart Weller <lhw@ring0.de> Wed, 27 Jul 2016 14:43:23 +0200 + +pgcli (1.0.0-1) unstable; urgency=medium + + * Imported new upstream release (Closes: #829021) + * Added python3-humanize dependency + * Updated debian Standards-Version + * Added manpage + * Updated Vcs links + + -- Lennart Weller <lhw@ring0.de> Fri, 01 Jul 2016 10:53:56 +0200 + +pgcli (0.20.1-3) unstable; urgency=medium + + * Merged upstream patch to support prompt_toolkit == 0.57 (Closes: #811478) + * Updated Build-Depends for pgspecial + + -- Lennart Weller <lhw@ring0.de> Tue, 19 Jan 2016 13:44:14 +0100 + +pgcli (0.20.1-2) unstable; urgency=medium + + * Added patch to get pgcli to work with prompt_toolkit >= 0.51 + + -- Lennart Weller <lhw@ring0.de> Fri, 27 Nov 2015 14:15:20 +0100 + +pgcli (0.20.1-1) unstable; urgency=low + + * Initial release (Closes: #794250) + + -- Lennart Weller <lhw@ring0.de> Fri, 31 Jul 2015 18:16:30 +0200 diff --git a/debian/control b/debian/control new file mode 100644 index 0000000..d4920ce --- /dev/null +++ b/debian/control @@ -0,0 +1,34 @@ +Source: pgcli +Maintainer: Lennart Weller <lhw@ring0.de> +Section: database +Priority: optional +Vcs-git: https://salsa.debian.org/lhw-guest/pgcli.git +Vcs-browser: https://salsa.debian.org/lhw-guest/pgcli +Homepage: http://pgcli.com +Build-Depends: debhelper (>= 12), + debhelper-compat (= 12), + dh-python, + python3, + python3-pgspecial (>= 1.9.0), + python3-setuptools (>= 0.6b3) +Standards-Version: 4.5.0 +Testsuite: autopkgtest-pkg-python + +Package: pgcli +Architecture: all +Depends: python3-click (>= 4.1), + python3-configobj (>= 5.0.6), + python3-pendulum (>= 2.1.0), + python3-pkg-resources (>= 40), + python3-prompt-toolkit (>= 3.0), + python3-psycopg2 (>= 2.8), + python3-pygments (>= 2.0), + python3-sqlparse (>= 0.3.0), + python3-tabulate (>= 0.8.2), + python3-terminaltables, + ${misc:Depends}, + ${python3:Depends} +Description: CLI for Postgres + pgcli is a command line interface for PostgreSQL with + auto-completion and syntax highlighting. The CLI is also capable of pretty + printing tabular data. diff --git a/debian/copyright b/debian/copyright new file mode 100644 index 0000000..b698781 --- /dev/null +++ b/debian/copyright @@ -0,0 +1,41 @@ +Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Upstream-Name: pgcli +Source: https://pgcli.com/ + +Files: * +Copyright: Copyright (c) 2014 Amjith Ramanujam <amjith.r@gmail.com> +License: BSD-3-clause + +Files: debian/* +Copyright: Copyright (c) 2015 Lennart Weller <lhw@ring0.de> +License: BSD-3-clause + +License: BSD-3-clause + All rights reserved. + . + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + . + Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + . + Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + . + Neither the name of the modp.com nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + . + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/debian/gbp.conf b/debian/gbp.conf new file mode 100644 index 0000000..cec628c --- /dev/null +++ b/debian/gbp.conf @@ -0,0 +1,2 @@ +[DEFAULT] +pristine-tar = True diff --git a/debian/links b/debian/links new file mode 100644 index 0000000..38c1aeb --- /dev/null +++ b/debian/links @@ -0,0 +1 @@ +/usr/share/pgcli/console /usr/bin/pgcli diff --git a/debian/patches/0001-Change-dependencies-and-rename-entrypoint.patch b/debian/patches/0001-Change-dependencies-and-rename-entrypoint.patch new file mode 100644 index 0000000..e23577c --- /dev/null +++ b/debian/patches/0001-Change-dependencies-and-rename-entrypoint.patch @@ -0,0 +1,33 @@ +From: Lennart Weller <lhw@ring0.de> +Date: Fri, 8 May 2020 10:28:38 +0200 +Subject: Change dependencies and rename entrypoint + +diff -Naurp pgcli.orig/setup.py pgcli/setup.py +--- pgcli.orig/setup.py ++++ pgcli/setup.py +@@ -6,7 +6,7 @@ from pgcli import __version__ + description = "CLI for Postgres Database. With auto-completion and syntax highlighting." + + install_requirements = [ +- "pgspecial>=1.11.8", ++ "pgspecial>=1.9", + "click >= 4.1", + "Pygments >= 2.0", # Pygments has to be Capitalcased. WTF? + # We still need to use pt-2 unless pt-3 released on Fedora32 +@@ -16,7 +16,6 @@ install_requirements = [ + "sqlparse >=0.3.0,<0.5", + "configobj >= 5.0.6", + "pendulum>=2.1.0", +- "cli_helpers[styles] >= 2.0.0", + ] + + +@@ -43,7 +42,7 @@ setup( + python_requires=">=3.6", + entry_points=""" + [console_scripts] +- pgcli=pgcli.main:cli ++ console=pgcli.main:cli + """, + classifiers=[ + "Intended Audience :: Developers", diff --git a/debian/patches/0002-vendorized-cli_helpers.patch b/debian/patches/0002-vendorized-cli_helpers.patch new file mode 100644 index 0000000..8150397 --- /dev/null +++ b/debian/patches/0002-vendorized-cli_helpers.patch @@ -0,0 +1,1347 @@ +From: Lennart Weller <lhw@ring0.de> +Date: Fri, 8 May 2020 10:29:09 +0200 +Subject: vendorized cli_helpers + +--- + pgcli/main.py | 4 +- + pgcli/packages/cli_helpers/__init__.py | 1 + + pgcli/packages/cli_helpers/compat.py | 42 ++++ + pgcli/packages/cli_helpers/config.py | 270 +++++++++++++++++++++ + .../cli_helpers/tabular_output/__init__.py | 13 + + .../tabular_output/delimited_output_adapter.py | 48 ++++ + .../cli_helpers/tabular_output/output_formatter.py | 228 +++++++++++++++++ + .../cli_helpers/tabular_output/preprocessors.py | 266 ++++++++++++++++++++ + .../cli_helpers/tabular_output/tabulate_adapter.py | 98 ++++++++ + .../tabular_output/terminaltables_adapter.py | 96 ++++++++ + .../tabular_output/tsv_output_adapter.py | 16 ++ + .../tabular_output/vertical_table_adapter.py | 66 +++++ + pgcli/packages/cli_helpers/utils.py | 70 ++++++ + pgcli/pgcompleter.py | 2 +- + 14 files changed, 1217 insertions(+), 3 deletions(-) + create mode 100644 pgcli/packages/cli_helpers/__init__.py + create mode 100644 pgcli/packages/cli_helpers/compat.py + create mode 100644 pgcli/packages/cli_helpers/config.py + create mode 100644 pgcli/packages/cli_helpers/tabular_output/__init__.py + create mode 100644 pgcli/packages/cli_helpers/tabular_output/delimited_output_adapter.py + create mode 100644 pgcli/packages/cli_helpers/tabular_output/output_formatter.py + create mode 100644 pgcli/packages/cli_helpers/tabular_output/preprocessors.py + create mode 100644 pgcli/packages/cli_helpers/tabular_output/tabulate_adapter.py + create mode 100644 pgcli/packages/cli_helpers/tabular_output/terminaltables_adapter.py + create mode 100644 pgcli/packages/cli_helpers/tabular_output/tsv_output_adapter.py + create mode 100644 pgcli/packages/cli_helpers/tabular_output/vertical_table_adapter.py + create mode 100644 pgcli/packages/cli_helpers/utils.py + +diff --git a/pgcli/main.py b/pgcli/main.py +index 08c6c49..a1ad8ed 100644 +--- a/pgcli/main.py ++++ b/pgcli/main.py +@@ -20,8 +20,8 @@ from codecs import open + + keyring = None # keyring will be loaded later + +-from cli_helpers.tabular_output import TabularOutputFormatter +-from cli_helpers.tabular_output.preprocessors import align_decimals, format_numbers ++from .packages.cli_helpers.tabular_output import TabularOutputFormatter ++from .packages.cli_helpers.tabular_output.preprocessors import align_decimals, format_numbers + import click + + try: +diff --git a/pgcli/packages/cli_helpers/__init__.py b/pgcli/packages/cli_helpers/__init__.py +new file mode 100644 +index 0000000..3f262a6 +--- /dev/null ++++ b/pgcli/packages/cli_helpers/__init__.py +@@ -0,0 +1 @@ ++__version__ = '1.2.1' +diff --git a/pgcli/packages/cli_helpers/compat.py b/pgcli/packages/cli_helpers/compat.py +new file mode 100644 +index 0000000..3f67c62 +--- /dev/null ++++ b/pgcli/packages/cli_helpers/compat.py +@@ -0,0 +1,42 @@ ++# -*- coding: utf-8 -*- ++"""OS and Python compatibility support.""" ++ ++from decimal import Decimal ++import sys ++ ++PY2 = sys.version_info[0] == 2 ++WIN = sys.platform.startswith('win') ++MAC = sys.platform == 'darwin' ++ ++ ++if PY2: ++ text_type = unicode ++ binary_type = str ++ long_type = long ++ int_types = (int, long) ++ ++ from UserDict import UserDict ++ from backports import csv ++ ++ from StringIO import StringIO ++ from itertools import izip_longest as zip_longest ++else: ++ text_type = str ++ binary_type = bytes ++ long_type = int ++ int_types = (int,) ++ ++ from collections import UserDict ++ import csv ++ from io import StringIO ++ from itertools import zip_longest ++ ++ ++HAS_PYGMENTS = True ++try: ++ from pygments.formatters.terminal256 import Terminal256Formatter ++except ImportError: ++ HAS_PYGMENTS = False ++ Terminal256Formatter = None ++ ++float_types = (float, Decimal) +diff --git a/pgcli/packages/cli_helpers/config.py b/pgcli/packages/cli_helpers/config.py +new file mode 100644 +index 0000000..b75a8c9 +--- /dev/null ++++ b/pgcli/packages/cli_helpers/config.py +@@ -0,0 +1,270 @@ ++# -*- coding: utf-8 -*- ++"""Read and write an application's config files.""" ++ ++from __future__ import unicode_literals ++import io ++import logging ++import os ++ ++from configobj import ConfigObj, ConfigObjError ++from validate import ValidateError, Validator ++ ++from compat import MAC, text_type, UserDict, WIN ++ ++logger = logging.getLogger(__name__) ++ ++ ++class ConfigError(Exception): ++ """Base class for exceptions in this module.""" ++ pass ++ ++ ++class DefaultConfigValidationError(ConfigError): ++ """Indicates the default config file did not validate correctly.""" ++ pass ++ ++ ++class Config(UserDict, object): ++ """Config reader/writer class. ++ ++ :param str app_name: The application's name. ++ :param str app_author: The application author/organization. ++ :param str filename: The config filename to look for (e.g. ``config``). ++ :param dict/str default: The default config values or absolute path to ++ config file. ++ :param bool validate: Whether or not to validate the config file. ++ :param bool write_default: Whether or not to write the default config ++ file to the user config directory if it doesn't ++ already exist. ++ :param tuple additional_dirs: Additional directories to check for a config ++ file. ++ """ ++ ++ def __init__(self, app_name, app_author, filename, default=None, ++ validate=False, write_default=False, additional_dirs=()): ++ super(Config, self).__init__() ++ #: The :class:`ConfigObj` instance. ++ self.data = ConfigObj() ++ ++ self.default = {} ++ self.default_file = self.default_config = None ++ self.config_filenames = [] ++ ++ self.app_name, self.app_author = app_name, app_author ++ self.filename = filename ++ self.write_default = write_default ++ self.validate = validate ++ self.additional_dirs = additional_dirs ++ ++ if isinstance(default, dict): ++ self.default = default ++ self.update(default) ++ elif isinstance(default, text_type): ++ self.default_file = default ++ elif default is not None: ++ raise TypeError( ++ '"default" must be a dict or {}, not {}'.format( ++ text_type.__name__, type(default))) ++ ++ if self.write_default and not self.default_file: ++ raise ValueError('Cannot use "write_default" without specifying ' ++ 'a default file.') ++ ++ if self.validate and not self.default_file: ++ raise ValueError('Cannot use "validate" without specifying a ' ++ 'default file.') ++ ++ def read_default_config(self): ++ """Read the default config file. ++ ++ :raises DefaultConfigValidationError: There was a validation error with ++ the *default* file. ++ """ ++ if self.validate: ++ self.default_config = ConfigObj(configspec=self.default_file, ++ list_values=False, _inspec=True, ++ encoding='utf8') ++ valid = self.default_config.validate(Validator(), copy=True, ++ preserve_errors=True) ++ if valid is not True: ++ for name, section in valid.items(): ++ if section is True: ++ continue ++ for key, value in section.items(): ++ if isinstance(value, ValidateError): ++ raise DefaultConfigValidationError( ++ 'section [{}], key "{}": {}'.format( ++ name, key, value)) ++ elif self.default_file: ++ self.default_config, _ = self.read_config_file(self.default_file) ++ ++ self.update(self.default_config) ++ ++ def read(self): ++ """Read the default, additional, system, and user config files. ++ ++ :raises DefaultConfigValidationError: There was a validation error with ++ the *default* file. ++ """ ++ if self.default_file: ++ self.read_default_config() ++ return self.read_config_files(self.all_config_files()) ++ ++ def user_config_file(self): ++ """Get the absolute path to the user config file.""" ++ return os.path.join( ++ get_user_config_dir(self.app_name, self.app_author), ++ self.filename) ++ ++ def system_config_files(self): ++ """Get a list of absolute paths to the system config files.""" ++ return [os.path.join(f, self.filename) for f in get_system_config_dirs( ++ self.app_name, self.app_author)] ++ ++ def additional_files(self): ++ """Get a list of absolute paths to the additional config files.""" ++ return [os.path.join(f, self.filename) for f in self.additional_dirs] ++ ++ def all_config_files(self): ++ """Get a list of absolute paths to all the config files.""" ++ return (self.additional_files() + self.system_config_files() + ++ [self.user_config_file()]) ++ ++ def write_default_config(self, overwrite=False): ++ """Write the default config to the user's config file. ++ ++ :param bool overwrite: Write over an existing config if it exists. ++ """ ++ destination = self.user_config_file() ++ if not overwrite and os.path.exists(destination): ++ return ++ ++ with io.open(destination, mode='wb') as f: ++ self.default_config.write(f) ++ ++ def write(self, outfile=None, section=None): ++ """Write the current config to a file (defaults to user config). ++ ++ :param str outfile: The path to the file to write to. ++ :param None/str section: The config section to write, or :data:`None` ++ to write the entire config. ++ """ ++ with io.open(outfile or self.user_config_file(), 'wb') as f: ++ self.data.write(outfile=f, section=section) ++ ++ def read_config_file(self, f): ++ """Read a config file *f*. ++ ++ :param str f: The path to a file to read. ++ """ ++ configspec = self.default_file if self.validate else None ++ try: ++ config = ConfigObj(infile=f, configspec=configspec, ++ interpolation=False, encoding='utf8') ++ except ConfigObjError as e: ++ logger.warning( ++ 'Unable to parse line {} of config file {}'.format( ++ e.line_number, f)) ++ config = e.config ++ ++ valid = True ++ if self.validate: ++ valid = config.validate(Validator(), preserve_errors=True, ++ copy=True) ++ if bool(config): ++ self.config_filenames.append(config.filename) ++ ++ return config, valid ++ ++ def read_config_files(self, files): ++ """Read a list of config files. ++ ++ :param iterable files: An iterable (e.g. list) of files to read. ++ """ ++ errors = {} ++ for _file in files: ++ config, valid = self.read_config_file(_file) ++ self.update(config) ++ if valid is not True: ++ errors[_file] = valid ++ return errors or True ++ ++ ++def get_user_config_dir(app_name, app_author, roaming=True, force_xdg=True): ++ """Returns the config folder for the application. The default behavior ++ is to return whatever is most appropriate for the operating system. ++ ++ For an example application called ``"My App"`` by ``"Acme"``, ++ something like the following folders could be returned: ++ ++ macOS (non-XDG): ++ ``~/Library/Application Support/My App`` ++ Mac OS X (XDG): ++ ``~/.config/my-app`` ++ Unix: ++ ``~/.config/my-app`` ++ Windows 7 (roaming): ++ ``C:\\Users\<user>\AppData\Roaming\Acme\My App`` ++ Windows 7 (not roaming): ++ ``C:\\Users\<user>\AppData\Local\Acme\My App`` ++ ++ :param app_name: the application name. This should be properly capitalized ++ and can contain whitespace. ++ :param app_author: The app author's name (or company). This should be ++ properly capitalized and can contain whitespace. ++ :param roaming: controls if the folder should be roaming or not on Windows. ++ Has no effect on non-Windows systems. ++ :param force_xdg: if this is set to `True`, then on macOS the XDG Base ++ Directory Specification will be followed. Has no effect ++ on non-macOS systems. ++ ++ """ ++ if WIN: ++ key = 'APPDATA' if roaming else 'LOCALAPPDATA' ++ folder = os.path.expanduser(os.environ.get(key, '~')) ++ return os.path.join(folder, app_author, app_name) ++ if MAC and not force_xdg: ++ return os.path.join(os.path.expanduser( ++ '~/Library/Application Support'), app_name) ++ return os.path.join( ++ os.path.expanduser(os.environ.get('XDG_CONFIG_HOME', '~/.config')), ++ _pathify(app_name)) ++ ++ ++def get_system_config_dirs(app_name, app_author, force_xdg=True): ++ r"""Returns a list of system-wide config folders for the application. ++ ++ For an example application called ``"My App"`` by ``"Acme"``, ++ something like the following folders could be returned: ++ ++ macOS (non-XDG): ++ ``['/Library/Application Support/My App']`` ++ Mac OS X (XDG): ++ ``['/etc/xdg/my-app']`` ++ Unix: ++ ``['/etc/xdg/my-app']`` ++ Windows 7: ++ ``['C:\ProgramData\Acme\My App']`` ++ ++ :param app_name: the application name. This should be properly capitalized ++ and can contain whitespace. ++ :param app_author: The app author's name (or company). This should be ++ properly capitalized and can contain whitespace. ++ :param force_xdg: if this is set to `True`, then on macOS the XDG Base ++ Directory Specification will be followed. Has no effect ++ on non-macOS systems. ++ ++ """ ++ if WIN: ++ folder = os.environ.get('PROGRAMDATA') ++ return [os.path.join(folder, app_author, app_name)] ++ if MAC and not force_xdg: ++ return [os.path.join('/Library/Application Support', app_name)] ++ dirs = os.environ.get('XDG_CONFIG_DIRS', '/etc/xdg') ++ paths = [os.path.expanduser(x) for x in dirs.split(os.pathsep)] ++ return [os.path.join(d, _pathify(app_name)) for d in paths] ++ ++ ++def _pathify(s): ++ """Convert spaces to hyphens and lowercase a string.""" ++ return '-'.join(s.split()).lower() +diff --git a/pgcli/packages/cli_helpers/tabular_output/__init__.py b/pgcli/packages/cli_helpers/tabular_output/__init__.py +new file mode 100644 +index 0000000..de2f62f +--- /dev/null ++++ b/pgcli/packages/cli_helpers/tabular_output/__init__.py +@@ -0,0 +1,13 @@ ++# -*- coding: utf-8 -*- ++"""CLI Helper's tabular output module makes it easy to format your data using ++various formatting libraries. ++ ++When formatting data, you'll primarily use the ++:func:`~cli_helpers.tabular_output.format_output` function and ++:class:`~cli_helpers.tabular_output.TabularOutputFormatter` class. ++ ++""" ++ ++from .output_formatter import format_output, TabularOutputFormatter ++ ++__all__ = ['format_output', 'TabularOutputFormatter'] +diff --git a/pgcli/packages/cli_helpers/tabular_output/delimited_output_adapter.py b/pgcli/packages/cli_helpers/tabular_output/delimited_output_adapter.py +new file mode 100644 +index 0000000..b13d008 +--- /dev/null ++++ b/pgcli/packages/cli_helpers/tabular_output/delimited_output_adapter.py +@@ -0,0 +1,48 @@ ++# -*- coding: utf-8 -*- ++"""A delimited data output adapter (e.g. CSV, TSV).""" ++ ++from __future__ import unicode_literals ++import contextlib ++ ++from ..compat import csv, StringIO ++from ..utils import filter_dict_by_key ++from .preprocessors import bytes_to_string, override_missing_value ++ ++supported_formats = ('csv', 'csv-tab') ++preprocessors = (override_missing_value, bytes_to_string) ++ ++ ++class linewriter(object): ++ def __init__(self): ++ self.reset() ++ ++ def reset(self): ++ self.line = None ++ ++ def write(self, d): ++ self.line = d ++ ++ ++def adapter(data, headers, table_format='csv', **kwargs): ++ """Wrap the formatting inside a function for TabularOutputFormatter.""" ++ keys = ('dialect', 'delimiter', 'doublequote', 'escapechar', ++ 'quotechar', 'quoting', 'skipinitialspace', 'strict') ++ if table_format == 'csv': ++ delimiter = ',' ++ elif table_format == 'csv-tab': ++ delimiter = '\t' ++ else: ++ raise ValueError('Invalid table_format specified.') ++ ++ ckwargs = {'delimiter': delimiter, 'lineterminator': ''} ++ ckwargs.update(filter_dict_by_key(kwargs, keys)) ++ ++ l = linewriter() ++ writer = csv.writer(l, **ckwargs) ++ writer.writerow(headers) ++ yield l.line ++ ++ for row in data: ++ l.reset() ++ writer.writerow(row) ++ yield l.line +diff --git a/pgcli/packages/cli_helpers/tabular_output/output_formatter.py b/pgcli/packages/cli_helpers/tabular_output/output_formatter.py +new file mode 100644 +index 0000000..471a2ea +--- /dev/null ++++ b/pgcli/packages/cli_helpers/tabular_output/output_formatter.py +@@ -0,0 +1,228 @@ ++# -*- coding: utf-8 -*- ++"""A generic tabular data output formatter interface.""" ++ ++from __future__ import unicode_literals ++from collections import namedtuple ++ ++from ..compat import (text_type, binary_type, int_types, float_types, ++ zip_longest) ++from ..utils import unique_items ++from . import (delimited_output_adapter, vertical_table_adapter, ++ tabulate_adapter, terminaltables_adapter, tsv_output_adapter) ++from decimal import Decimal ++ ++import itertools ++ ++MISSING_VALUE = '<null>' ++MAX_FIELD_WIDTH = 500 ++ ++TYPES = { ++ type(None): 0, ++ bool: 1, ++ int: 2, ++ float: 3, ++ Decimal: 3, ++ binary_type: 4, ++ text_type: 5 ++} ++ ++OutputFormatHandler = namedtuple( ++ 'OutputFormatHandler', ++ 'format_name preprocessors formatter formatter_args') ++ ++ ++class TabularOutputFormatter(object): ++ """An interface to various tabular data formatting libraries. ++ ++ The formatting libraries supported include: ++ - `tabulate <https://bitbucket.org/astanin/python-tabulate>`_ ++ - `terminaltables <https://robpol86.github.io/terminaltables/>`_ ++ - a CLI Helper vertical table layout ++ - delimited formats (CSV and TSV) ++ ++ :param str format_name: An optional, default format name. ++ ++ Usage:: ++ ++ >>> from cli_helpers.tabular_output import TabularOutputFormatter ++ >>> formatter = TabularOutputFormatter(format_name='simple') ++ >>> data = ((1, 87), (2, 80), (3, 79)) ++ >>> headers = ('day', 'temperature') ++ >>> print(formatter.format_output(data, headers)) ++ day temperature ++ ----- ------------- ++ 1 87 ++ 2 80 ++ 3 79 ++ ++ You can use any :term:`iterable` for the data or headers:: ++ ++ >>> data = enumerate(('87', '80', '79'), 1) ++ >>> print(formatter.format_output(data, headers)) ++ day temperature ++ ----- ------------- ++ 1 87 ++ 2 80 ++ 3 79 ++ ++ """ ++ ++ _output_formats = {} ++ ++ def __init__(self, format_name=None): ++ """Set the default *format_name*.""" ++ self._format_name = None ++ ++ if format_name: ++ self.format_name = format_name ++ ++ @property ++ def format_name(self): ++ """The current format name. ++ ++ This value must be in :data:`supported_formats`. ++ ++ """ ++ return self._format_name ++ ++ @format_name.setter ++ def format_name(self, format_name): ++ """Set the default format name. ++ ++ :param str format_name: The display format name. ++ :raises ValueError: if the format is not recognized. ++ ++ """ ++ if format_name in self.supported_formats: ++ self._format_name = format_name ++ else: ++ raise ValueError('unrecognized format_name "{}"'.format( ++ format_name)) ++ ++ @property ++ def supported_formats(self): ++ """The names of the supported output formats in a :class:`tuple`.""" ++ return tuple(self._output_formats.keys()) ++ ++ @classmethod ++ def register_new_formatter(cls, format_name, handler, preprocessors=(), ++ kwargs=None): ++ """Register a new output formatter. ++ ++ :param str format_name: The name of the format. ++ :param callable handler: The function that formats the data. ++ :param tuple preprocessors: The preprocessors to call before ++ formatting. ++ :param dict kwargs: Keys/values for keyword argument defaults. ++ ++ """ ++ cls._output_formats[format_name] = OutputFormatHandler( ++ format_name, preprocessors, handler, kwargs or {}) ++ ++ def format_output(self, data, headers, format_name=None, ++ preprocessors=(), column_types=None, **kwargs): ++ """Format the headers and data using a specific formatter. ++ ++ *format_name* must be a supported formatter (see ++ :attr:`supported_formats`). ++ ++ :param iterable data: An :term:`iterable` (e.g. list) of rows. ++ :param iterable headers: The column headers. ++ :param str format_name: The display format to use (optional, if the ++ :class:`TabularOutputFormatter` object has a default format set). ++ :param tuple preprocessors: Additional preprocessors to call before ++ any formatter preprocessors. ++ :param \*\*kwargs: Optional arguments for the formatter. ++ :return: The formatted data. ++ :rtype: str ++ :raises ValueError: If the *format_name* is not recognized. ++ ++ """ ++ format_name = format_name or self._format_name ++ if format_name not in self.supported_formats: ++ raise ValueError('unrecognized format "{}"'.format(format_name)) ++ ++ (_, _preprocessors, formatter, ++ fkwargs) = self._output_formats[format_name] ++ fkwargs.update(kwargs) ++ if column_types is None: ++ data = list(data) ++ column_types = self._get_column_types(data) ++ for f in unique_items(preprocessors + _preprocessors): ++ data, headers = f(data, headers, column_types=column_types, ++ **fkwargs) ++ return formatter(list(data), headers, column_types=column_types, **fkwargs) ++ ++ def _get_column_types(self, data): ++ """Get a list of the data types for each column in *data*.""" ++ columns = list(zip_longest(*data)) ++ return [self._get_column_type(column) for column in columns] ++ ++ def _get_column_type(self, column): ++ """Get the most generic data type for iterable *column*.""" ++ type_values = [TYPES[self._get_type(v)] for v in column] ++ inverse_types = {v: k for k, v in TYPES.items()} ++ return inverse_types[max(type_values)] ++ ++ def _get_type(self, value): ++ """Get the data type for *value*.""" ++ if value is None: ++ return type(None) ++ elif type(value) in int_types: ++ return int ++ elif type(value) in float_types: ++ return float ++ elif isinstance(value, binary_type): ++ return binary_type ++ else: ++ return text_type ++ ++ ++def format_output(data, headers, format_name, **kwargs): ++ """Format output using *format_name*. ++ ++ This is a wrapper around the :class:`TabularOutputFormatter` class. ++ ++ :param iterable data: An :term:`iterable` (e.g. list) of rows. ++ :param iterable headers: The column headers. ++ :param str format_name: The display format to use. ++ :param \*\*kwargs: Optional arguments for the formatter. ++ :return: The formatted data. ++ :rtype: str ++ ++ """ ++ formatter = TabularOutputFormatter(format_name=format_name) ++ return formatter.format_output(data, headers, **kwargs) ++ ++ ++for vertical_format in vertical_table_adapter.supported_formats: ++ TabularOutputFormatter.register_new_formatter( ++ vertical_format, vertical_table_adapter.adapter, ++ vertical_table_adapter.preprocessors, ++ {'table_format': vertical_format, 'missing_value': MISSING_VALUE, 'max_field_width': None}) ++ ++for delimited_format in delimited_output_adapter.supported_formats: ++ TabularOutputFormatter.register_new_formatter( ++ delimited_format, delimited_output_adapter.adapter, ++ delimited_output_adapter.preprocessors, ++ {'table_format': delimited_format, 'missing_value': '', 'max_field_width': None}) ++ ++for tabulate_format in tabulate_adapter.supported_formats: ++ TabularOutputFormatter.register_new_formatter( ++ tabulate_format, tabulate_adapter.adapter, ++ tabulate_adapter.preprocessors + ++ (tabulate_adapter.style_output_table(tabulate_format),), ++ {'table_format': tabulate_format, 'missing_value': MISSING_VALUE, 'max_field_width': MAX_FIELD_WIDTH}) ++ ++for terminaltables_format in terminaltables_adapter.supported_formats: ++ TabularOutputFormatter.register_new_formatter( ++ terminaltables_format, terminaltables_adapter.adapter, ++ terminaltables_adapter.preprocessors + ++ (terminaltables_adapter.style_output_table(terminaltables_format),), ++ {'table_format': terminaltables_format, 'missing_value': MISSING_VALUE, 'max_field_width': MAX_FIELD_WIDTH}) ++ ++for tsv_format in tsv_output_adapter.supported_formats: ++ TabularOutputFormatter.register_new_formatter( ++ tsv_format, tsv_output_adapter.adapter, ++ tsv_output_adapter.preprocessors, ++ {'table_format': tsv_format, 'missing_value': '', 'max_field_width': None}) +diff --git a/pgcli/packages/cli_helpers/tabular_output/preprocessors.py b/pgcli/packages/cli_helpers/tabular_output/preprocessors.py +new file mode 100644 +index 0000000..37bb788 +--- /dev/null ++++ b/pgcli/packages/cli_helpers/tabular_output/preprocessors.py +@@ -0,0 +1,266 @@ ++# -*- coding: utf-8 -*- ++"""These preprocessor functions are used to process data prior to output.""" ++ ++import string ++ ++from .. import utils ++from ..compat import (text_type, int_types, float_types, ++ HAS_PYGMENTS, Terminal256Formatter, StringIO) ++ ++ ++def truncate_string(data, headers, max_field_width=None, skip_multiline_string=True, **_): ++ """Truncate very long strings. Only needed for tabular ++ representation, because trying to tabulate very long data ++ is problematic in terms of performance, and does not make any ++ sense visually. ++ ++ :param iterable data: An :term:`iterable` (e.g. list) of rows. ++ :param iterable headers: The column headers. ++ :param int max_field_width: Width to truncate field for display ++ :return: The processed data and headers. ++ :rtype: tuple ++ """ ++ return (([utils.truncate_string(v, max_field_width, skip_multiline_string) for v in row] for row in data), ++ [utils.truncate_string(h, max_field_width, skip_multiline_string) for h in headers]) ++ ++ ++def convert_to_string(data, headers, **_): ++ """Convert all *data* and *headers* to strings. ++ ++ Binary data that cannot be decoded is converted to a hexadecimal ++ representation via :func:`binascii.hexlify`. ++ ++ :param iterable data: An :term:`iterable` (e.g. list) of rows. ++ :param iterable headers: The column headers. ++ :return: The processed data and headers. ++ :rtype: tuple ++ ++ """ ++ return (([utils.to_string(v) for v in row] for row in data), ++ [utils.to_string(h) for h in headers]) ++ ++ ++def override_missing_value(data, headers, missing_value='', **_): ++ """Override missing values in the *data* with *missing_value*. ++ ++ A missing value is any value that is :data:`None`. ++ ++ :param iterable data: An :term:`iterable` (e.g. list) of rows. ++ :param iterable headers: The column headers. ++ :param missing_value: The default value to use for missing data. ++ :return: The processed data and headers. ++ :rtype: tuple ++ ++ """ ++ return (([missing_value if v is None else v for v in row] for row in data), ++ headers) ++ ++ ++def override_tab_value(data, headers, new_value=' ', **_): ++ """Override tab values in the *data* with *new_value*. ++ ++ :param iterable data: An :term:`iterable` (e.g. list) of rows. ++ :param iterable headers: The column headers. ++ :param new_value: The new value to use for tab. ++ :return: The processed data and headers. ++ :rtype: tuple ++ ++ """ ++ return (([v.replace('\t', new_value) if isinstance(v, text_type) else v ++ for v in row] for row in data), ++ headers) ++ ++ ++def bytes_to_string(data, headers, **_): ++ """Convert all *data* and *headers* bytes to strings. ++ ++ Binary data that cannot be decoded is converted to a hexadecimal ++ representation via :func:`binascii.hexlify`. ++ ++ :param iterable data: An :term:`iterable` (e.g. list) of rows. ++ :param iterable headers: The column headers. ++ :return: The processed data and headers. ++ :rtype: tuple ++ ++ """ ++ return (([utils.bytes_to_string(v) for v in row] for row in data), ++ [utils.bytes_to_string(h) for h in headers]) ++ ++ ++def align_decimals(data, headers, column_types=(), **_): ++ """Align numbers in *data* on their decimal points. ++ ++ Whitespace padding is added before a number so that all numbers in a ++ column are aligned. ++ ++ Outputting data before aligning the decimals:: ++ ++ 1 ++ 2.1 ++ 10.59 ++ ++ Outputting data after aligning the decimals:: ++ ++ 1 ++ 2.1 ++ 10.59 ++ ++ :param iterable data: An :term:`iterable` (e.g. list) of rows. ++ :param iterable headers: The column headers. ++ :param iterable column_types: The columns' type objects (e.g. int or float). ++ :return: The processed data and headers. ++ :rtype: tuple ++ ++ """ ++ pointpos = len(headers) * [0] ++ data = list(data) ++ for row in data: ++ for i, v in enumerate(row): ++ if column_types[i] is float and type(v) in float_types: ++ v = text_type(v) ++ pointpos[i] = max(utils.intlen(v), pointpos[i]) ++ ++ def results(data): ++ for row in data: ++ result = [] ++ for i, v in enumerate(row): ++ if column_types[i] is float and type(v) in float_types: ++ v = text_type(v) ++ result.append((pointpos[i] - utils.intlen(v)) * " " + v) ++ else: ++ result.append(v) ++ yield result ++ ++ return results(data), headers ++ ++ ++def quote_whitespaces(data, headers, quotestyle="'", **_): ++ """Quote leading/trailing whitespace in *data*. ++ ++ When outputing data with leading or trailing whitespace, it can be useful ++ to put quotation marks around the value so the whitespace is more ++ apparent. If one value in a column needs quoted, then all values in that ++ column are quoted to keep things consistent. ++ ++ .. NOTE:: ++ :data:`string.whitespace` is used to determine which characters are ++ whitespace. ++ ++ :param iterable data: An :term:`iterable` (e.g. list) of rows. ++ :param iterable headers: The column headers. ++ :param str quotestyle: The quotation mark to use (defaults to ``'``). ++ :return: The processed data and headers. ++ :rtype: tuple ++ ++ """ ++ whitespace = tuple(string.whitespace) ++ quote = len(headers) * [False] ++ data = list(data) ++ for row in data: ++ for i, v in enumerate(row): ++ v = text_type(v) ++ if v.startswith(whitespace) or v.endswith(whitespace): ++ quote[i] = True ++ ++ def results(data): ++ for row in data: ++ result = [] ++ for i, v in enumerate(row): ++ quotation = quotestyle if quote[i] else '' ++ result.append('{quotestyle}{value}{quotestyle}'.format( ++ quotestyle=quotation, value=v)) ++ yield result ++ return results(data), headers ++ ++ ++def style_output(data, headers, style=None, ++ header_token='Token.Output.Header', ++ odd_row_token='Token.Output.OddRow', ++ even_row_token='Token.Output.EvenRow', **_): ++ """Style the *data* and *headers* (e.g. bold, italic, and colors) ++ ++ .. NOTE:: ++ This requires the `Pygments <http://pygments.org/>`_ library to ++ be installed. You can install it with CLI Helpers as an extra:: ++ $ pip install cli_helpers[styles] ++ ++ Example usage:: ++ ++ from cli_helpers.tabular_output.preprocessors import style_output ++ from pygments.style import Style ++ from pygments.token import Token ++ ++ class YourStyle(Style): ++ default_style = "" ++ styles = { ++ Token.Output.Header: 'bold ansibrightred', ++ Token.Output.OddRow: 'bg:#eee #111', ++ Token.Output.EvenRow: '#0f0' ++ } ++ ++ headers = ('First Name', 'Last Name') ++ data = [['Fred', 'Roberts'], ['George', 'Smith']] ++ ++ data, headers = style_output(data, headers, style=YourStyle) ++ ++ :param iterable data: An :term:`iterable` (e.g. list) of rows. ++ :param iterable headers: The column headers. ++ :param str/pygments.style.Style style: A Pygments style. You can `create ++ your own styles <http://pygments.org/docs/styles/#creating-own-styles>`_. ++ :param str header_token: The token type to be used for the headers. ++ :param str odd_row_token: The token type to be used for odd rows. ++ :param str even_row_token: The token type to be used for even rows. ++ :return: The styled data and headers. ++ :rtype: tuple ++ ++ """ ++ if style and HAS_PYGMENTS: ++ formatter = Terminal256Formatter(style=style) ++ ++ def style_field(token, field): ++ """Get the styled text for a *field* using *token* type.""" ++ s = StringIO() ++ formatter.format(((token, field),), s) ++ return s.getvalue() ++ ++ headers = [style_field(header_token, header) for header in headers] ++ data = ([style_field(odd_row_token if i % 2 else even_row_token, f) ++ for f in r] for i, r in enumerate(data, 1)) ++ ++ return iter(data), headers ++ ++ ++def format_numbers(data, headers, column_types=(), integer_format=None, ++ float_format=None, **_): ++ """Format numbers according to a format specification. ++ ++ This uses Python's format specification to format numbers of the following ++ types: :class:`int`, :class:`py2:long` (Python 2), :class:`float`, and ++ :class:`~decimal.Decimal`. See the :ref:`python:formatspec` for more ++ information about the format strings. ++ ++ .. NOTE:: ++ A column is only formatted if all of its values are the same type ++ (except for :data:`None`). ++ ++ :param iterable data: An :term:`iterable` (e.g. list) of rows. ++ :param iterable headers: The column headers. ++ :param iterable column_types: The columns' type objects (e.g. int or float). ++ :param str integer_format: The format string to use for integer columns. ++ :param str float_format: The format string to use for float columns. ++ :return: The processed data and headers. ++ :rtype: tuple ++ ++ """ ++ if (integer_format is None and float_format is None) or not column_types: ++ return iter(data), headers ++ ++ def _format_number(field, column_type): ++ if integer_format and column_type is int and type(field) in int_types: ++ return format(field, integer_format) ++ elif float_format and column_type is float and type(field) in float_types: ++ return format(field, float_format) ++ return field ++ ++ data = ([_format_number(v, column_types[i]) for i, v in enumerate(row)] for row in data) ++ return data, headers +diff --git a/pgcli/packages/cli_helpers/tabular_output/tabulate_adapter.py b/pgcli/packages/cli_helpers/tabular_output/tabulate_adapter.py +new file mode 100644 +index 0000000..3e66594 +--- /dev/null ++++ b/pgcli/packages/cli_helpers/tabular_output/tabulate_adapter.py +@@ -0,0 +1,98 @@ ++# -*- coding: utf-8 -*- ++"""Format adapter for the tabulate module.""" ++ ++from __future__ import unicode_literals ++ ++from ..utils import filter_dict_by_key ++from .preprocessors import (convert_to_string, truncate_string, override_missing_value, ++ style_output, HAS_PYGMENTS, Terminal256Formatter, StringIO) ++ ++import tabulate ++ ++supported_markup_formats = ('mediawiki', 'html', 'latex', 'latex_booktabs', ++ 'textile', 'moinmoin', 'jira') ++supported_table_formats = ('plain', 'simple', 'grid', 'fancy_grid', 'pipe', ++ 'orgtbl', 'psql', 'rst') ++supported_formats = supported_markup_formats + supported_table_formats ++ ++preprocessors = (override_missing_value, convert_to_string, truncate_string, style_output) ++ ++ ++def style_output_table(format_name=""): ++ def style_output(data, headers, style=None, ++ table_separator_token='Token.Output.TableSeparator', **_): ++ """Style the *table* a(e.g. bold, italic, and colors) ++ ++ .. NOTE:: ++ This requires the `Pygments <http://pygments.org/>`_ library to ++ be installed. You can install it with CLI Helpers as an extra:: ++ $ pip install cli_helpers[styles] ++ ++ Example usage:: ++ ++ from cli_helpers.tabular_output import tabulate_adapter ++ from pygments.style import Style ++ from pygments.token import Token ++ ++ class YourStyle(Style): ++ default_style = "" ++ styles = { ++ Token.Output.TableSeparator: '#ansigray' ++ } ++ ++ headers = ('First Name', 'Last Name') ++ data = [['Fred', 'Roberts'], ['George', 'Smith']] ++ style_output_table = tabulate_adapter.style_output_table('psql') ++ style_output_table(data, headers, style=CliStyle) ++ ++ data, headers = style_output(data, headers, style=YourStyle) ++ output = tabulate_adapter.adapter(data, headers, style=YourStyle) ++ ++ :param iterable data: An :term:`iterable` (e.g. list) of rows. ++ :param iterable headers: The column headers. ++ :param str/pygments.style.Style style: A Pygments style. You can `create ++ your own styles <http://pygments.org/docs/styles/#creating-own-styles>`_. ++ :param str table_separator_token: The token type to be used for the table separator. ++ :return: data and headers. ++ :rtype: tuple ++ ++ """ ++ if style and HAS_PYGMENTS and format_name in supported_table_formats: ++ formatter = Terminal256Formatter(style=style) ++ ++ def style_field(token, field): ++ """Get the styled text for a *field* using *token* type.""" ++ s = StringIO() ++ formatter.format(((token, field),), s) ++ return s.getvalue() ++ ++ def addColorInElt(elt): ++ if not elt: ++ return elt ++ if elt.__class__ == tabulate.Line: ++ return tabulate.Line(*(style_field(table_separator_token, val) for val in elt)) ++ if elt.__class__ == tabulate.DataRow: ++ return tabulate.DataRow(*(style_field(table_separator_token, val) for val in elt)) ++ return elt ++ ++ srcfmt = tabulate._table_formats[format_name] ++ newfmt = tabulate.TableFormat( ++ *(addColorInElt(val) for val in srcfmt)) ++ tabulate._table_formats[format_name] = newfmt ++ ++ return iter(data), headers ++ return style_output ++ ++def adapter(data, headers, table_format=None, preserve_whitespace=False, ++ **kwargs): ++ """Wrap tabulate inside a function for TabularOutputFormatter.""" ++ keys = ('floatfmt', 'numalign', 'stralign', 'showindex', 'disable_numparse') ++ tkwargs = {'tablefmt': table_format} ++ tkwargs.update(filter_dict_by_key(kwargs, keys)) ++ ++ if table_format in supported_markup_formats: ++ tkwargs.update(numalign=None, stralign=None) ++ ++ tabulate.PRESERVE_WHITESPACE = preserve_whitespace ++ ++ return iter(tabulate.tabulate(data, headers, **tkwargs).split('\n')) +diff --git a/pgcli/packages/cli_helpers/tabular_output/terminaltables_adapter.py b/pgcli/packages/cli_helpers/tabular_output/terminaltables_adapter.py +new file mode 100644 +index 0000000..270e2f4 +--- /dev/null ++++ b/pgcli/packages/cli_helpers/tabular_output/terminaltables_adapter.py +@@ -0,0 +1,96 @@ ++# -*- coding: utf-8 -*- ++"""Format adapter for the terminaltables module.""" ++ ++from __future__ import unicode_literals ++ ++import terminaltables ++import itertools ++ ++from ..utils import filter_dict_by_key ++from .preprocessors import (convert_to_string, truncate_string, override_missing_value, ++ style_output, HAS_PYGMENTS, Terminal256Formatter, StringIO, ++ override_tab_value) ++ ++supported_formats = ('ascii', 'double', 'github') ++preprocessors = ( ++ override_missing_value, convert_to_string, override_tab_value, ++ truncate_string, style_output ++) ++ ++table_format_handler = { ++ 'ascii': terminaltables.AsciiTable, ++ 'double': terminaltables.DoubleTable, ++ 'github': terminaltables.GithubFlavoredMarkdownTable, ++} ++ ++ ++def style_output_table(format_name=""): ++ def style_output(data, headers, style=None, ++ table_separator_token='Token.Output.TableSeparator', **_): ++ """Style the *table* (e.g. bold, italic, and colors) ++ ++ .. NOTE:: ++ This requires the `Pygments <http://pygments.org/>`_ library to ++ be installed. You can install it with CLI Helpers as an extra:: ++ $ pip install cli_helpers[styles] ++ ++ Example usage:: ++ ++ from cli_helpers.tabular_output import terminaltables_adapter ++ from pygments.style import Style ++ from pygments.token import Token ++ ++ class YourStyle(Style): ++ default_style = "" ++ styles = { ++ Token.Output.TableSeparator: '#ansigray' ++ } ++ ++ headers = ('First Name', 'Last Name') ++ data = [['Fred', 'Roberts'], ['George', 'Smith']] ++ style_output_table = terminaltables_adapter.style_output_table('psql') ++ style_output_table(data, headers, style=CliStyle) ++ ++ output = terminaltables_adapter.adapter(data, headers, style=YourStyle) ++ ++ :param iterable data: An :term:`iterable` (e.g. list) of rows. ++ :param iterable headers: The column headers. ++ :param str/pygments.style.Style style: A Pygments style. You can `create ++ your own styles <http://pygments.org/docs/styles/#creating-own-styles>`_. ++ :param str table_separator_token: The token type to be used for the table separator. ++ :return: data and headers. ++ :rtype: tuple ++ ++ """ ++ if style and HAS_PYGMENTS and format_name in supported_formats: ++ formatter = Terminal256Formatter(style=style) ++ ++ def style_field(token, field): ++ """Get the styled text for a *field* using *token* type.""" ++ s = StringIO() ++ formatter.format(((token, field),), s) ++ return s.getvalue() ++ ++ clss = table_format_handler[format_name] ++ for char in [char for char in terminaltables.base_table.BaseTable.__dict__ if char.startswith("CHAR_")]: ++ setattr(clss, char, style_field( ++ table_separator_token, getattr(clss, char))) ++ ++ return iter(data), headers ++ return style_output ++ ++ ++def adapter(data, headers, table_format=None, **kwargs): ++ """Wrap terminaltables inside a function for TabularOutputFormatter.""" ++ keys = ('title', ) ++ ++ table = table_format_handler[table_format] ++ ++ t = table([headers] + list(data), **filter_dict_by_key(kwargs, keys)) ++ ++ dimensions = terminaltables.width_and_alignment.max_dimensions( ++ t.table_data, ++ t.padding_left, ++ t.padding_right)[:3] ++ for r in t.gen_table(*dimensions): ++ yield u''.join(r) +diff --git a/pgcli/packages/cli_helpers/tabular_output/tsv_output_adapter.py b/pgcli/packages/cli_helpers/tabular_output/tsv_output_adapter.py +new file mode 100644 +index 0000000..69714f1 +--- /dev/null ++++ b/pgcli/packages/cli_helpers/tabular_output/tsv_output_adapter.py +@@ -0,0 +1,16 @@ ++# -*- coding: utf-8 -*- ++"""A tsv data output adapter""" ++ ++from __future__ import unicode_literals ++ ++from .preprocessors import bytes_to_string, override_missing_value, convert_to_string ++from itertools import chain ++from ..utils import replace ++ ++supported_formats = ('tsv',) ++preprocessors = (override_missing_value, bytes_to_string, convert_to_string) ++ ++def adapter(data, headers, **kwargs): ++ """Wrap the formatting inside a function for TabularOutputFormatter.""" ++ for row in chain((headers,), data): ++ yield "\t".join((replace(r, (('\n', r'\n'), ('\t', r'\t'))) for r in row)) +diff --git a/pgcli/packages/cli_helpers/tabular_output/vertical_table_adapter.py b/pgcli/packages/cli_helpers/tabular_output/vertical_table_adapter.py +new file mode 100644 +index 0000000..4a44521 +--- /dev/null ++++ b/pgcli/packages/cli_helpers/tabular_output/vertical_table_adapter.py +@@ -0,0 +1,66 @@ ++# -*- coding: utf-8 -*- ++"""Format data into a vertical table layout.""" ++ ++from __future__ import unicode_literals ++ ++from ..utils import filter_dict_by_key ++from .preprocessors import (convert_to_string, override_missing_value, ++ style_output) ++ ++supported_formats = ('vertical', ) ++preprocessors = (override_missing_value, convert_to_string, style_output) ++ ++ ++def _get_separator(num, sep_title, sep_character, sep_length): ++ """Get a row separator for row *num*.""" ++ left_divider_length = right_divider_length = sep_length ++ if isinstance(sep_length, tuple): ++ left_divider_length, right_divider_length = sep_length ++ left_divider = sep_character * left_divider_length ++ right_divider = sep_character * right_divider_length ++ title = sep_title.format(n=num + 1) ++ ++ return "{left_divider}[ {title} ]{right_divider}\n".format( ++ left_divider=left_divider, right_divider=right_divider, title=title) ++ ++ ++def _format_row(headers, row): ++ """Format a row.""" ++ formatted_row = [' | '.join(field) for field in zip(headers, row)] ++ return '\n'.join(formatted_row) ++ ++ ++def vertical_table(data, headers, sep_title='{n}. row', sep_character='*', ++ sep_length=27): ++ """Format *data* and *headers* as an vertical table. ++ ++ The values in *data* and *headers* must be strings. ++ ++ :param iterable data: An :term:`iterable` (e.g. list) of rows. ++ :param iterable headers: The column headers. ++ :param str sep_title: The title given to each row separator. Defaults to ++ ``'{n}. row'``. Any instance of ``'{n}'`` is ++ replaced by the record number. ++ :param str sep_character: The character used to separate rows. Defaults to ++ ``'*'``. ++ :param int/tuple sep_length: The number of separator characters that should ++ appear on each side of the *sep_title*. Use ++ a tuple to specify the left and right values ++ separately. ++ :return: The formatted data. ++ :rtype: str ++ ++ """ ++ header_len = max([len(x) for x in headers]) ++ padded_headers = [x.ljust(header_len) for x in headers] ++ formatted_rows = [_format_row(padded_headers, row) for row in data] ++ ++ output = [] ++ for i, result in enumerate(formatted_rows): ++ yield _get_separator(i, sep_title, sep_character, sep_length) + result ++ ++ ++def adapter(data, headers, **kwargs): ++ """Wrap vertical table in a function for TabularOutputFormatter.""" ++ keys = ('sep_title', 'sep_character', 'sep_length') ++ return vertical_table(data, headers, **filter_dict_by_key(kwargs, keys)) +diff --git a/pgcli/packages/cli_helpers/utils.py b/pgcli/packages/cli_helpers/utils.py +new file mode 100644 +index 0000000..5a5c032 +--- /dev/null ++++ b/pgcli/packages/cli_helpers/utils.py +@@ -0,0 +1,70 @@ ++# -*- coding: utf-8 -*- ++"""Various utility functions and helpers.""" ++ ++import binascii ++import re ++ ++from .compat import binary_type, text_type ++ ++ ++def bytes_to_string(b): ++ """Convert bytes *b* to a string. ++ ++ Hexlify bytes that can't be decoded. ++ ++ """ ++ if isinstance(b, binary_type): ++ try: ++ return b.decode('utf8') ++ except UnicodeDecodeError: ++ return '0x' + binascii.hexlify(b).decode('ascii') ++ return b ++ ++ ++def to_string(value): ++ """Convert *value* to a string.""" ++ if isinstance(value, binary_type): ++ return bytes_to_string(value) ++ else: ++ return text_type(value) ++ ++ ++def truncate_string(value, max_width=None, skip_multiline_string=True): ++ """Truncate string values.""" ++ if skip_multiline_string and isinstance(value, text_type) and '\n' in value: ++ return value ++ elif isinstance(value, text_type) and max_width is not None and len(value) > max_width: ++ return value[:max_width-3] + "..." ++ return value ++ ++ ++def intlen(n): ++ """Find the length of the integer part of a number *n*.""" ++ pos = n.find('.') ++ return len(n) if pos < 0 else pos ++ ++ ++def filter_dict_by_key(d, keys): ++ """Filter the dict *d* to remove keys not in *keys*.""" ++ return {k: v for k, v in d.items() if k in keys} ++ ++ ++def unique_items(seq): ++ """Return the unique items from iterable *seq* (in order).""" ++ seen = set() ++ return [x for x in seq if not (x in seen or seen.add(x))] ++ ++ ++_ansi_re = re.compile('\033\[((?:\d|;)*)([a-zA-Z])') ++ ++ ++def strip_ansi(value): ++ """Strip the ANSI escape sequences from a string.""" ++ return _ansi_re.sub('', value) ++ ++ ++def replace(s, replace): ++ """Replace multiple values in a string""" ++ for r in replace: ++ s = s.replace(*r) ++ return s +diff --git a/pgcli/pgcompleter.py b/pgcli/pgcompleter.py +index b2a5783..3f1bbcf 100644 +--- a/pgcli/pgcompleter.py ++++ b/pgcli/pgcompleter.py +@@ -3,7 +3,7 @@ import re + from itertools import count, repeat, chain + import operator + from collections import namedtuple, defaultdict, OrderedDict +-from cli_helpers.tabular_output import TabularOutputFormatter ++from .packages.cli_helpers.tabular_output import TabularOutputFormatter + from pgspecial.namedqueries import NamedQueries + from prompt_toolkit.completion import Completer, Completion, PathCompleter + from prompt_toolkit.document import Document diff --git a/debian/patches/series b/debian/patches/series new file mode 100644 index 0000000..0795e22 --- /dev/null +++ b/debian/patches/series @@ -0,0 +1,2 @@ +0001-Change-dependencies-and-rename-entrypoint.patch +0002-vendorized-cli_helpers.patch diff --git a/debian/pgcli.1 b/debian/pgcli.1 new file mode 100644 index 0000000..55827dd --- /dev/null +++ b/debian/pgcli.1 @@ -0,0 +1,51 @@ +.TH PGCLI 1 "Jul 01, 2016" +.\" Please adjust this date whenever revising the manpage. +.SH NAME +pgcli \- Command line client for PostgreSQL +.SH SYNOPSIS +\fBpgcli\fP [\fIOPTIONS\fP] \fI[DATABASE\fP] \fI[USERNAME\fP] +.SH DESCRIPTION +pgcli is a command line interface for PostgreSQL with +auto-completion and syntax highlighting. The CLI is also capable of pretty +printing tabular data. +.SH OPTIONS +.TP +.BI -h|--host\ \fITEXT\fP +Host address of the postgres database +.TP +.BI -p|--port\ \fIINTEGER\fP +Port number at which the postgres instance is listening +.TP +.BI -U|--user\ \fITEXT\fP +User name to connect to the postgres database +.TP +.BI -W|--password +Force password prompt +.TP +.BI -w|--no-password +Never prompt for password +.TP +.BI -v|--version +Version of pgcli +.TP +.BI -d|--dbname \ \fITEXT\fP +Database name to connect to +.TP +.BI --pgclirc \ \fITEXT\fP +Location of pgclirc file +.TP +.BI --help +Show this message and exit +.RE +.SH EXAMPLE USAGE +$ pgcli local_database +.TP +$ pgcli postgres://amjith:passw0rd@example.com:5432/app_db +.TP +$ pgcli -h localhost -p 5432 -U amjith app_db +.RE +.SH AUTHOR +pgcli is written by Amjith Ramanujam (http://pgcli.com) +.PP +This manual page was written by Lennart Weller <lhw@ring0.de>, +for the Debian project (but may be used by others). diff --git a/debian/pgcli.manpages b/debian/pgcli.manpages new file mode 100644 index 0000000..a9d6b72 --- /dev/null +++ b/debian/pgcli.manpages @@ -0,0 +1 @@ +debian/pgcli.1 diff --git a/debian/rules b/debian/rules new file mode 100755 index 0000000..5117815 --- /dev/null +++ b/debian/rules @@ -0,0 +1,10 @@ +#!/usr/bin/make -f + +export PYBUILD_NAME=pgcli +%: + dh $@ --with python3 --buildsystem=pybuild + +override_dh_auto_install: + python3 setup.py install --root=debian/pgcli --install-layout=deb --install-lib=/usr/share/pgcli --install-scripts=/usr/share/pgcli/ + +override_dh_auto_build: diff --git a/debian/source/format b/debian/source/format new file mode 100644 index 0000000..163aaf8 --- /dev/null +++ b/debian/source/format @@ -0,0 +1 @@ +3.0 (quilt) diff --git a/debian/watch b/debian/watch new file mode 100644 index 0000000..3fa18c4 --- /dev/null +++ b/debian/watch @@ -0,0 +1,3 @@ +version=3 +opts=uversionmangle=s/(rc|a|b|c)/~$1/ \ +https://pypi.debian.net/pgcli/pgcli-(.+)\.(?:zip|tgz|tbz|txz|(?:tar\.(?:gz|bz2|xz))) |