mirror of
https://github.com/dbcli/pgcli.git
synced 2024-09-11 13:56:36 +03:00
Respect \pset pager on expected behavior
"\pset pager" has three possible values: "always", "on", and "off". pgcli previously treated all non-"off" values as "always". This change implements the expected behavior, which is to use the pager when the output is larger than the terminal height (See \pset pager in https://www.postgresql.org/docs/9.2/static/app-psql.html). Pgcli adds to this by also using the pager when the output is wider than the terminal width. Fixes #813
This commit is contained in:
parent
7889b2838f
commit
172c9cd271
3
.gitignore
vendored
3
.gitignore
vendored
@ -66,3 +66,6 @@ target/
|
|||||||
# Generated Packages
|
# Generated Packages
|
||||||
*.deb
|
*.deb
|
||||||
*.rpm
|
*.rpm
|
||||||
|
|
||||||
|
.vscode/
|
||||||
|
venv/
|
||||||
|
1
AUTHORS
1
AUTHORS
@ -84,6 +84,7 @@ Contributors:
|
|||||||
* Saif Hakim
|
* Saif Hakim
|
||||||
* Artur Balabanov
|
* Artur Balabanov
|
||||||
* Kenny Do
|
* Kenny Do
|
||||||
|
* Max Rothman
|
||||||
|
|
||||||
|
|
||||||
Creator:
|
Creator:
|
||||||
|
@ -1,3 +1,11 @@
|
|||||||
|
Upcoming
|
||||||
|
========
|
||||||
|
|
||||||
|
Features:
|
||||||
|
---------
|
||||||
|
|
||||||
|
* Respect `\pset pager on` and use pager when output is longer than terminal height (Thanks: `Max Rothman`_)
|
||||||
|
|
||||||
1.10.3
|
1.10.3
|
||||||
======
|
======
|
||||||
|
|
||||||
@ -868,3 +876,4 @@ Improvements:
|
|||||||
.. _`Saif Hakim`: https://github.com/saifelse
|
.. _`Saif Hakim`: https://github.com/saifelse
|
||||||
.. _`Artur Balabanov`: https://github.com/arturbalabanov
|
.. _`Artur Balabanov`: https://github.com/arturbalabanov
|
||||||
.. _`Kenny Do`: https://github.com/kennydo
|
.. _`Kenny Do`: https://github.com/kennydo
|
||||||
|
.. _`Max Rothman`: https://github.com/maxrothman
|
||||||
|
@ -42,7 +42,7 @@ from prompt_toolkit.auto_suggest import AutoSuggestFromHistory
|
|||||||
from pygments.lexers.sql import PostgresLexer
|
from pygments.lexers.sql import PostgresLexer
|
||||||
from pygments.token import Token
|
from pygments.token import Token
|
||||||
|
|
||||||
from pgspecial.main import (PGSpecial, NO_QUERY, PAGER_OFF)
|
from pgspecial.main import (PGSpecial, NO_QUERY, PAGER_OFF, PAGER_LONG_OUTPUT)
|
||||||
import pgspecial as special
|
import pgspecial as special
|
||||||
try:
|
try:
|
||||||
import keyring
|
import keyring
|
||||||
@ -77,6 +77,9 @@ from collections import namedtuple
|
|||||||
|
|
||||||
from textwrap import dedent
|
from textwrap import dedent
|
||||||
|
|
||||||
|
# Ref: https://stackoverflow.com/questions/30425105/filter-special-chars-such-as-color-codes-from-shell-output
|
||||||
|
COLOR_CODE_REGEX = re.compile(r'\x1b(\[.*?[@-~]|\].*?(\x07|\x1b\\))')
|
||||||
|
|
||||||
# Query tuples are used for maintaining history
|
# Query tuples are used for maintaining history
|
||||||
MetaQuery = namedtuple(
|
MetaQuery = namedtuple(
|
||||||
'Query',
|
'Query',
|
||||||
@ -929,6 +932,15 @@ class PGCli(object):
|
|||||||
def echo_via_pager(self, text, color=None):
|
def echo_via_pager(self, text, color=None):
|
||||||
if self.pgspecial.pager_config == PAGER_OFF or self.watch_command:
|
if self.pgspecial.pager_config == PAGER_OFF or self.watch_command:
|
||||||
click.echo(text, color=color)
|
click.echo(text, color=color)
|
||||||
|
elif self.pgspecial.pager_config == PAGER_LONG_OUTPUT:
|
||||||
|
lines = text.split('\n')
|
||||||
|
|
||||||
|
# The last 4 lines are reserved for the pgcli menu and padding
|
||||||
|
if len(lines) >= self.cli.output.get_size().rows - 4 \
|
||||||
|
or any(len(COLOR_CODE_REGEX.sub('', l)) > self.cli.output.get_size().columns for l in lines):
|
||||||
|
click.echo_via_pager(text, color=color)
|
||||||
|
else:
|
||||||
|
click.echo(text, color=color)
|
||||||
else:
|
else:
|
||||||
click.echo_via_pager(text, color)
|
click.echo_via_pager(text, color)
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
pytest>=2.7.0
|
# The maximum version requirement can be removed once Python 3.4 goes EOL
|
||||||
|
pytest>=2.7.0,<=3.0.7
|
||||||
mock>=1.0.1
|
mock>=1.0.1
|
||||||
tox>=1.9.2
|
tox>=1.9.2
|
||||||
behave>=1.2.4
|
behave>=1.2.4
|
||||||
|
@ -3,6 +3,7 @@ from __future__ import unicode_literals
|
|||||||
|
|
||||||
import re
|
import re
|
||||||
import pexpect
|
import pexpect
|
||||||
|
from pgcli.main import COLOR_CODE_REGEX
|
||||||
|
|
||||||
|
|
||||||
def expect_exact(context, expected, timeout):
|
def expect_exact(context, expected, timeout):
|
||||||
@ -10,7 +11,7 @@ def expect_exact(context, expected, timeout):
|
|||||||
context.cli.expect_exact(expected, timeout=timeout)
|
context.cli.expect_exact(expected, timeout=timeout)
|
||||||
except:
|
except:
|
||||||
# Strip color codes out of the output.
|
# Strip color codes out of the output.
|
||||||
actual = re.sub(r'\x1b\[([0-9A-Za-z;?])+[m|K]?', '', context.cli.before)
|
actual = COLOR_CODE_REGEX.sub('', context.cli.before)
|
||||||
raise Exception('Expected:\n---\n{0!r}\n---\n\nActual:\n---\n{1!r}\n---'.format(
|
raise Exception('Expected:\n---\n{0!r}\n---\n\nActual:\n---\n{1!r}\n---'.format(
|
||||||
expected,
|
expected,
|
||||||
actual))
|
actual))
|
||||||
|
@ -11,7 +11,7 @@ except ImportError:
|
|||||||
setproctitle = None
|
setproctitle = None
|
||||||
|
|
||||||
from pgcli.main import (
|
from pgcli.main import (
|
||||||
obfuscate_process_password, format_output, PGCli, OutputSettings
|
obfuscate_process_password, format_output, PGCli, OutputSettings, COLOR_CODE_REGEX
|
||||||
)
|
)
|
||||||
from pgspecial.main import (PAGER_OFF, PAGER_LONG_OUTPUT, PAGER_ALWAYS)
|
from pgspecial.main import (PAGER_OFF, PAGER_LONG_OUTPUT, PAGER_ALWAYS)
|
||||||
from tests.utils import dbtest, run
|
from tests.utils import dbtest, run
|
||||||
@ -148,6 +148,100 @@ def test_format_output_auto_expand():
|
|||||||
assert '\n'.join(expanded_results) == '\n'.join(expanded)
|
assert '\n'.join(expanded_results) == '\n'.join(expanded)
|
||||||
|
|
||||||
|
|
||||||
|
termsize = namedtuple('termsize', ['rows', 'columns'])
|
||||||
|
test_line = '-' * 10
|
||||||
|
test_data = [
|
||||||
|
(10, 10, '\n'.join([test_line] * 7)),
|
||||||
|
(10, 10, '\n'.join([test_line] * 6)),
|
||||||
|
(10, 10, '\n'.join([test_line] * 5)),
|
||||||
|
(10, 10, '-' * 11),
|
||||||
|
(10, 10, '-' * 10),
|
||||||
|
(10, 10, '-' * 9),
|
||||||
|
]
|
||||||
|
|
||||||
|
# 4 lines are reserved at the bottom of the terminal for pgcli's prompt
|
||||||
|
use_pager_when_on = [True,
|
||||||
|
True,
|
||||||
|
False,
|
||||||
|
True,
|
||||||
|
False,
|
||||||
|
False]
|
||||||
|
|
||||||
|
# Can be replaced with pytest.param once we can upgrade pytest after Python 3.4 goes EOL
|
||||||
|
test_ids = ["Output longer than terminal height",
|
||||||
|
"Output equal to terminal height",
|
||||||
|
"Output shorter than terminal height",
|
||||||
|
"Output longer than terminal width",
|
||||||
|
"Output equal to terminal width",
|
||||||
|
"Output shorter than terminal width"]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def pset_pager_mocks():
|
||||||
|
cli = PGCli()
|
||||||
|
cli.watch_command = None
|
||||||
|
with mock.patch('pgcli.main.click.echo') as mock_echo, \
|
||||||
|
mock.patch('pgcli.main.click.echo_via_pager') as mock_echo_via_pager, \
|
||||||
|
mock.patch.object(cli, 'cli') as mock_cli:
|
||||||
|
|
||||||
|
yield cli, mock_echo, mock_echo_via_pager, mock_cli
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('term_height,term_width,text', test_data, ids=test_ids)
|
||||||
|
def test_pset_pager_off(term_height, term_width, text, pset_pager_mocks):
|
||||||
|
cli, mock_echo, mock_echo_via_pager, mock_cli = pset_pager_mocks
|
||||||
|
mock_cli.output.get_size.return_value = termsize(
|
||||||
|
rows=term_height, columns=term_width)
|
||||||
|
|
||||||
|
with mock.patch.object(cli.pgspecial, 'pager_config', PAGER_OFF):
|
||||||
|
cli.echo_via_pager(text)
|
||||||
|
|
||||||
|
mock_echo.assert_called()
|
||||||
|
mock_echo_via_pager.assert_not_called()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('term_height,term_width,text', test_data, ids=test_ids)
|
||||||
|
def test_pset_pager_always(term_height, term_width, text, pset_pager_mocks):
|
||||||
|
cli, mock_echo, mock_echo_via_pager, mock_cli = pset_pager_mocks
|
||||||
|
mock_cli.output.get_size.return_value = termsize(
|
||||||
|
rows=term_height, columns=term_width)
|
||||||
|
|
||||||
|
with mock.patch.object(cli.pgspecial, 'pager_config', PAGER_ALWAYS):
|
||||||
|
cli.echo_via_pager(text)
|
||||||
|
|
||||||
|
mock_echo.assert_not_called()
|
||||||
|
mock_echo_via_pager.assert_called()
|
||||||
|
|
||||||
|
|
||||||
|
pager_on_test_data = [l + (r,) for l, r in zip(test_data, use_pager_when_on)]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('term_height,term_width,text,use_pager', pager_on_test_data, ids=test_ids)
|
||||||
|
def test_pset_pager_on(term_height, term_width, text, use_pager, pset_pager_mocks):
|
||||||
|
cli, mock_echo, mock_echo_via_pager, mock_cli = pset_pager_mocks
|
||||||
|
mock_cli.output.get_size.return_value = termsize(
|
||||||
|
rows=term_height, columns=term_width)
|
||||||
|
|
||||||
|
with mock.patch.object(cli.pgspecial, 'pager_config', PAGER_LONG_OUTPUT):
|
||||||
|
cli.echo_via_pager(text)
|
||||||
|
|
||||||
|
if use_pager:
|
||||||
|
mock_echo.assert_not_called()
|
||||||
|
mock_echo_via_pager.assert_called()
|
||||||
|
else:
|
||||||
|
mock_echo_via_pager.assert_not_called()
|
||||||
|
mock_echo.assert_called()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('text,expected_length', [
|
||||||
|
(u"22200K .......\u001b[0m\u001b[91m... .......... ...\u001b[0m\u001b[91m.\u001b[0m\u001b[91m...... .........\u001b[0m\u001b[91m.\u001b[0m\u001b[91m \u001b[0m\u001b[91m.\u001b[0m\u001b[91m.\u001b[0m\u001b[91m.\u001b[0m\u001b[91m.\u001b[0m\u001b[91m...... 50% 28.6K 12m55s", 78),
|
||||||
|
(u"=\u001b[m=", 2),
|
||||||
|
(u"-\u001b]23\u0007-", 2),
|
||||||
|
])
|
||||||
|
def test_color_pattern(text, expected_length, pset_pager_mocks):
|
||||||
|
cli = pset_pager_mocks[0]
|
||||||
|
assert len(COLOR_CODE_REGEX.sub('', text)) == expected_length
|
||||||
|
|
||||||
@dbtest
|
@dbtest
|
||||||
def test_i_works(tmpdir, executor):
|
def test_i_works(tmpdir, executor):
|
||||||
sqlfile = tmpdir.join("test.sql")
|
sqlfile = tmpdir.join("test.sql")
|
||||||
|
Loading…
Reference in New Issue
Block a user