Skip to content

Commit d054c75

Browse files
committed
Add support for multiple output formats
Three are provided: - table (default, same as what we currently have) - simple (less markup than default but still legible) - csv (comma-separated output) This can be configured via git-config, an environment option, or a command line argument. Signed-off-by: Stephen Finucane <stephen@that.guru>
1 parent b1f6b65 commit d054c75

File tree

9 files changed

+160
-57
lines changed

9 files changed

+160
-57
lines changed

git_pw/bundle.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
import sys
77

88
import click
9-
from tabulate import tabulate
109

1110
from git_pw import api
1211
from git_pw import utils
@@ -82,8 +81,9 @@ def download_cmd(bundle_id, output):
8281

8382

8483
@click.command(name='show')
84+
@utils.format_options
8585
@click.argument('bundle_id')
86-
def show_cmd(bundle_id):
86+
def show_cmd(fmt, bundle_id):
8787
"""Show information about bundle.
8888
8989
Retrieve Patchwork metadata for a bundle.
@@ -108,8 +108,7 @@ def _format_patch(patch):
108108
output.append((prefix, _format_patch(patch)))
109109
prefix = ''
110110

111-
# TODO(stephenfin): We might want to make this machine readable?
112-
click.echo(tabulate(output, ['Property', 'Value'], tablefmt='psql'))
111+
utils.echo(output, ['Property', 'Value'], fmt=fmt)
113112

114113

115114
@click.command(name='list')
@@ -124,9 +123,10 @@ def _format_patch(patch):
124123
@click.option('--sort', metavar='FIELD', default='name', type=click.Choice(
125124
['id', '-id', 'name', '-name']),
126125
help='Sort output on given field.')
126+
@utils.format_options
127127
@click.argument('name', required=False)
128128
@api.validate_multiple_filter_support
129-
def list_cmd(owner, limit, page, sort, name):
129+
def list_cmd(owner, limit, page, sort, fmt, name):
130130
"""List bundles.
131131
132132
List bundles on the Patchwork instance.
@@ -163,4 +163,4 @@ def list_cmd(owner, limit, page, sort, name):
163163
'yes' if bundle.get('public') else 'no',
164164
] for bundle in bundles]
165165

166-
utils.echo_via_pager(tabulate(output, headers, tablefmt='psql'))
166+
utils.echo_via_pager(output, headers, fmt=fmt)

git_pw/patch.py

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77

88
import arrow
99
import click
10-
from tabulate import tabulate
1110

1211
from git_pw import api
1312
from git_pw import utils
@@ -88,7 +87,7 @@ def download_cmd(patch_id, output, fmt):
8887
LOG.info('Downloaded patch to %s', path)
8988

9089

91-
def _show_patch(patch):
90+
def _show_patch(patch, fmt):
9291

9392
def _format_series(series):
9493
return '%-4d %s' % (series.get('id'), series.get('name') or '-')
@@ -113,13 +112,13 @@ def _format_series(series):
113112
output.append((prefix, _format_series(series)))
114113
prefix = ''
115114

116-
# TODO(stephenfin): We might want to make this machine readable?
117-
click.echo(tabulate(output, ['Property', 'Value'], tablefmt='psql'))
115+
utils.echo(output, ['Property', 'Value'], fmt=fmt)
118116

119117

120118
@click.command(name='show')
119+
@utils.format_options
121120
@click.argument('patch_id', type=click.INT)
122-
def show_cmd(patch_id):
121+
def show_cmd(fmt, patch_id):
123122
"""Show information about patch.
124123
125124
Retrieve Patchwork metadata for a patch.
@@ -128,7 +127,7 @@ def show_cmd(patch_id):
128127

129128
patch = api.detail('patches', patch_id)
130129

131-
_show_patch(patch)
130+
_show_patch(patch, fmt)
132131

133132

134133
@click.command(name='update')
@@ -143,7 +142,8 @@ def show_cmd(patch_id):
143142
'either a username or a user\'s email address.')
144143
@click.option('--archived', metavar='ARCHIVED', type=click.BOOL,
145144
help='Set the patch archived state.')
146-
def update_cmd(patch_ids, commit_ref, state, delegate, archived):
145+
@utils.format_options
146+
def update_cmd(patch_ids, commit_ref, state, delegate, archived, fmt):
147147
"""Update one or more patches.
148148
149149
Updates one or more Patches on the Patchwork instance. Some operations may
@@ -174,7 +174,7 @@ def update_cmd(patch_ids, commit_ref, state, delegate, archived):
174174

175175
patch = api.update('patches', patch_id, data)
176176

