mirror of https://github.com/dbcli/pgcli
Merge pull request #735 from dbcli/feature/cli_helpers_output_format
Use CLI Helpers for output formatting
This commit is contained in:
commit
018f495f10
|
@ -25,11 +25,3 @@ 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.
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
This program also bundles with it python-tabulate
|
||||
(https://pypi.python.org/pypi/tabulate) library. This library is licensed under
|
||||
MIT License.
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
|
52
README.rst
52
README.rst
|
@ -1,4 +1,4 @@
|
|||
A REPL for Postgres
|
||||
A REPL for Postgres
|
||||
-------------------
|
||||
|
||||
|Build Status| |PyPI| |Gitter|
|
||||
|
@ -25,25 +25,25 @@ If you already know how to install python packages, then you can simply do:
|
|||
|
||||
$ brew tap dbcli/tap && brew tap-pin dbcli/tap && brew install pgcli # Only on macOS
|
||||
|
||||
If you don't know how to install python packages, please check the
|
||||
If you don't know how to install python packages, please check the
|
||||
`detailed instructions`__.
|
||||
|
||||
__ https://github.com/dbcli/pgcli#detailed-installation-instructions
|
||||
__ https://github.com/dbcli/pgcli#detailed-installation-instructions
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
::
|
||||
::
|
||||
|
||||
$ pgcli [database_name]
|
||||
|
||||
or
|
||||
|
||||
$ pgcli postgresql://[user[:password]@][netloc][:port][/dbname]
|
||||
$ pgcli postgresql://[user[:password]@][netloc][:port][/dbname]
|
||||
|
||||
Examples:
|
||||
Examples:
|
||||
|
||||
::
|
||||
::
|
||||
|
||||
$ pgcli local_database
|
||||
|
||||
|
@ -60,10 +60,10 @@ The `pgcli` is written using prompt_toolkit_.
|
|||
* Smart-completion (enabled by default) will suggest context-sensitive
|
||||
completion.
|
||||
|
||||
- ``SELECT * FROM <tab>`` will only show table names.
|
||||
- ``SELECT * FROM users WHERE <tab>`` will only show column names.
|
||||
- ``SELECT * FROM <tab>`` will only show table names.
|
||||
- ``SELECT * FROM users WHERE <tab>`` will only show column names.
|
||||
|
||||
* Primitive support for ``psql`` back-slash commands.
|
||||
* Primitive support for ``psql`` back-slash commands.
|
||||
* Pretty prints tabular data.
|
||||
Note: `pgcli` uses [tabulate](https://github.com/dbcli/pgcli/blob/master/pgcli/packages/tabulate.py)
|
||||
package to pretty-print tables. This library does smart formatting of numbers,
|
||||
|
@ -86,7 +86,7 @@ get this running in a development setup.
|
|||
|
||||
https://github.com/dbcli/pgcli/blob/master/DEVELOP.rst
|
||||
|
||||
Please feel free to reach out to me if you need help.
|
||||
Please feel free to reach out to me if you need help.
|
||||
My email: amjith.r@gmail.com, Twitter: `@amjithr <http://twitter.com/amjithr>`_
|
||||
|
||||
Detailed Installation Instructions:
|
||||
|
@ -107,22 +107,22 @@ Done!
|
|||
|
||||
Alternatively, you can install ``pgcli`` as a python package using a package
|
||||
manager called called ``pip``. You will need postgres installed on your system
|
||||
for this to work.
|
||||
for this to work.
|
||||
|
||||
In depth getting started guide for ``pip`` - https://pip.pypa.io/en/latest/installing.html.
|
||||
|
||||
::
|
||||
::
|
||||
|
||||
$ which pip
|
||||
|
||||
If it is installed then you can do:
|
||||
|
||||
::
|
||||
::
|
||||
|
||||
$ pip install pgcli
|
||||
|
||||
If that fails due to permission issues, you might need to run the command with
|
||||
sudo permissions.
|
||||
sudo permissions.
|
||||
|
||||
::
|
||||
|
||||
|
@ -130,7 +130,7 @@ sudo permissions.
|
|||
|
||||
If pip is not installed check if easy_install is available on the system.
|
||||
|
||||
::
|
||||
::
|
||||
|
||||
$ which easy_install
|
||||
|
||||
|
@ -143,12 +143,12 @@ In depth getting started guide for ``pip`` - https://pip.pypa.io/en/latest/insta
|
|||
|
||||
Check if pip is already available in your system.
|
||||
|
||||
::
|
||||
::
|
||||
|
||||
$ which pip
|
||||
|
||||
If it doesn't exist, use your linux package manager to install `pip`. This
|
||||
might look something like:
|
||||
might look something like:
|
||||
|
||||
::
|
||||
|
||||
|
@ -159,20 +159,20 @@ might look something like:
|
|||
$ sudo yum install python-pip # RHEL, Centos, Fedora etc
|
||||
|
||||
``pgcli`` requires python-dev, libpq-dev and libevent-dev packages. You can
|
||||
install these via your operating system package manager.
|
||||
install these via your operating system package manager.
|
||||
|
||||
|
||||
::
|
||||
|
||||
$ sudo apt-get install python-dev libpq-dev libevent-dev
|
||||
|
||||
or
|
||||
or
|
||||
|
||||
$ sudo yum install python-devel postgresql-devel
|
||||
|
||||
Then you can install pgcli:
|
||||
Then you can install pgcli:
|
||||
|
||||
::
|
||||
::
|
||||
|
||||
$ sudo pip install pgcli
|
||||
|
||||
|
@ -214,17 +214,11 @@ Thanks:
|
|||
-------
|
||||
|
||||
A special thanks to `Jonathan Slenders <https://twitter.com/jonathan_s>`_ for
|
||||
creating `Python Prompt Toolkit <http://github.com/jonathanslenders/python-prompt-toolkit>`_,
|
||||
creating `Python Prompt Toolkit <http://github.com/jonathanslenders/python-prompt-toolkit>`_,
|
||||
which is quite literally the backbone library, that made this app possible.
|
||||
Jonathan has also provided valuable feedback and support during the development
|
||||
of this app.
|
||||
|
||||
This app includes the awesome `tabulate <https://pypi.python.org/pypi/tabulate>`_
|
||||
library for pretty printing the output of tables. The reason for vendoring this
|
||||
library rather than listing it as a dependency in setup.py, is because I had to
|
||||
make a change to the table format which is merged back into the original repo,
|
||||
but not yet released in PyPI.
|
||||
|
||||
`Click <http://click.pocoo.org/>`_ is used for command line option parsing
|
||||
and printing error messages.
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ Upcoming
|
|||
* Use dbcli's Homebrew tap for installing pgcli on macOS (issue #718) (Thanks: `Thomas Roten`_).
|
||||
* Only set `LESS` environment variable if it's unset. (Thanks: `Irina Truong`_)
|
||||
* Quote schema in `SET SCHEMA` statement (issue #469) (Thanks: `Irina Truong`_)
|
||||
* Use CLI Helpers for pretty printing query results (Thanks: `Thomas Roten`_).
|
||||
|
||||
1.6.0
|
||||
=====
|
||||
|
|
|
@ -16,6 +16,9 @@ from time import time, sleep
|
|||
from codecs import open
|
||||
|
||||
|
||||
from cli_helpers.tabular_output import TabularOutputFormatter
|
||||
from cli_helpers.tabular_output.preprocessors import (align_decimals,
|
||||
format_numbers)
|
||||
import click
|
||||
try:
|
||||
import setproctitle
|
||||
|
@ -34,9 +37,7 @@ from prompt_toolkit.history import FileHistory
|
|||
from pygments.lexers.sql import PostgresLexer
|
||||
from pygments.token import Token
|
||||
|
||||
from .packages.tabulate import tabulate
|
||||
from .packages.expanded import expanded_table
|
||||
from pgspecial.main import (PGSpecial, NO_QUERY, content_exceeds_width)
|
||||
from pgspecial.main import (PGSpecial, NO_QUERY)
|
||||
import pgspecial as special
|
||||
from .pgcompleter import PGCompleter
|
||||
from .pgtoolbar import create_toolbar_tokens_func
|
||||
|
@ -758,7 +759,6 @@ class PGCli(object):
|
|||
return self.query_history[-1][0] if self.query_history else None
|
||||
|
||||
|
||||
|
||||
@click.command()
|
||||
# Default host is '' so psycopg2 can default to either localhost or unix socket
|
||||
@click.option('-h', '--host', default='', envvar='PGHOST',
|
||||
|
@ -862,35 +862,6 @@ def obfuscate_process_password():
|
|||
setproctitle.setproctitle(process_title)
|
||||
|
||||
|
||||
def format_output(title, cur, headers, status, settings):
|
||||
output = []
|
||||
missingval = settings.missingval
|
||||
table_format = settings.table_format
|
||||
dcmlfmt = settings.dcmlfmt
|
||||
floatfmt = settings.floatfmt
|
||||
expanded = settings.expanded
|
||||
max_width = settings.max_width
|
||||
case_function = settings.case_function
|
||||
if title: # Only print the title if it's not None.
|
||||
output.append(title)
|
||||
if cur:
|
||||
headers = [case_function(utf8tounicode(x)) for x in headers]
|
||||
if expanded and headers:
|
||||
output.append(expanded_table(cur, headers, missingval))
|
||||
else:
|
||||
tabulated, rows = tabulate(cur, headers, tablefmt=table_format,
|
||||
missingval=missingval, dcmlfmt=dcmlfmt, floatfmt=floatfmt)
|
||||
if (max_width and rows and
|
||||
content_exceeds_width(rows[0], max_width) and
|
||||
headers):
|
||||
output.append(expanded_table(rows, headers, missingval))
|
||||
else:
|
||||
output.append(tabulated)
|
||||
if status: # Only print the status if it's not None.
|
||||
output.append(status)
|
||||
return output
|
||||
|
||||
|
||||
def has_meta_cmd(query):
|
||||
"""Determines if the completion needs a refresh by checking if the sql
|
||||
statement is an alter, create, drop, commit or rollback."""
|
||||
|
@ -949,5 +920,49 @@ def exception_formatter(e):
|
|||
return click.style(utf8tounicode(str(e)), fg='red')
|
||||
|
||||
|
||||
def format_output(title, cur, headers, status, settings):
|
||||
output = []
|
||||
expanded = (settings.expanded or settings.table_format == 'vertical')
|
||||
table_format = ('vertical' if settings.expanded else
|
||||
settings.table_format)
|
||||
max_width = settings.max_width
|
||||
case_function = settings.case_function
|
||||
formatter = TabularOutputFormatter(format_name=table_format)
|
||||
|
||||
output_kwargs = {
|
||||
'sep_title': 'RECORD {n}',
|
||||
'sep_character': '-',
|
||||
'sep_length': (1, 25),
|
||||
'missing_value': settings.missingval,
|
||||
'integer_format': settings.dcmlfmt,
|
||||
'float_format': settings.floatfmt,
|
||||
'preprocessors': (format_numbers, ),
|
||||
'disable_numparse': True,
|
||||
'preserve_whitespace': True
|
||||
}
|
||||
if not settings.floatfmt:
|
||||
output_kwargs['preprocessors'] = (align_decimals, )
|
||||
|
||||
if title: # Only print the title if it's not None.
|
||||
output.append(title)
|
||||
|
||||
if cur:
|
||||
headers = [case_function(utf8tounicode(x)) for x in headers]
|
||||
rows = list(cur)
|
||||
formatted = formatter.format_output(rows, headers, **output_kwargs)
|
||||
first_line = formatted[:formatted.find('\n')]
|
||||
|
||||
if (not expanded and max_width and len(first_line) > max_width and headers):
|
||||
formatted = formatter.format_output(
|
||||
rows, headers, format_name='vertical', **output_kwargs)
|
||||
|
||||
output.append(formatted)
|
||||
|
||||
if status: # Only print the status if it's not None.
|
||||
output.append(status)
|
||||
|
||||
return output
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
cli()
|
||||
|
|
|
@ -1,36 +0,0 @@
|
|||
from .tabulate import _text_type
|
||||
from ..encodingutils import utf8tounicode
|
||||
|
||||
|
||||
def pad(field, total, char=u" "):
|
||||
return field + (char * (total - len(field)))
|
||||
|
||||
|
||||
def expanded_table(rows, headers, missingval=""):
|
||||
header_len = max([len(x) for x in headers])
|
||||
max_row_len = 0
|
||||
results = []
|
||||
sep = u"-[ RECORD {0} ]-------------------------\n"
|
||||
|
||||
padded_headers = [pad(x, header_len) + u" |" for x in headers]
|
||||
header_len += 2
|
||||
|
||||
for row in rows:
|
||||
row_len = max([len(_text_type(utf8tounicode(x))) for x in row])
|
||||
row_result = []
|
||||
if row_len > max_row_len:
|
||||
max_row_len = row_len
|
||||
|
||||
for header, value in zip(padded_headers, row):
|
||||
value = missingval if value is None else value
|
||||
row_result.append((u"%s" % header) + " " + (u"%s" % utf8tounicode(value)).strip())
|
||||
|
||||
results.append('\n'.join(row_result))
|
||||
|
||||
output = []
|
||||
for i, result in enumerate(results):
|
||||
output.append(sep.format(i))
|
||||
output.append(result)
|
||||
output.append('\n')
|
||||
|
||||
return ''.join(output)
|
File diff suppressed because it is too large
Load Diff
|
@ -82,7 +82,8 @@ search_path_filter = False
|
|||
timing = True
|
||||
|
||||
# Table format. Possible values: psql, plain, simple, grid, fancy_grid, pipe,
|
||||
# orgtbl, rst, mediawiki, html, latex, latex_booktabs.
|
||||
# ascii, double, github, orgtbl, rst, mediawiki, html, latex, latex_booktabs,
|
||||
# textile, moinmoin, jira, vertical, tsv, csv.
|
||||
# Recommended: psql, fancy_grid and grid.
|
||||
table_format = psql
|
||||
|
||||
|
|
1
setup.py
1
setup.py
|
@ -21,6 +21,7 @@ install_requirements = [
|
|||
'configobj >= 5.0.6',
|
||||
'humanize >= 0.5.1',
|
||||
'wcwidth >= 0.1.6',
|
||||
'cli_helpers >= 0.2.0, < 1.0.0',
|
||||
]
|
||||
|
||||
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
# coding=UTF-8
|
||||
from pgcli.packages.expanded import expanded_table
|
||||
|
||||
|
||||
def test_expanded_table_renders():
|
||||
input = [("hello", 123),("world", 456)]
|
||||
|
||||
expected = """-[ RECORD 0 ]-------------------------
|
||||
name | hello
|
||||
age | 123
|
||||
-[ RECORD 1 ]-------------------------
|
||||
name | world
|
||||
age | 456
|
||||
"""
|
||||
assert expected == expanded_table(input, ["name", "age"])
|
||||
|
||||
|
||||
def test_unicode_expanded_table():
|
||||
input = [(u'ö', 123)]
|
||||
|
||||
expected = u"""-[ RECORD 0 ]-------------------------
|
||||
name | ö
|
||||
age | 123
|
||||
"""
|
||||
assert expected == expanded_table(input, ["name", "age"])
|
|
@ -57,14 +57,16 @@ def test_format_output():
|
|||
|
||||
|
||||
def test_format_output_auto_expand():
|
||||
settings = OutputSettings(table_format='psql', dcmlfmt='d', floatfmt='g', max_width=100)
|
||||
settings = OutputSettings(
|
||||
table_format='psql', dcmlfmt='d', floatfmt='g', max_width=100)
|
||||
table_results = format_output('Title', [('abc', 'def')],
|
||||
['head1', 'head2'], 'test status', settings)
|
||||
table = ['Title', '+---------+---------+\n| head1 | head2 |\n|---------+---------|\n| abc | def |\n+---------+---------+', 'test status']
|
||||
assert table_results == table
|
||||
expanded_results = format_output('Title', [('abc', 'def')],
|
||||
['head1', 'head2'], 'test status', settings._replace(max_width=1))
|
||||
expanded = ['Title', u'-[ RECORD 0 ]-------------------------\nhead1 | abc\nhead2 | def\n', 'test status']
|
||||
expanded = [
|
||||
'Title', u'-[ RECORD 1 ]-------------------------\nhead1 | abc\nhead2 | def\n', 'test status']
|
||||
assert expanded_results == expanded
|
||||
|
||||
|
||||
|
|
|
@ -250,7 +250,6 @@ def test_large_numbers_render_directly(executor, value):
|
|||
run(executor, "create table numbertest(a numeric)")
|
||||
run(executor,
|
||||
"insert into numbertest (a) values ({0})".format(value))
|
||||
value = format(float(value), ',g')
|
||||
assert value in run(executor, "select * from numbertest", join=True)
|
||||
|
||||
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
from pgcli.packages.tabulate import tabulate
|
||||
from textwrap import dedent
|
||||
|
||||
|
||||
def test_dont_strip_leading_whitespace():
|
||||
data = [[' abc']]
|
||||
headers = ['xyz']
|
||||
tbl, _ = tabulate(data, headers, tablefmt='psql')
|
||||
assert tbl == dedent('''
|
||||
+---------+
|
||||
| xyz |
|
||||
|---------|
|
||||
| abc |
|
||||
+---------+ ''').strip()
|
|
@ -65,7 +65,7 @@ def run(executor, sql, join=False, expanded=False, pgspecial=None,
|
|||
results = executor.run(sql, pgspecial, exception_formatter)
|
||||
formatted = []
|
||||
settings = OutputSettings(table_format='psql', dcmlfmt='d', floatfmt='g',
|
||||
expanded=expanded)
|
||||
expanded=expanded)
|
||||
for title, rows, headers, status, sql, success in results:
|
||||
formatted.extend(format_output(title, rows, headers, status, settings))
|
||||
if join:
|
||||
|
|
Loading…
Reference in New Issue