diff --git a/changelog.rst b/changelog.rst index ee5d1fcf5..a008bcf0f 100644 --- a/changelog.rst +++ b/changelog.rst @@ -8,6 +8,11 @@ Features: * You can specify multiple times. * Runs one statement at a time, like `-f`, and can be combined with `-f`: both run, the same way psql does. +* Add ``--no-timings`` and ``--no-status`` to suppress the timing line and the + status footer independently. ``-t``/``--tuples-only`` already turns off both, + along with the headers and the title, but there was no way to keep the table + formatting and drop only one of the two, which scripts that post-process the + output often want. * Add support for forcing destructive commands without confirmation. * Command line option `-y` or `--yes`. * Skips the destructive command confirmation prompt when enabled. diff --git a/pgcli/main.py b/pgcli/main.py index 3ff820ce2..23f3a21dc 100644 --- a/pgcli/main.py +++ b/pgcli/main.py @@ -120,7 +120,7 @@ OutputSettings = namedtuple( "OutputSettings", "table_format dcmlfmt floatfmt column_date_formats missingval expanded max_width case_function style_output " - "max_field_width tuples_only", + "max_field_width tuples_only show_status", ) OutputSettings.__new__.__defaults__ = ( None, @@ -134,6 +134,7 @@ None, DEFAULT_MAX_FIELD_WIDTH, False, + True, ) @@ -224,6 +225,8 @@ def __init__( single_connection=False, less_chatty=None, tuples_only=None, + no_timings=False, + no_status=False, prompt=None, prompt_dsn=None, auto_vertical_output=False, @@ -290,8 +293,14 @@ def __init__( # alone here and switched to an unadorned one at output time, so \T # still reports (and can change) the configured format. self.tuples_only = bool(tuples_only) - if self.tuples_only: + # --no-timings and --no-status turn off one thing each, for scripts that + # want the rows plus one of the two. -t is the psql-compatible shorthand + # that turns off both, along with the headers and the title. + self.show_status = not no_status + if no_timings or self.tuples_only: self.pgspecial.timing_enabled = False + if self.tuples_only: + self.show_status = False self.syntax_style = c["main"]["syntax_style"] self.cli_style = c["colors"] self.wider_completion_menu = c["main"].as_bool("wider_completion_menu") @@ -1370,6 +1379,7 @@ def _evaluate_command(self, text): style_output=self.style_output, max_field_width=self.max_field_width, tuples_only=self.tuples_only, + show_status=self.show_status, ) # Hide query text for named queries in quiet mode @@ -1650,6 +1660,20 @@ def echo_via_pager(self, text, color=None): default=False, help="Print rows only: no column headers, no status footer and no timing, like psql.", ) +@click.option( + "--no-timings", + "no_timings", + is_flag=True, + default=False, + help="Do not print the timing line after each query.", +) +@click.option( + "--no-status", + "no_status", + is_flag=True, + default=False, + help="Do not print the status footer (SELECT 3, UPDATE 1, ...) after each query.", +) @click.option("--prompt", help='Prompt format (Default: "\\u@\\h:\\d> ").') @click.option( "--prompt-dsn", @@ -1737,6 +1761,8 @@ def cli( application_name, less_chatty, tuples_only, + no_timings: bool, + no_status: bool, prompt, prompt_dsn, list_databases, @@ -1809,6 +1835,8 @@ def cli( single_connection=single_connection, less_chatty=less_chatty, tuples_only=tuples_only, + no_timings=no_timings, + no_status=no_status, prompt=prompt, prompt_dsn=prompt_dsn, auto_vertical_output=auto_vertical_output, @@ -2229,7 +2257,7 @@ def format_status(cur, status): output = itertools.chain(output, formatted) # Likewise the status footer. - if status and not settings.tuples_only: + if status and settings.show_status and not settings.tuples_only: output = itertools.chain(output, [format_status(cur, status)]) return output diff --git a/tests/test_no_timings_no_status.py b/tests/test_no_timings_no_status.py new file mode 100644 index 000000000..dc87aecda --- /dev/null +++ b/tests/test_no_timings_no_status.py @@ -0,0 +1,67 @@ +from unittest.mock import patch + +from click.testing import CliRunner + +from pgcli.main import cli, format_output, OutputSettings, PGCli + + +def test_no_timings_flag_passed_to_pgcli(): + runner = CliRunner() + with patch.object(PGCli, "__init__", autospec=True, return_value=None) as mock_pgcli: + runner.invoke(cli, ["--no-timings", "mydb"]) + assert mock_pgcli.call_args[1]["no_timings"] is True + + +def test_no_status_flag_passed_to_pgcli(): + runner = CliRunner() + with patch.object(PGCli, "__init__", autospec=True, return_value=None) as mock_pgcli: + runner.invoke(cli, ["--no-status", "mydb"]) + assert mock_pgcli.call_args[1]["no_status"] is True + + +def test_both_default_to_false(): + runner = CliRunner() + with patch.object(PGCli, "__init__", autospec=True, return_value=None) as mock_pgcli: + runner.invoke(cli, ["mydb"]) + assert mock_pgcli.call_args[1]["no_timings"] is False + assert mock_pgcli.call_args[1]["no_status"] is False + + +def test_no_timings_turns_off_timing_only(): + """The point of having two flags: each one leaves the other alone.""" + cli_obj = PGCli(no_timings=True) + + assert cli_obj.pgspecial.timing_enabled is False + assert cli_obj.show_status is True + + +def test_no_status_turns_off_status_only(): + cli_obj = PGCli(no_status=True) + + assert cli_obj.show_status is False + assert cli_obj.pgspecial.timing_enabled is True + + +def test_tuples_only_still_turns_off_both(): + """-t stays the psql-compatible shorthand for both.""" + cli_obj = PGCli(tuples_only=True) + + assert cli_obj.show_status is False + assert cli_obj.pgspecial.timing_enabled is False + + +def test_no_status_suppresses_only_the_footer(): + settings = OutputSettings(table_format="psql", show_status=False) + output = "\n".join(format_output("Title", [(1,)], ["a"], "SELECT 1", settings)) + + assert "SELECT 1" not in output + # Rows, headers and title are untouched. + assert "Title" in output + assert "a" in output and "1" in output + + +def test_status_footer_is_printed_by_default(): + settings = OutputSettings(table_format="psql") + output = "\n".join(format_output("Title", [(1,)], ["a"], "SELECT 1", settings)) + + assert "SELECT 1" in output