177-
_show_patch(patch)
177+
_show_patch(patch, fmt)
178178

179179

180180
@click.command(name='list')
@@ -199,9 +199,11 @@ def update_cmd(patch_ids, commit_ref, state, delegate, archived):
199199
@click.option('--sort', metavar='FIELD', default='-date', type=click.Choice(
200200
['id', '-id', 'name', '-name', 'date', '-date']),
201201
help='Sort output on given field.')
202+
@utils.format_options
202203
@click.argument('name', required=False)
203204
@api.validate_multiple_filter_support
204-
def list_cmd(state, submitter, delegate, archived, limit, page, sort, name):
205+
def list_cmd(state, submitter, delegate, archived, limit, page, sort, fmt,
206+
name):
205207
"""List patches.
206208
207209
List patches on the Patchwork instance.
@@ -256,4 +258,4 @@ def list_cmd(state, submitter, delegate, archived, limit, page, sort, name):
256258
if patch.get('delegate') else ''),
257259
] for patch in patches]
258260

259-
utils.echo_via_pager(tabulate(output, headers, tablefmt='psql'))
261+
utils.echo_via_pager(output, headers, fmt=fmt)

git_pw/series.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77

88
import arrow
99
import click
10-
from tabulate import tabulate
1110

1211
from git_pw import api
1312
from git_pw import utils
@@ -62,8 +61,9 @@ def download_cmd(series_id, output):
6261

6362

6463
@click.command(name='show')
64+
@utils.format_options
6565
@click.argument('series_id', type=click.INT)
66-
def show_cmd(series_id):
66+
def show_cmd(fmt, series_id):
6767
"""Show information about series.
6868
6969
Retrieve Patchwork metadata for a series.
@@ -95,8 +95,7 @@ def _format_submission(submission):
9595
output.append((prefix, _format_submission(patch)))
9696
prefix = ''
9797

98-
# TODO(stephenfin): We might want to make this machine readable?
99-
click.echo(tabulate(output, ['Property', 'Value'], tablefmt='psql'))
98+
utils.echo(output, ['Property', 'Value'], fmt=fmt)
10099

101100

