Add '--sort-ascending', '--sort-descending' parameters
Allow users to reverse sorting direction. Change-Id: Iecd539139c5a7ce4abaaee2ff5632a2459437d51 Signed-off-by: Stephen Finucane <sfinucan@redhat.com>
This commit is contained in:
parent
c1c991045c
commit
7798cb2e37
|
@ -10,8 +10,8 @@
|
||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
"""Application base class for providing a list of data as output.
|
"""Application base class for providing a list of data as output."""
|
||||||
"""
|
|
||||||
import abc
|
import abc
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
@ -19,8 +19,7 @@ from . import display
|
||||||
|
|
||||||
|
|
||||||
class Lister(display.DisplayCommandBase, metaclass=abc.ABCMeta):
|
class Lister(display.DisplayCommandBase, metaclass=abc.ABCMeta):
|
||||||
"""Command base class for providing a list of data as output.
|
"""Command base class for providing a list of data as output."""
|
||||||
"""
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -37,13 +36,16 @@ class Lister(display.DisplayCommandBase, metaclass=abc.ABCMeta):
|
||||||
"""Whether sort procedure is performed by cliff itself.
|
"""Whether sort procedure is performed by cliff itself.
|
||||||
|
|
||||||
Should be overridden (return False) when there is a need to implement
|
Should be overridden (return False) when there is a need to implement
|
||||||
custom sorting procedure or data is already sorted."""
|
custom sorting procedure or data is already sorted.
|
||||||
|
"""
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def take_action(self, parsed_args):
|
def take_action(self, parsed_args):
|
||||||
"""Return a tuple containing the column names and an iterable
|
"""Run command.
|
||||||
containing the data to be listed.
|
|
||||||
|
Return a tuple containing the column names and an iterable containing
|
||||||
|
the data to be listed.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def get_parser(self, prog_name):
|
def get_parser(self, prog_name):
|
||||||
|
@ -55,16 +57,36 @@ class Lister(display.DisplayCommandBase, metaclass=abc.ABCMeta):
|
||||||
default=[],
|
default=[],
|
||||||
dest='sort_columns',
|
dest='sort_columns',
|
||||||
metavar='SORT_COLUMN',
|
metavar='SORT_COLUMN',
|
||||||
help=("specify the column(s) to sort the data (columns specified "
|
help=(
|
||||||
"first have a priority, non-existing columns are ignored), "
|
'specify the column(s) to sort the data (columns specified '
|
||||||
"can be repeated")
|
'first have a priority, non-existing columns are ignored), '
|
||||||
|
'can be repeated'
|
||||||
|
),
|
||||||
|
)
|
||||||
|
sort_dir_group = group.add_mutually_exclusive_group()
|
||||||
|
sort_dir_group.add_argument(
|
||||||
|
'--sort-ascending',
|
||||||
|
action='store_const',
|
||||||
|
dest='sort_direction',
|
||||||
|
const='asc',
|
||||||
|
help=('sort the column(s) in ascending order'),
|
||||||
|
)
|
||||||
|
sort_dir_group.add_argument(
|
||||||
|
'--sort-descending',
|
||||||
|
action='store_const',
|
||||||
|
dest='sort_direction',
|
||||||
|
const='desc',
|
||||||
|
help=('sort the column(s) in descending order'),
|
||||||
)
|
)
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
def produce_output(self, parsed_args, column_names, data):
|
def produce_output(self, parsed_args, column_names, data):
|
||||||
if parsed_args.sort_columns and self.need_sort_by_cliff:
|
if parsed_args.sort_columns and self.need_sort_by_cliff:
|
||||||
indexes = [column_names.index(c) for c in parsed_args.sort_columns
|
indexes = [
|
||||||
if c in column_names]
|
column_names.index(c) for c in parsed_args.sort_columns
|
||||||
|
if c in column_names
|
||||||
|
]
|
||||||
|
reverse = parsed_args.sort_direction == 'desc'
|
||||||
for index in indexes[::-1]:
|
for index in indexes[::-1]:
|
||||||
try:
|
try:
|
||||||
# We need to handle unset values (i.e. None) so we sort on
|
# We need to handle unset values (i.e. None) so we sort on
|
||||||
|
@ -76,6 +98,7 @@ class Lister(display.DisplayCommandBase, metaclass=abc.ABCMeta):
|
||||||
# the same, i.e. both None or both not-None
|
# the same, i.e. both None or both not-None
|
||||||
data = sorted(
|
data = sorted(
|
||||||
data, key=lambda k: (k[index] is None, k[index]),
|
data, key=lambda k: (k[index] is None, k[index]),
|
||||||
|
reverse=reverse,
|
||||||
)
|
)
|
||||||
except TypeError:
|
except TypeError:
|
||||||
# Simply log and then ignore this; sorting is best effort
|
# Simply log and then ignore this; sorting is best effort
|
||||||
|
@ -84,18 +107,20 @@ class Lister(display.DisplayCommandBase, metaclass=abc.ABCMeta):
|
||||||
parsed_args.sort_columns[index],
|
parsed_args.sort_columns[index],
|
||||||
)
|
)
|
||||||
|
|
||||||
(columns_to_include, selector) = self._generate_columns_and_selector(
|
columns_to_include, selector = self._generate_columns_and_selector(
|
||||||
parsed_args, column_names)
|
parsed_args, column_names,
|
||||||
|
)
|
||||||
if selector:
|
if selector:
|
||||||
# Generator expression to only return the parts of a row
|
# Generator expression to only return the parts of a row
|
||||||
# of data that the user has expressed interest in
|
# of data that the user has expressed interest in
|
||||||
# seeing. We have to convert the compress() output to a
|
# seeing. We have to convert the compress() output to a
|
||||||
# list so the table formatter can ask for its length.
|
# list so the table formatter can ask for its length.
|
||||||
data = (list(self._compress_iterable(row, selector))
|
data = (
|
||||||
for row in data)
|
list(self._compress_iterable(row, selector)) for row in data
|
||||||
self.formatter.emit_list(columns_to_include,
|
)
|
||||||
data,
|
|
||||||
self.app.stdout,
|
self.formatter.emit_list(
|
||||||
parsed_args,
|
columns_to_include, data, self.app.stdout, parsed_args,
|
||||||
)
|
)
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
|
|
|
@ -106,6 +106,21 @@ class TestLister(base.TestBase):
|
||||||
data = list(args[1])
|
data = list(args[1])
|
||||||
self.assertEqual([['a', 'A'], ['c', 'A'], ['b', 'B']], data)
|
self.assertEqual([['a', 'A'], ['c', 'A'], ['b', 'B']], data)
|
||||||
|
|
||||||
|
def test_sort_by_column_reverse_order(self):
|
||||||
|
test_lister = ExerciseLister(mock.Mock(), [])
|
||||||
|
parsed_args = mock.Mock()
|
||||||
|
parsed_args.columns = ('Col1', 'Col2')
|
||||||
|
parsed_args.formatter = 'test'
|
||||||
|
parsed_args.sort_columns = ['Col2', 'Col1']
|
||||||
|
parsed_args.sort_direction = 'desc'
|
||||||
|
|
||||||
|
test_lister.run(parsed_args)
|
||||||
|
|
||||||
|
f = test_lister._formatter_plugins['test']
|
||||||
|
args = f.args[0]
|
||||||
|
data = list(args[1])
|
||||||
|
self.assertEqual([['b', 'B'], ['c', 'A'], ['a', 'A']], data)
|
||||||
|
|
||||||
def test_sort_by_column_data_already_sorted(self):
|
def test_sort_by_column_data_already_sorted(self):
|
||||||
test_lister = ExerciseListerCustomSort(mock.Mock(), [])
|
test_lister = ExerciseListerCustomSort(mock.Mock(), [])
|
||||||
parsed_args = mock.Mock()
|
parsed_args = mock.Mock()
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
---
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
The ``cliff.lister.Lister`` base class now implements ``--sort-ascending``
|
||||||
|
and ``--sort-descending`` options, which can be used to configure the sort
|
||||||
|
direction. For example::
|
||||||
|
|
||||||
|
$ hello-world list-users --sort-column email --sort-descending
|
||||||
|
+----------------+-----------------------------+
|
||||||
|
| Name | Email |
|
||||||
|
+----------------+-----------------------------+
|
||||||
|
| Charles Xavier | therealcharliex@example.com |
|
||||||
|
| Jim Hendrix | jim@example.com |
|
||||||
|
| John Doe | doe.john@example.com |
|
||||||
|
| Alice Baker | abaker@example.com |
|
||||||
|
+----------------+-----------------------------+
|
||||||
|
upgrade:
|
||||||
|
- |
|
||||||
|
``cliff.lister.Lister`` implementations that override the
|
||||||
|
``need_sort_by_cliff`` property should now consider the
|
||||||
|
``--sort-ascending`` and ``--sort-descending`` options.
|
Loading…
Reference in New Issue