1
0
Fork 0
mirror of https://github.com/topydo/topydo.git synced 2024-05-20 13:58:33 +00:00

Compare commits

...

4 commits

Author SHA1 Message Date
Marek Šuppa 33ec48ecac
Merge f007b1b5fd into f3dc108d58 2023-09-02 05:59:39 -07:00
David Steele f3dc108d58 Fix tests for test completion test change 2023-08-14 10:25:40 -04:00
David Steele 5d7a742095 Use todo.txt spec for id-ing completed tasks
Topydo was only identifying a task as completed if it included a
completion date (which topydo automatically adds). This affects
compatibility with other todo.txt apps, making archiving unreliable.
2023-08-14 10:14:31 -04:00
mr.Shu f007b1b5fd update: Add counts for projects and contexts
* Add `projects_counts` and `contexts_counts` functions to
  `TodoListBase` that return a dict of all projects/contexts along with
  their counts.

* Add command line flags to `lsproj` and `lscont` that allow
  project/contexts to be listed with their respective counts, while also
  being sorted by this count.

* Add tests for all new functionality.

Signed-off-by: mr.Shu <mr@shu.io>
2018-10-25 23:08:23 +02:00
11 changed files with 215 additions and 13 deletions

View file

@ -1,4 +1,3 @@
x 2014-10-19 Complete
x 2014-10-20 Another one complete
x Not complete
(C) Active

View file

@ -0,0 +1,11 @@
(C) Foo @Context2 Not@Context +Project1 Not+Project
(D) Bar @Context1 +Project2
(C) Baz @Context1 +Project1 key:value
(C) Drink beer @ home
(C) 13 + 29 = 42
Another Bar @Context3
Different Foo +Project3
Another Different Bar @Context3
Different Another Bar Foo @Context3 +Project3
Foo Bar @Context3 +Project3

View file

@ -33,7 +33,7 @@ class ArchiveCommandTest(CommandTest):
self.assertTrue(todolist.dirty)
self.assertTrue(archive.dirty)
self.assertEqual(todolist.print_todos(), "x Not complete\n(C) Active")
self.assertEqual(todolist.print_todos(), "(C) Active")
self.assertEqual(archive.print_todos(), "x 2014-10-19 Complete\nx 2014-10-20 Another one complete")
if __name__ == '__main__':

View file

@ -39,6 +39,30 @@ class ListContextCommandTest(CommandTest):
self.assertEqual(self.output, "Context1\nContext2\n")
self.assertFalse(self.errors)
def test_contexts_with_counts(self):
todolist = load_file_to_todolist("test/data/TodoListBiggerTest.txt")
command = ListContextCommand(["-c"], todolist, self.out, self.error)
command.execute()
self.assertEqual(self.output, "2\tContext1\n1\tContext2\n4\tContext3\n")
self.assertFalse(self.errors)
def test_contexts_with_counts_sorted(self):
todolist = load_file_to_todolist("test/data/TodoListBiggerTest.txt")
command = ListContextCommand(["-c", "-s"], todolist, self.out, self.error)
command.execute()
self.assertEqual(self.output, "1\tContext2\n2\tContext1\n4\tContext3\n")
self.assertFalse(self.errors)
def test_contexts_with_counts_sorted_inversely(self):
todolist = load_file_to_todolist("test/data/TodoListBiggerTest.txt")
command = ListContextCommand(["-c", "-S"], todolist, self.out, self.error)
command.execute()
self.assertEqual(self.output, "4\tContext3\n2\tContext1\n1\tContext2\n")
self.assertFalse(self.errors)
def test_listcontext_name(self):
name = ListContextCommand.name()

View file

@ -41,6 +41,33 @@ class ListProjectCommandTest(CommandTest):
self.assertEqual(self.output, "Project1\nProject2\n")
self.assertFalse(self.errors)
def test_projects_with_counts(self):
todolist = load_file_to_todolist("test/data/TodoListBiggerTest.txt")
command = ListProjectCommand(["-c"], todolist, self.out, self.error)
command.execute()
command.execute_post_archive_actions()
self.assertEqual(self.output, "2\tProject1\n1\tProject2\n3\tProject3\n")
self.assertFalse(self.errors)
def test_projects_with_counts_sorted(self):
todolist = load_file_to_todolist("test/data/TodoListBiggerTest.txt")
command = ListProjectCommand(["-c", "-s"], todolist, self.out, self.error)
command.execute()
command.execute_post_archive_actions()
self.assertEqual(self.output, "1\tProject2\n2\tProject1\n3\tProject3\n")
self.assertFalse(self.errors)
def test_projects_with_counts_sorted_inversely(self):
todolist = load_file_to_todolist("test/data/TodoListBiggerTest.txt")
command = ListProjectCommand(["-c", "-S"], todolist, self.out, self.error)
command.execute()
command.execute_post_archive_actions()
self.assertEqual(self.output, "3\tProject3\n2\tProject1\n1\tProject2\n")
self.assertFalse(self.errors)
def test_listproject_name(self):
name = ListProjectCommand.name()

68
test/test_parse.py Normal file
View file