102101
@click.command(name='list')
@@ -111,9 +110,10 @@ def _format_submission(submission):
111110
@click.option('--sort', metavar='FIELD', default='-date', type=click.Choice(
112111
['id', '-id', 'name', '-name', 'date', '-date']),
113112
help='Sort output on given field.')
113+
@utils.format_options
114114
@click.argument('name', required=False)
115115
@api.validate_multiple_filter_support
116-
def list_cmd(submitter, limit, page, sort, name):
116+
def list_cmd(submitter, limit, page, sort, fmt, name):
117117
"""List series.
118118
119119
List series on the Patchwork instance.
@@ -152,4 +152,4 @@ def list_cmd(submitter, limit, page, sort, name):
152152
series_.get('submitter').get('email'))
153153
] for series_ in series]
154154

155-
utils.echo_via_pager(tabulate(output, headers, tablefmt='psql'))
155+
utils.echo_via_pager(output, headers, fmt=fmt)

git_pw/utils.py

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@
77
import subprocess
88
import sys
99

10+
import click
11+
from tabulate import tabulate
12+
13+
1014
if sys.version_info < (3, 0):
1115
_text = unicode # noqa
1216
else:
@@ -48,6 +52,25 @@ def git_am(mbox, args):
4852
sys.exit(exc.returncode)
4953

5054

55+
def _tabulate(output, headers, fmt):
56+
fmt = fmt or git_config('pw.format') or 'table'
57+
58+
if fmt == 'table':
59+
return tabulate(output, headers, tablefmt='psql')
60+
elif fmt == 'simple':
61+
return tabulate(output, headers, tablefmt='simple')
62+
elif fmt == 'csv':
63+
result = []
64+
result.append(','.join(headers))
65+
for item in output:
66+
result.append(
67+
','.join(_text(x if x is not None else '') for x in item))
68+
return '\n'.join(result)
69+
70+
print('pw.format must be one of: table, simple, csv')
71+
sys.exit(1)
72+
73+
5174
def _is_ascii_encoding(encoding):
5275
"""Checks if a given encoding is ascii."""
5376
try:
@@ -92,7 +115,7 @@ def _echo_via_pager(pager, output):
92115
break
93116

94117

95-
def echo_via_pager(output):
118+
def echo_via_pager(output, headers, fmt):
96119
"""Echo using git's default pager.
97120
98121
Wrap ``click.echo_via_pager``, setting some environment variables in the
@@ -102,6 +125,8 @@ def echo_via_pager(output):
102125
then ``core.pager`` configuration, then ``$PAGER``, and then the
103126
default chosen at compile time (usually ``less``).
104127
"""
128+
output = _tabulate(output, headers, fmt)
129+
105130
pager = os.environ.get('GIT_PAGER', None)
106131
if pager:
107132
_echo_via_pager(pager, output)
@@ -118,3 +143,17 @@ def echo_via_pager(output):
118143
return
119144

120145
_echo_via_pager('less', output)
146+
147+
148+
def echo(output, headers, fmt):
149+
click.echo(_tabulate(output, headers, fmt))
150+
151+
152+
def format_options(f):
153+
"""Shared output format options."""
154+
f = click.option('--format', '-f', 'fmt', envvar='PW_FORMAT',
155+
default=None,
156+
type=click.Choice(['simple', 'table', 'csv']),
157+
help='Output format. Defaults to table.')(f)
158+
159+
return f
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
features:
3+
- |
4+
Many commands now take a ``--format``/``-f`` parameter, which can be used
5+
to control the output format. Three formats are currently supported:
6+
7+
- ``table`` (default)
8+
- ``simple`` (a version of table with less markup)
9+
- ``csv`` (comma-separated output)

tests/test_bundle.py

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -176,9 +176,9 @@ def test_show(self, mock_get_bundle):
176176
mock_get_bundle.assert_called_once_with('123')
177177

178178

179-
@mock.patch('git_pw.utils.echo_via_pager', new=mock.Mock)
180179
@mock.patch('git_pw.api.version', return_value=(1, 0))
181180
@mock.patch('git_pw.api.index')
181+
@mock.patch('git_pw.utils.echo_via_pager')
182182
class ListTestCase(unittest.TestCase):
183183

184184
@staticmethod
@@ -194,7 +194,7 @@ def _get_users(**kwargs):
194194
rsp.update(**kwargs)
195195
return rsp
196196

197-
def test_list(self, mock_index, mock_version):
197+
def test_list(self, mock_echo, mock_index, mock_version):
198198
"""Validate standard behavior."""
199199

200200
rsp = [self._get_bundle()]
@@ -208,7 +208,22 @@ def test_list(self, mock_index, mock_version):
208208
('q', None), ('page', None), ('per_page', None),
209209
('order', 'name')])
210210

211-
def test_list_with_filters(self, mock_index, mock_version):
211+
def test_list_with_formatting(self, mock_echo, mock_index, mock_version):
212+
"""Validate behavior with formatting applied."""
213+
214+
rsp = [self._get_bundle()]
215+
mock_index.return_value = rsp
216+
217+
runner = CLIRunner()
218+
result = runner.invoke(bundle.list_cmd, [
219+
'--format', 'simple'])
220+
221+
assert result.exit_code == 0, result
222+
223+
mock_echo.assert_called_once_with(mock.ANY, mock.ANY,
224+
fmt='simple')
225+
226+
def test_list_with_filters(self, mock_echo, mock_index, mock_version):
212227
"""Validate behavior with filters applied.
213228
214229
Apply all filters, including those for pagination.
@@ -233,7 +248,7 @@ def test_list_with_filters(self, mock_index, mock_version):
233248
mock_index.assert_has_calls(calls)
234249

235250
@mock.patch('git_pw.api.LOG')
236-
def test_list_with_wildcard_filters(self, mock_log, mock_index,
251+
def test_list_with_wildcard_filters(self, mock_log, mock_echo, mock_index,
237252
mock_version):
238253
"""Validate behavior with a "wildcard" filter.
239254
@@ -251,7 +266,7 @@ def test_list_with_wildcard_filters(self, mock_log, mock_index,
251266
assert mock_log.warning.called
252267

253268
@mock.patch('git_pw.api.LOG')
254-
def test_list_with_multiple_filters(self, mock_log, mock_index,
269+
def test_list_with_multiple_filters(self, mock_log, mock_echo, mock_index,
255270
mock_version):
256271
"""Validate behavior with use of multiple filters.
257272
@@ -271,7 +286,8 @@ def test_list_with_multiple_filters(self, mock_log, mock_index,
271286
assert mock_log.warning.called
272287

273288
@mock.patch('git_pw.api.LOG')
274-
def test_list_api_v1_1(self, mock_log, mock_index, mock_version):
289+
def test_list_api_v1_1(self, mock_log, mock_echo, mock_index,
290+
mock_version):
275291
"""Validate behavior with API v1.1."""
276292

277293
mock_version.return_value = (1, 1)

0 commit comments

Comments
 (0)