Commit 860c8f91 authored by Michał Góral's avatar Michał Góral

Implemented sorting and limiting results outside of filter

parent 3da6d2cd
Pipeline #56590775 passed with stage
in 1 minute and 22 seconds
......@@ -31,7 +31,8 @@ class Block:
fmt = attr.ib('{description}')
filter = attr.ib(None)
sort = attr.ib(None)
height = attr.ib('100%')
limit = attr.ib(None)
height = attr.ib('100%') # int or string
@attr.s
......@@ -103,16 +104,17 @@ def _create_default_blocks(cfg):
name='Next tasks',
filter='status:pending',
fmt='<m class=comment>{id: >4}</m> <m class=warning>{priority:<2}</m>{description} <m class=info>{tags:>}</m>', # pylint: disable=line-too-long
sort='priority-,id+',
sort='priority,urgency-',
height='70%')
cfg.add_block(
'default',
name='Recently completed',
filter='status:completed limit:10',
filter='status:completed',
fmt='{description}',
sort='id+',
height='12')
sort='end-',
limit=10,
height=12)
def config_path():
......
......@@ -18,21 +18,71 @@
'''TaskWarrior utilities'''
import shlex
import functools
class TaskComparer:
'''Class which can be used as a key in all Python sort methods for sorting
TaskWarrior lists of task dictionaries. It compares tasks by applying a
complex TaskWarrior-compatible sort conditions.'''
def __init__(self, dct, cmp):
self.dct = dct
self.cmp = cmp
def __lt__(self, other):
for attr, rev in self.cmp:
sp = self.dct.get(attr)
op = other.dct.get(attr)
if sp is None and op is None:
continue
if sp is None:
return False ^ rev
if op is None:
return True ^ rev
if sp == op:
continue
return (sp < op) ^ rev
# we're here because all checked attributes are equal
return False
def _process_filter(f):
if not f:
return []
# TODO: search for limit:<l>. It should be extracted and applied after sort.
# Also remember about different forms of limit:
# - (limit:blah)
# ... and limit:blah
# etc...
# (mgoral, 2019-04-12)
return shlex.split(f)
def _process_sort_string(sort_string):
'''Returns a list of tuples: (sort condition, reverse sort)'''
cmp = []
for attr in sort_string.split(','):
attr = attr.strip()
rev = False
if attr.endswith('+'):
attr = attr[:-1]
elif attr.endswith('-'):
attr = attr[:-1]
rev = True
cmp.append((attr, rev))
return cmp
def filter_tasks(filter_string, tw):
'''Returns a list of tasks filtered by a given TW-compatible string'''
query = _process_filter(filter_string)
return tw._get_task_objects(*query, 'export') # pylint: disable=protected-access
def sort_tasks(tasks, sort_string):
'''Sorts a list of tasks (in-place) according to a complex TW-compatible
sort string.'''
if not sort_string:
return
cmp = _process_sort_string(sort_string)
comparer = functools.partial(TaskComparer, cmp=cmp)
tasks.sort(key=comparer)
......@@ -58,6 +58,10 @@ class BlockView(urwid.BoxAdapter):
def __init__(self, screen, cfg, tw, block):
self.block = block
self.tasks = twutils.filter_tasks(block.filter, tw)
twutils.sort_tasks(self.tasks, block.sort)
if block.limit is not None:
self.tasks = self.tasks[:block.limit]
focus_map = {c: '{}-focus'.format(c)
for c in cfg.colors if not c.endswith('-focus')}
......@@ -69,7 +73,7 @@ class BlockView(urwid.BoxAdapter):
lw = urwid.ListBox(urwid.SimpleFocusListWalker(textboxes))
lb = urwid.LineBox(lw, title=self.block.name)
height = _dim(self.block.height, screen.get_cols_rows()[1])
height = _dim(str(self.block.height), screen.get_cols_rows()[1])
super().__init__(lb, height=height)
......
from collections import OrderedDict
import pytest
import twc.twutils as twutils
@pytest.mark.parametrize('sortcond,expected_order', [
('', [0, 1, 2, 3]),
('id', [0, 1, 2, 3]),
('id-', [3, 2, 1, 0]),
('a+', [0, 1, 2, 3]), # a+ == a-; it is expected, because these do not
('a-', [0, 1, 2, 3]), # reverse values, but rather means __lt__ vs __gt__
('c', [1, 0, 2, 3]),
('c+', [1, 0, 2, 3]),
('c-', [3, 0, 2, 1]),
('d', [0, 3, 1, 2]),
('d-', [1, 2, 3, 0]), # same as TW: tasks without attr are at the top
('b+,id-', [3, 0, 2, 1]),
('b+,id+', [0, 3, 1, 2]),
('a+,d-,id-', [2, 1, 3, 0]),
('a+,d+,id-', [0, 3, 2, 1]),
])
def test_sort(sortcond, expected_order):
# Keep ids unique and incremented. They simplify investigation and reporting
# of errors.
tl = [
OrderedDict([('id', 0), ('a', 0), ('b', 0), ('c', 1), ('d', 0)]),
OrderedDict([('id', 1), ('a', 0), ('b', 1), ('c', 0)]),
OrderedDict([('id', 2), ('a', 0), ('b', 1), ('c', 1)]),
OrderedDict([('id', 3), ('a', 0), ('b', 0), ('c', 2), ('d', 1)]),
]
exp = [tl[i] for i in expected_order]
twutils.sort_tasks(tl, sortcond)
assert tl == exp, \
'Order: %s != %s' % (expected_order, [e['id'] for e in tl])
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment