--tsv output option, closes #193

This commit is contained in:
Simon Willison 2020-11-06 16:09:42 -08:00
commit afee15f04b
3 changed files with 65 additions and 28 deletions

View file

@ -122,14 +122,14 @@ You can use the ``--json-cols`` option to automatically detect these JSON column
}
]
}
]
]-
.. _cli_query_csv:
Running queries and returning CSV
=================================
You can use the ``--csv`` option (or ``-c`` shortcut) to return results as CSV::
You can use the ``--csv`` option to return results as CSV::
$ sqlite-utils dogs.db "select * from dogs" --csv
id,age,name
@ -142,6 +142,13 @@ This will default to including the column names as a header row. To exclude the
1,4,Cleo
2,2,Pancakes
Use ``--tsv`` instead of ``--csv`` to get back tab-separated values::
$ sqlite-utils dogs.db "select * from dogs" --tsv
id age name
1 4 Cleo
2 2 Pancakes
.. _cli_query_table:
Running queries and outputting a table
@ -189,7 +196,7 @@ You can return every row in a specified table using the ``rows`` command::
[{"id": 1, "age": 4, "name": "Cleo"},
{"id": 2, "age": 2, "name": "Pancakes"}]
This command accepts the same output options as ``query`` - so you can pass ``--nl``, ``--csv``, ``--no-headers``, ``--table`` and ``--fmt``.
This command accepts the same output options as ``query`` - so you can pass ``--nl``, ``--csv``, ``--tsv``, ``--no-headers``, ``--table`` and ``--fmt``.
.. _cli_tables:
@ -203,7 +210,7 @@ You can list the names of tables in a database using the ``tables`` command::
{"table": "cats"},
{"table": "chickens"}]
You can output this list in CSV using the ``--csv`` option::
You can output this list in CSV using the ``--csv`` or ``--tsv`` options::
$ sqlite-utils tables mydb.db --csv --no-headers
dogs
@ -241,7 +248,7 @@ Use ``--schema`` to include the schema of each table::
[age] INTEGER,
[name] TEXT)
The ``--nl``, ``--csv`` and ``--table`` options are all available.
The ``--nl``, ``--csv``, ``--tsv`` and ``--table`` options are all available.
.. _cli_views:
@ -263,6 +270,7 @@ It takes the same options as the ``tables`` command:
* ``--counts``
* ``--nl``
* ``--csv``
* ``--tsv``
* ``--table``
.. _cli_inserting_data:
@ -894,7 +902,7 @@ Once you have configured full-text search for a table, you can search it using `
$ sqlite-utils search mydb.db documents searchterm
This command accepts the same output options as ``sqlite-utils query``: ``--table``, ``--csv``, ``--nl`` etc.
This command accepts the same output options as ``sqlite-utils query``: ``--table``, ``--csv``, ``--tsv``, ``--nl`` etc.
By default it shows the most relevant matches first. You can specify a different sort order using the ``-o`` option, which can take a column or a column followed by ``desc``::

View file

@ -46,6 +46,7 @@ def output_options(fn):
default=False,
),
click.option("--csv", is_flag=True, help="Output CSV"),
click.option("--tsv", is_flag=True, help="Output TSV"),
click.option("--no-headers", is_flag=True, help="Omit CSV headers"),
click.option("-t", "--table", is_flag=True, help="Output as a table"),
click.option(
@ -120,6 +121,7 @@ def tables(
nl,
arrays,
csv,
tsv,
no_headers,
table,
fmt,
@ -161,8 +163,8 @@ def tables(
if table:
print(tabulate.tabulate(_iter(), headers=headers, tablefmt=fmt))
elif csv:
writer = csv_std.writer(sys.stdout)
elif csv or tsv:
writer = csv_std.writer(sys.stdout, dialect="excel-tab" if tsv else "excel")
if not no_headers:
writer.writerow(headers)
for row in _iter():
@ -201,6 +203,7 @@ def views(
nl,
arrays,
csv,
tsv,
no_headers,
table,
fmt,
@ -218,6 +221,7 @@ def views(
nl=nl,
arrays=arrays,
csv=csv,
tsv=tsv,
no_headers=no_headers,
table=table,
fmt=fmt,
@ -930,6 +934,7 @@ def query(
nl,
arrays,
csv,
tsv,
no_headers,
table,
fmt,
@ -958,8 +963,8 @@ def query(
sys.stdout.write(str(data))
elif table:
print(tabulate.tabulate(list(cursor), headers=headers, tablefmt=fmt))
elif csv:
writer = csv_std.writer(sys.stdout)
elif csv or tsv:
writer = csv_std.writer(sys.stdout, dialect="excel-tab" if tsv else "excel")
if not no_headers:
writer.writerow(headers)
for row in cursor:
@ -1003,6 +1008,7 @@ def search(
nl,
arrays,
csv,
tsv,
no_headers,
table,
fmt,
@ -1039,6 +1045,7 @@ def search(
nl=nl,
arrays=arrays,
csv=csv,
tsv=tsv,
no_headers=no_headers,
table=table,
fmt=fmt,
@ -1065,6 +1072,7 @@ def rows(
nl,
arrays,
csv,
tsv,
no_headers,
table,
fmt,
@ -1079,6 +1087,7 @@ def rows(
nl=nl,
arrays=arrays,
csv=csv,
tsv=tsv,
no_headers=no_headers,
table=table,
fmt=fmt,

View file

@ -62,24 +62,37 @@ def test_tables_counts_and_columns(db_path):
) == result.output.strip()
def test_tables_counts_and_columns_csv(db_path):
@pytest.mark.parametrize(
"format,expected",
[
(
"--csv",
(
"table,count,columns\n"
'Gosh,0,"c1\n'
"c2\n"
'c3"\n'
'Gosh2,0,"c1\n'
"c2\n"
'c3"\n'
'lots,30,"id\n'
'age"'
),
),
(
"--tsv",
"table\tcount\tcolumns\nGosh\t0\t['c1', 'c2', 'c3']\nGosh2\t0\t['c1', 'c2', 'c3']\nlots\t30\t['id', 'age']",
),
],
)
def test_tables_counts_and_columns_csv(db_path, format, expected):
db = Database(db_path)
with db.conn:
db["lots"].insert_all([{"id": i, "age": i + 1} for i in range(30)])
result = CliRunner().invoke(
cli.cli, ["tables", "--counts", "--columns", "--csv", db_path]
cli.cli, ["tables", "--counts", "--columns", format, db_path]
)
assert (
"table,count,columns\n"
'Gosh,0,"c1\n'
"c2\n"
'c3"\n'
'Gosh2,0,"c1\n'
"c2\n"
'c3"\n'
'lots,30,"id\n'
'age"'
) == result.output.strip()
assert result.output.strip() == expected
def test_tables_schema(db_path):
@ -809,7 +822,14 @@ def test_insert_alter(db_path, tmpdir):
] == db.execute_returning_dicts("select foo, n, baz from from_json_nl")
def test_query_csv(db_path):
@pytest.mark.parametrize(
"format,expected",
[
("--csv", "id,name,age\n1,Cleo,4\n2,Pancakes,2\n"),
("--tsv", "id\tname\tage\n1\tCleo\t4\n2\tPancakes\t2\n"),
],
)
def test_query_csv(db_path, format, expected):
db = Database(db_path)
with db.conn:
db["dogs"].insert_all(
@ -819,15 +839,15 @@ def test_query_csv(db_path):
]
)
result = CliRunner().invoke(
cli.cli, [db_path, "select id, name, age from dogs", "--csv"]
cli.cli, [db_path, "select id, name, age from dogs", format]
)
assert 0 == result.exit_code
assert "id,name,age\n1,Cleo,4\n2,Pancakes,2\n" == result.output
assert result.output == expected
# Test the no-headers option:
result = CliRunner().invoke(
cli.cli, [db_path, "select id, name, age from dogs", "--no-headers", "--csv"]
cli.cli, [db_path, "select id, name, age from dogs", "--no-headers", format]
)
assert "1,Cleo,4\n2,Pancakes,2\n" == result.output
assert result.output.strip() == "\n".join(expected.split("\n")[1:]).strip()
_all_query = "select id, name, age from dogs"