@ -0,0 +1,68 @@
# Topydo - A todo.txt client written in Python.
# Copyright (C) 2023 David Steele <dsteele@gmail.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import unittest
from topydo.lib.TodoParser import parse_line
complete_tasks = [
"x 2023-08-12",
"x 2023-08-12 ",
"x 2023-08-12 2023-08-12",
"x 2023-08-12 2023-08-12 ",
"x 2023-08-12 2023-08-12 task text",
"x 2023-08-12 2023-08-12 task text",
"x 2023-08-12 task text",
"x task text",
"x ",
]
incomplete_tasks = [
"",
" ",
" x",
" x ",
"x",
"incomplete task",
]
def gen_test(task, result):
def test_fn(self):
task_dict = parse_line(task)
self.assertEqual(task_dict["completed"], result, f'Failed "{task}"')
return test_fn
def populate_tasks(cls):
for i, task in enumerate(complete_tasks):
setattr(cls, f"test_complete_{i}", gen_test(task, True))
for i, task in enumerate(incomplete_tasks):
setattr(cls, f"test_incomplete_{i}", gen_test(task, False))
return cls
@populate_tasks
class ParseTodoTest(unittest.TestCase):
pass
if __name__ == "__main__":
unittest.main()

View file

@ -229,12 +229,6 @@ class TodoBaseTester(TopydoTest):
self.assertFalse(todo.is_completed())
def test_completion3(self):
""" A completed todo must start with an x followed by a date. """
todo = TodoBase("x Not complete")
self.assertFalse(todo.is_completed())
def test_completion4(self):
""" A completed todo must start with an x followed by a date. """
todo = TodoBase("X 2014-06-14 Not complete")

View file

@ -25,12 +25,39 @@ class ListContextCommand(Command):
super().__init__(
p_args, p_todolist, p_out, p_err, p_prompt)
self.counts = False
self.sort_by = 'name'
def _process_flags(self):
flags, args = self.getopt("csS")
for flag, _ in flags:
if flag == "-c":
self.counts = True
elif flag == "-s":
self.sort_by = 'counts'
elif flag == "-S":
self.sort_by = 'counts_inv'
def execute(self):
if not super().execute():
return False
for context in sorted(self.todolist.contexts(), key=lambda s: s.lower()):
self.out(context)
self._process_flags()
if self.counts:
sorting_fns = {
'name': lambda s: s[0].lower(),
'counts': lambda s: s[1],
'counts_inv': lambda s: -s[1]
}
for context, c in sorted(self.todolist.contexts_counts().items(),
key=sorting_fns[self.sort_by]):
self.out("{}\t{}".format(c, context))
else:
for context in sorted(self.todolist.contexts(),
key=lambda s: s.lower()):
self.out(context)
def usage(self):
return """Synopsis: lscon"""

View file

@ -25,12 +25,39 @@ class ListProjectCommand(Command):
super().__init__(
p_args, p_todolist, p_out, p_err, p_prompt)
self.counts = False
self.sort_by = 'name'
def _process_flags(self):
flags, args = self.getopt("csS")
for flag, _ in flags:
if flag == "-c":
self.counts = True
elif flag == "-s":
self.sort_by = 'counts'
elif flag == "-S":
self.sort_by = 'counts_inv'
def execute(self):
if not super().execute():
return False
for project in sorted(self.todolist.projects(), key=lambda s: s.lower()):
self.out(project)
self._process_flags()
if self.counts:
sorting_fns = {
'name': lambda s: s[0].lower(),
'counts': lambda s: s[1],
'counts_inv': lambda s: -s[1]
}
for project, c in sorted(self.todolist.projects_counts().items(),
key=sorting_fns[self.sort_by]):
self.out("{}\t{}".format(c, project))
else:
for project in sorted(self.todolist.projects(),
key=lambda s: s.lower()):
self.out(project)
def usage(self):
return """Synopsis: lsprj"""

View file

@ -20,6 +20,7 @@ A list of todo items.
import math
import re
from collections import Counter
from datetime import date
from topydo.lib import Filter
@ -218,6 +219,18 @@ class TodoListBase(object):
return result
def projects_counts(self):
""" Returns a dict of all projects in this list with their respective
couts. """
result = Counter()
for todo in self._todos:
projects = todo.projects()
for project in projects:
result[project] += 1
return dict(result)
def contexts(self):
""" Returns a set of all contexts in this list. """
result = set()
@ -227,6 +240,18 @@ class TodoListBase(object):
return result
def contexts_counts(self):
""" Returns a dict of all contexts in this list with their respective
counts. """
result = Counter()
for todo in self._todos:
contexts = todo.contexts()
for context in contexts:
result[context] += 1
return dict(result)
def view(self, p_sorter, p_filters):
"""
Constructs a view of the todo list.

View file

@ -26,7 +26,7 @@ from topydo.lib.Utils import date_string_to_date
_DATE_MATCH = r'\d{4}-\d{2}-\d{2}'
_COMPLETED_HEAD_MATCH = re.compile(
r'x ((?P<completionDate>' + _DATE_MATCH + ') )' + '((?P<creationDate>' +
r'x ((?P<completionDate>' + _DATE_MATCH + ') )?' + '((?P<creationDate>' +
_DATE_MATCH + ') )?(?P<rest>.*)')
_NORMAL_HEAD_MATCH = re.compile(