Repository: fniessink/next-action

Issue:
Show only overdue tasks
Select a next action that is over due:

```console
$ next-action --over-due
Buy flowers due:2018-02-14
```

Show all overdue next actions:
```console
$ next-action --all --over-due
Buy flowers due:2018-02-14
Submit taxes due:2018-05-01
```


Discussion:

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1e024cc..d57b572 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
 The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
 and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
 
+## [Unreleased]
+
+### Added
+
+- Option to limit the next action to tasks that are over due. Closes #75.
+
 ## [0.8.0] - 2018-05-24
 
 ### Added
diff --git a/README.md b/README.md
index f76dbf3..46d4a98 100644
--- a/README.md
+++ b/README.md
@@ -27,7 +27,7 @@ Don't know what *Todo.txt* is? See <https://github.com/todotxt/todo.txt> for the
 
 ```console
 $ next-action --help
-usage: next-action [-h] [--version] [-c <config.cfg> | -C] [-f <todo.txt>] [-n <number> | -a] [<context|project> ...]
+usage: next-action [-h] [--version] [-c <config.cfg> | -C] [-f <todo.txt>] [-n <number> | -a] [-o] [<context|project> ...]
 
 Show the next action in your todo.txt. The next action is selected from the tasks in the todo.txt file based on task
 properties such as priority, due date, and creation date. Limit the tasks from which the next action is selected by
@@ -46,6 +46,7 @@ optional arguments:
   -n <number>, --number <number>
                         number of next actions to show (default: 1)
   -a, --all             show all next actions
+  -o, --overdue         show only overdue next actions
 
 optional context and project arguments; these can be repeated:
   @<context>            context the next action must have
@@ -99,6 +100,13 @@ $ next-action -+PaintHouse @store
 (G) Buy wood for new +DogHouse @store
 ```
 
+To make sure you have no overdue actions, or work on overdue actions first, limit the tasks from which the next action is selected to overdue actions:
+
+```console
+$ next-action --overdue
+Buy flowers due:2018-02-14
+```
+
 ### Showing more than one next action
 
 To show more than one next action, supply the number you think you can handle:
@@ -189,9 +197,9 @@ To run the unit tests:
 
 ```console
 $ python -m unittest
-........................................................................................................................
+...........................................................................................................................
 ----------------------------------------------------------------------
-Ran 120 tests in 0.202s
+Ran 123 tests in 0.187s
 
 OK
 ```
diff --git a/docs/todo.txt b/docs/todo.txt
index df5e79a..f69b27d 100644
--- a/docs/todo.txt
+++ b/docs/todo.txt
@@ -4,5 +4,6 @@
 (G) Buy wood for new +DogHouse @store
 Get rid of old +DogHouse @home
 Borrow ladder from the neighbors +PaintHouse @home
+Buy flowers due:2018-02-14
 x This is a completed task
 9999-01-01 Start preparing for five-digit years
diff --git a/next_action/arguments/__init__.py b/next_action/arguments/__init__.py
index efc3a95..6b132bd 100644
--- a/next_action/arguments/__init__.py
+++ b/next_action/arguments/__init__.py
@@ -3,23 +3,23 @@
 import argparse
 import os
 import shutil
-import sys
-from typing import cast, Dict, List, Set, Tuple
+from typing import Tuple
 
 from .arguments import Arguments
 from .parser import NextActionArgumentParser
 
 
-def parse_arguments() -> Arguments:
+def parse_arguments() -> Tuple[argparse.ArgumentParser, Arguments]:
     """ Build the argument parser, parse the command line arguments, and post-process them. """
     # Ensure that the help info is printed using all columns available
     os.environ['COLUMNS'] = str(shutil.get_terminal_size().columns)
     default_filenames = ["~/todo.txt"]
     parser = NextActionArgumentParser(default_filenames)
-    arguments = Arguments(parser, default_filenames)
+    arguments = Arguments(default_filenames)
     namespace = parser.parse_args()
     arguments.filenames = namespace.file
     arguments.number = namespace.number
     arguments.show_all(namespace.all)
+    arguments.overdue = namespace.overdue
     arguments.filters = namespace.filters
-    return arguments
+    return parser, arguments
diff --git a/next_action/arguments/arguments.py b/next_action/arguments/arguments.py
index b7b5a73..5356fc5 100644
--- a/next_action/arguments/arguments.py
+++ b/next_action/arguments/arguments.py
@@ -1,6 +1,5 @@
 """ Argument data class for transfering command line arguments. """
 
-import argparse
 import os.path
 import sys
 from typing import List, Set
@@ -8,12 +7,12 @@ from typing import List, Set
 
 class Arguments(object):
     """ Argument data class. """
-    def __init__(self, parser: argparse.ArgumentParser, default_filenames: List[str]) -> None:
-        self.parser = parser
+    def __init__(self, default_filenames: List[str]) -> None:
         self.__default_filenames = default_filenames
         self.__filenames: List[str] = []
-        self.__filters: List[Set[str]] = []
+        self.__filters: List[Set[str]] = [set(), set(), set(), set()]
         self.number = 1
+        self.overdue = False
 
     @property
     def filenames(self) -> List[str]:
diff --git a/next_action/arguments/parser.py b/next_action/arguments/parser.py
index 604915c..860f94a 100644
--- a/next_action/arguments/parser.py
+++ b/next_action/arguments/parser.py
@@ -19,7 +19,7 @@ class NextActionArgumentParser(argparse.ArgumentParser):
                         "todo.txt file based on task properties such as priority, due date, and creation date. Limit "
                         "the tasks from which the next action is selected by specifying contexts the tasks must have "
                         "and/or projects the tasks must belong to.",
-            usage="next-action [-h] [--version] [-c <config.cfg> | -C] [-f <todo.txt>] [-n <number> | -a] "
+            usage="next-action [-h] [--version] [-c <config.cfg> | -C] [-f <todo.txt>] [-n <number> | -a] [-o] "
                   "[<context|project> ...]")
         self.add_optional_arguments(default_filenames)
         self.add_positional_arguments()
@@ -45,6 +45,7 @@ class NextActionArgumentParser(argparse.ArgumentParser):
             "-n", "--number", metavar="<number>", type=int, default=1,
             help="number of next actions to show (default: %(default)s)")
         number.add_argument("-a", "--all", help="show all next actions", action="store_true")
+        self.add_argument("-o", "--overdue", help="show only overdue next actions", action="store_true")
 
     def add_positional_arguments(self) -> None:
         """ Add the positional arguments to the parser. """
diff --git a/next_action/cli.py b/next_action/cli.py
index 03cda4c..68f2c50 100644
--- a/next_action/cli.py
+++ b/next_action/cli.py
@@ -15,11 +15,11 @@ def next_action() -> None:
         2) read todo.txt file(s),
         3) determine the next action(s) and display them.
     """
-    arguments = parse_arguments()
+    parser, arguments = parse_arguments()
     try:
         with fileinput.input(arguments.filenames) as todotxt_file:
             tasks = read_todotxt_file(todotxt_file)
     except OSError as reason:
-        arguments.parser.error("can't open file: {0}".format(reason))
-    actions = next_actions(tasks, *arguments.filters)
+        parser.error("can't open file: {0}".format(reason))
+    actions = next_actions(tasks, arguments)
     print("\n".join(action.text for action in actions[:arguments.number]) if actions else "Nothing to do!")
diff --git a/next_action/pick_action.py b/next_action/pick_action.py
index 71646d4..aa9d625 100644
--- a/next_action/pick_action.py
+++ b/next_action/pick_action.py
@@ -1,9 +1,10 @@
 """ Algorithm for deciding the next action(s). """
 
 import datetime
-from typing import Set, Sequence, Tuple
+from typing import Sequence, Tuple
 
 from .todotxt import Task
+from .arguments import Arguments
 
 
 def sort_key(task: Task) -> Tuple[str, datetime.date, datetime.date, int]:
@@ -12,9 +13,9 @@ def sort_key(task: Task) -> Tuple[str, datetime.date, datetime.date, int]:
             -len(task.projects()))
 
 
-def next_actions(tasks: Sequence[Task], contexts: Set[str] = None, projects: Set[str] = None,
-                 excluded_contexts: Set[str] = None, excluded_projects: Set[str] = None) -> Sequence[Task]:
+def next_actions(tasks: Sequence[Task], arguments: Arguments) -> Sequence[Task]:
     """ Return the next action(s) from the collection of tasks. """
+    contexts, projects, excluded_contexts, excluded_projects = arguments.filters
     # First, get the potential next actions by filtering out completed tasks and tasks with a future creation date
     actionable_tasks = [task for task in tasks if task.is_actionable()]
     # Then, exclude tasks that have an excluded context
@@ -27,5 +28,7 @@ def next_actions(tasks: Sequence[Task], contexts: Set[str] = None, projects: Set
     tasks_in_context = filter(lambda task: contexts <= task.contexts() if contexts else True, eligible_tasks)
     # Next, select the tasks that belong to at least one of the given projects, if any
     tasks_in_project = filter(lambda task: projects & task.projects() if projects else True, tasks_in_context)
+    # If the user only wants to see overdue tasks, filter out non-overdue tasks
+    tasks_in_project = filter(lambda task: task.is_overdue() if arguments.overdue else True, tasks_in_project)
     # Finally, sort by priority, due date and creation date
     return sorted(tasks_in_project, key=sort_key)
diff --git a/next_action/todotxt/task.py b/next_action/todotxt/task.py
index 5a2e37f..226a5df 100644
--- a/next_action/todotxt/task.py
+++ b/next_action/todotxt/task.py
@@ -54,6 +54,10 @@ class Task(object):
             date. """
         return not self.is_completed() and not self.is_future()
 
+    def is_overdue(self) -> bool:
+        """ Return whether the taks is overdue, i.e. whether it has a due date in the past. """
+        return self.due_date() < datetime.date.today() if self.due_date() else False
+
     def __prefixed_items(self, prefix: str) -> Set[str]:
         """ Return the prefixed items in the task. """
         return {match.group(1) for match in re.finditer(" {0}([^ ]+)".format(prefix), self.text)}


Test patch:
diff --git a/tests/unittests/arguments/test_arguments.py b/tests/unittests/arguments/test_arguments.py
index 50d4da0..3a76282 100644
--- a/tests/unittests/arguments/test_arguments.py
+++ b/tests/unittests/arguments/test_arguments.py
@@ -1,7 +1,6 @@
 """ Unit tests for the argument data class. """
 
 import unittest
-from unittest.mock import Mock
 
 from next_action.arguments import Arguments
 
@@ -11,22 +10,22 @@ class FilenameArgumentsTest(unittest.TestCase):
 
     def test_default_filenames(self):
         """ Test that the default file name is returned when no filenames are set. """
-        self.assertEqual(["todo.txt"], Arguments(Mock(), ["todo.txt"]).filenames)
+        self.assertEqual(["todo.txt"], Arguments(["todo.txt"]).filenames)
 
     def test_set_filenames(self):
         """ Test that the set file names are returned when filenames are set. """
-        arguments = Arguments(Mock(), ["todo.txt"])
+        arguments = Arguments(["todo.txt"])
         arguments.filenames = ["foo.txt"]
         self.assertEqual(["foo.txt"], arguments.filenames)
 
     def test_remove_default(self):
         """ Test that the default filename is removed when it is the filenames. """
-        arguments = Arguments(Mock(), ["todo.txt"])
+        arguments = Arguments(["todo.txt"])
         arguments.filenames = ["todo.txt", "foo.txt"]
         self.assertEqual(["foo.txt"], arguments.filenames)
 
     def test_remove_default_once(self):
         """ Test that the default filename is removed when it is the filenames, but only one time. """
-        arguments = Arguments(Mock(), ["todo.txt"])
+        arguments = Arguments(["todo.txt"])
         arguments.filenames = ["todo.txt", "todo.txt", "foo.txt"]
         self.assertEqual(["todo.txt", "foo.txt"], arguments.filenames)
diff --git a/tests/unittests/arguments/test_config.py b/tests/unittests/arguments/test_config.py
index 775e336..c0fd64c 100644
--- a/tests/unittests/arguments/test_config.py
+++ b/tests/unittests/arguments/test_config.py
@@ -19,14 +19,14 @@ class ConfigFileTest(unittest.TestCase):
     @patch.object(config, "open", mock_open(read_data=""))
     def test_empty_file(self):
         """ Test that an empty config file doesn't change the filenames. """
-        self.assertEqual([os.path.expanduser("~/todo.txt")], parse_arguments().filenames)
+        self.assertEqual([os.path.expanduser("~/todo.txt")], parse_arguments()[1].filenames)
 
     @patch.object(sys, "argv", ["next-action"])
     @patch.object(config, "open")
     def test_missing_default_config(self, mock_file_open):
         """ Test that a missing config file at the default location is no problem. """
         mock_file_open.side_effect = FileNotFoundError("some problem")
-        self.assertEqual([os.path.expanduser("~/todo.txt")], parse_arguments().filenames)
+        self.assertEqual([os.path.expanduser("~/todo.txt")], parse_arguments()[1].filenames)
 
     @patch.object(sys, "argv", ["next-action", "--config-file", "config.cfg"])
     @patch.object(config, "open", mock_open(read_data="- this_is_invalid"))
@@ -130,19 +130,19 @@ class FilenameTest(unittest.TestCase):
     @patch.object(config, "open", mock_open(read_data="file: todo.txt"))
     def test_valid_file(self):
         """ Test that a valid filename changes the filenames. """
-        self.assertEqual(["todo.txt"], parse_arguments().filenames)
+        self.assertEqual(["todo.txt"], parse_arguments()[1].filenames)
 
     @patch.object(sys, "argv", ["next-action", "--config-file", "config.cfg"])
     @patch.object(config, "open", mock_open(read_data="file:\n- todo.txt\n- tada.txt"))
     def test_valid_files(self):
         """ Test that a list of valid filenames changes the filenames. """
-        self.assertEqual(["todo.txt", "tada.txt"], parse_arguments().filenames)
+        self.assertEqual(["todo.txt", "tada.txt"], parse_arguments()[1].filenames)
 
     @patch.object(sys, "argv", ["next-action", "--config-file", "config.cfg", "--file", "tada.txt"])
     @patch.object(config, "open", mock_open(read_data="file: todo.txt"))
     def test_cli_takes_precedence(self):
         """ Test that a command line argument overrules the filename in the configuration file. """
-        self.assertEqual(["tada.txt"], parse_arguments().filenames)
+        self.assertEqual(["tada.txt"], parse_arguments()[1].filenames)
 
 
 class NumberTest(unittest.TestCase):
@@ -175,19 +175,19 @@ class NumberTest(unittest.TestCase):
     @patch.object(config, "open", mock_open(read_data="number: 3"))
     def test_valid_number(self):
         """ Test that a valid number changes the number argument. """
-        self.assertEqual(3, parse_arguments().number)
+        self.assertEqual(3, parse_arguments()[1].number)
 
     @patch.object(sys, "argv", ["next-action", "--config-file", "config.cfg", "--number", "3"])
     @patch.object(config, "open", mock_open(read_data="number: 2"))
     def test_cli_takes_precedence(self):
         """ Test that a command line argument overrules the number in the configuration file. """
-        self.assertEqual(3, parse_arguments().number)
+        self.assertEqual(3, parse_arguments()[1].number)
 
     @patch.object(sys, "argv", ["next-action", "--config-file", "config.cfg"])
     @patch.object(config, "open", mock_open(read_data="all: True"))
     def test_all_true(self):
         """ Test that all is true sets number to the max number. """
-        self.assertEqual(sys.maxsize, parse_arguments().number)
+        self.assertEqual(sys.maxsize, parse_arguments()[1].number)
 
     @patch.object(sys, "argv", ["next-action", "--config-file", "config.cfg"])
     @patch.object(config, "open", mock_open(read_data="all: False"))
@@ -218,10 +218,10 @@ class NumberTest(unittest.TestCase):
     @patch.object(config, "open", mock_open(read_data="all: True"))
     def test_argument_nr_overrides(self):
         """ Test that --number on the command line overrides --all in the configuration file. """
-        self.assertEqual(3, parse_arguments().number)
+        self.assertEqual(3, parse_arguments()[1].number)
 
     @patch.object(sys, "argv", ["next-action", "--config-file", "config.cfg", "--all"])
     @patch.object(config, "open", mock_open(read_data="number: 3"))
     def test_argument_all_overrides(self):
         """ Test that --all on the command line overrides --number in the configuration file. """
-        self.assertEqual(sys.maxsize, parse_arguments().number)
+        self.assertEqual(sys.maxsize, parse_arguments()[1].number)
diff --git a/tests/unittests/arguments/test_parser.py b/tests/unittests/arguments/test_parser.py
index 13801e9..d735f20 100644
--- a/tests/unittests/arguments/test_parser.py
+++ b/tests/unittests/arguments/test_parser.py
@@ -8,7 +8,7 @@ from unittest.mock import call, mock_open, patch
 from next_action.arguments import config, parse_arguments
 
 
-USAGE_MESSAGE = "usage: next-action [-h] [--version] [-c <config.cfg> | -C] [-f <todo.txt>] [-n <number> | -a] " \
+USAGE_MESSAGE = "usage: next-action [-h] [--version] [-c <config.cfg> | -C] [-f <todo.txt>] [-n <number> | -a] [-o] " \
                 "[<context|project> ...]\n"
 
 
@@ -19,12 +19,12 @@ class NoArgumentTest(unittest.TestCase):
     @patch.object(sys, "argv", ["next-action"])
     def test_filters(self):
         """ Test that the argument parser returns no filters if the user doesn't pass one. """
-        self.assertEqual([set(), set(), set(), set()], parse_arguments().filters)
+        self.assertEqual([set(), set(), set(), set()], parse_arguments()[1].filters)
 
     @patch.object(sys, "argv", ["next-action"])
     def test_filename(self):
         """ Test that the argument parser returns the default filename if the user doesn't pass one. """
-        self.assertEqual([os.path.expanduser("~/todo.txt")], parse_arguments().filenames)
+        self.assertEqual([os.path.expanduser("~/todo.txt")], parse_arguments()[1].filenames)
 
 
 @patch.object(config, "open", mock_open(read_data=""))
@@ -34,27 +34,27 @@ class FilenameTest(unittest.TestCase):
     @patch.object(sys, "argv", ["next-action", "-f", "my_todo.txt"])
     def test_filename_argument(self):
         """ Test that the argument parser accepts a filename. """
-        self.assertEqual(["my_todo.txt"], parse_arguments().filenames)
+        self.assertEqual(["my_todo.txt"], parse_arguments()[1].filenames)
 
     @patch.object(sys, "argv", ["next-action", "--file", "my_other_todo.txt"])
     def test_long_filename_argument(self):
         """ Test that the argument parser accepts a filename. """
-        self.assertEqual(["my_other_todo.txt"], parse_arguments().filenames)
+        self.assertEqual(["my_other_todo.txt"], parse_arguments()[1].filenames)
 
     @patch.object(sys, "argv", ["next-action", "-f", "todo.txt"])
     def test_add_default_filename(self):
         """ Test that adding the default filename doesn't get it included twice. """
-        self.assertEqual(["todo.txt"], parse_arguments().filenames)
+        self.assertEqual(["todo.txt"], parse_arguments()[1].filenames)
 
     @patch.object(sys, "argv", ["next-action", "-f", "todo.txt", "-f", "other.txt"])
     def test_default_and_non_default(self):
         """ Test that adding the default filename and another filename gets both included. """
-        self.assertEqual(["todo.txt", "other.txt"], parse_arguments().filenames)
+        self.assertEqual(["todo.txt", "other.txt"], parse_arguments()[1].filenames)
 
     @patch.object(sys, "argv", ["next-action", "-f", "other.txt", "-f", "other.txt"])
     def test__add_filename_twice(self):
         """ Test that adding the same filename twice includes it only once. """
-        self.assertEqual(["other.txt"], parse_arguments().filenames)
+        self.assertEqual(["other.txt"], parse_arguments()[1].filenames)
 
 
 @patch.object(config, "open", mock_open(read_data=""))
@@ -64,12 +64,12 @@ class FilterArgumentTest(unittest.TestCase):
     @patch.object(sys, "argv", ["next-action", "@home"])
     def test_one_context(self):
         """ Test that the argument parser returns the context if the user passes one. """
-        self.assertEqual({"home"}, parse_arguments().filters[0])
+        self.assertEqual({"home"}, parse_arguments()[1].filters[0])
 
     @patch.object(sys, "argv", ["next-action", "@home", "@work"])
     def test_multiple_contexts(self):
         """ Test that the argument parser returns all contexts if the user passes multiple contexts. """
-        self.assertEqual({"home", "work"}, parse_arguments().filters[0])
+        self.assertEqual({"home", "work"}, parse_arguments()[1].filters[0])
 
     @patch.object(sys, "argv", ["next-action", "@"])
     @patch.object(sys.stderr, "write")
@@ -84,7 +84,7 @@ class FilterArgumentTest(unittest.TestCase):
     @patch.object(sys, "argv", ["next-action", "-@home"])
     def test_exclude_context(self):
         """ Test that contexts can be excluded. """
-        self.assertEqual({"home"}, parse_arguments().filters[2])
+        self.assertEqual({"home"}, parse_arguments()[1].filters[2])
 
     @patch.object(sys, "argv", ["next-action", "@home", "-@home"])
     @patch.object(sys.stderr, "write")
@@ -108,12 +108,12 @@ class FilterArgumentTest(unittest.TestCase):
     @patch.object(sys, "argv", ["next-action", "+DogHouse"])
     def test_one_project(self):
         """ Test that the argument parser returns the project if the user passes one. """
-        self.assertEqual({"DogHouse"}, parse_arguments().filters[1])
+        self.assertEqual({"DogHouse"}, parse_arguments()[1].filters[1])
 
     @patch.object(sys, "argv", ["next-action", "+DogHouse", "+PaintHouse"])
     def test_multiple_projects(self):
         """ Test that the argument parser returns the projects if the user passes multiple projects. """
-        self.assertEqual({"DogHouse", "PaintHouse"}, parse_arguments().filters[1])
+        self.assertEqual({"DogHouse", "PaintHouse"}, parse_arguments()[1].filters[1])
 
     @patch.object(sys, "argv", ["next-action", "+"])
     @patch.object(sys.stderr, "write")
@@ -128,7 +128,7 @@ class FilterArgumentTest(unittest.TestCase):
     @patch.object(sys, "argv", ["next-action", "-+DogHouse"])
     def test_exclude_project(self):
         """ Test that projects can be excluded. """
-        self.assertEqual({"DogHouse"}, parse_arguments().filters[3])
+        self.assertEqual({"DogHouse"}, parse_arguments()[1].filters[3])
 
     @patch.object(sys, "argv", ["next-action", "+DogHouse", "-+DogHouse"])
     @patch.object(sys.stderr, "write")
@@ -153,8 +153,8 @@ class FilterArgumentTest(unittest.TestCase):
     @patch.object(sys, "argv", ["next-action", "+DogHouse", "@home", "+PaintHouse", "@weekend"])
     def test_contexts_and_projects(self):
         """ Test that the argument parser returns the contexts and the projects, even when mixed. """
-        self.assertEqual({"home", "weekend"}, parse_arguments().filters[0])
-        self.assertEqual({"DogHouse", "PaintHouse"}, parse_arguments().filters[1])
+        self.assertEqual({"home", "weekend"}, parse_arguments()[1].filters[0])
+        self.assertEqual({"DogHouse", "PaintHouse"}, parse_arguments()[1].filters[1])
 
     @patch.object(sys, "argv", ["next-action", "home"])
     @patch.object(sys.stderr, "write")
@@ -174,12 +174,12 @@ class NumberTest(unittest.TestCase):
     @patch.object(sys, "argv", ["next-action"])
     def test_default_number(self):
         """ Test that the argument parser has a default number of actions to return. """
-        self.assertEqual(1, parse_arguments().number)
+        self.assertEqual(1, parse_arguments()[1].number)
 
     @patch.object(sys, "argv", ["next-action", "--number", "3"])
     def test_number(self):
         """ Test that a number of actions to be shown can be passed. """
-        self.assertEqual(3, parse_arguments().number)
+        self.assertEqual(3, parse_arguments()[1].number)
 
     @patch.object(sys, "argv", ["next-action", "--number", "not_a_number"])
     @patch.object(sys.stderr, "write")
@@ -194,7 +194,7 @@ class NumberTest(unittest.TestCase):
     @patch.object(sys, "argv", ["next-action", "--all"])
     def test_all_actions(self):
         """ Test that --all option also sets the number of actions to show to a very big number. """
-        self.assertEqual(sys.maxsize, parse_arguments().number)
+        self.assertEqual(sys.maxsize, parse_arguments()[1].number)
 
     @patch.object(sys, "argv", ["next-action", "--all", "--number", "3"])
     @patch.object(sys.stderr, "write")
diff --git a/tests/unittests/test_cli.py b/tests/unittests/test_cli.py
index 0eca016..f8ad064 100644
--- a/tests/unittests/test_cli.py
+++ b/tests/unittests/test_cli.py
@@ -65,7 +65,8 @@ class CLITest(unittest.TestCase):
         os.environ['COLUMNS'] = "120"  # Fake that the terminal is wide enough.
         self.assertRaises(SystemExit, next_action)
         self.assertEqual(call("""\
-usage: next-action [-h] [--version] [-c <config.cfg> | -C] [-f <todo.txt>] [-n <number> | -a] [<context|project> ...]
+usage: next-action [-h] [--version] [-c <config.cfg> | -C] [-f <todo.txt>] [-n <number> | -a] [-o] [<context|project> \
+...]
 
 Show the next action in your todo.txt. The next action is selected from the tasks in the todo.txt file based on task
 properties such as priority, due date, and creation date. Limit the tasks from which the next action is selected by
@@ -84,6 +85,7 @@ optional arguments:
   -n <number>, --number <number>
                         number of next actions to show (default: 1)
   -a, --all             show all next actions
+  -o, --overdue         show only overdue next actions
 
 optional context and project arguments; these can be repeated:
   @<context>            context the next action must have
diff --git a/tests/unittests/test_pick_action.py b/tests/unittests/test_pick_action.py
index 8ccd64b..dad21e7 100644
--- a/tests/unittests/test_pick_action.py
+++ b/tests/unittests/test_pick_action.py
@@ -3,6 +3,7 @@
 import unittest
 
 from next_action import todotxt, pick_action
+from next_action.arguments import Arguments
 
 
 class PickActionTest(unittest.TestCase):
@@ -10,18 +11,18 @@ class PickActionTest(unittest.TestCase):
 
     def test_no_tasks(self):
         """ Test that no tasks means no next action. """
-        self.assertEqual([], pick_action.next_actions([]))
+        self.assertEqual([], pick_action.next_actions([], Arguments([])))
 
     def test_one_task(self):
         """ If there is one task, that one is the next action. """
         task = todotxt.Task("Todo")
-        self.assertEqual([task], pick_action.next_actions([task]))
+        self.assertEqual([task], pick_action.next_actions([task], Arguments([])))
 
     def test_multiple_tasks(self):
         """ If there are multiple tasks, the first is the next action. """
         task1 = todotxt.Task("Todo 1")
         task2 = todotxt.Task("Todo 2")
-        self.assertEqual([task1, task2], pick_action.next_actions([task1, task2]))
+        self.assertEqual([task1, task2], pick_action.next_actions([task1, task2], Arguments([])))
 
     def test_higher_prio_goes_first(self):
         """ If there are multiple tasks with different priorities, the task with the
@@ -29,7 +30,7 @@ class PickActionTest(unittest.TestCase):
         task1 = todotxt.Task("Todo 1")
         task2 = todotxt.Task("(B) Todo 2")
         task3 = todotxt.Task("(A) Todo 3")
-        self.assertEqual([task3, task2, task1], pick_action.next_actions([task1, task2, task3]))
+        self.assertEqual([task3, task2, task1], pick_action.next_actions([task1, task2, task3], Arguments([])))
 
     def test_creation_dates(self):
         """ Test that a task with an older creation date takes precedence. """
@@ -37,7 +38,7 @@ class PickActionTest(unittest.TestCase):
         newer_task = todotxt.Task("2018-02-02 Task 2")
         older_task = todotxt.Task("2017-01-01 Task 3")
         self.assertEqual([older_task, newer_task, no_creation_date],
-                         pick_action.next_actions([no_creation_date, newer_task, older_task]))
+                         pick_action.next_actions([no_creation_date, newer_task, older_task], Arguments([])))
 
     def test_priority_and_creation_date(self):
         """ Test that priority takes precedence over creation date. """
@@ -45,7 +46,7 @@ class PickActionTest(unittest.TestCase):
         newer_task = todotxt.Task("2018-02-02 Task 2")
         older_task = todotxt.Task("2017-01-01 Task 3")
         self.assertEqual([priority, older_task, newer_task],
-                         pick_action.next_actions([priority, newer_task, older_task]))
+                         pick_action.next_actions([priority, newer_task, older_task], Arguments([])))
 
     def test_due_dates(self):
         """ Test that a task with an earlier due date takes precedence. """
@@ -53,25 +54,25 @@ class PickActionTest(unittest.TestCase):
         earlier_task = todotxt.Task("Task 2 due:2018-02-02")
         later_task = todotxt.Task("Task 3 due:2019-01-01")
         self.assertEqual([earlier_task, later_task, no_due_date],
-                         pick_action.next_actions([no_due_date, later_task, earlier_task]))
+                         pick_action.next_actions([no_due_date, later_task, earlier_task], Arguments([])))
 
     def test_due_and_creation_dates(self):
         """ Test that a task with a due date takes precedence over creation date. """
         task1 = todotxt.Task("2018-1-1 Task 1")
         task2 = todotxt.Task("Task 2 due:2018-1-1")
-        self.assertEqual([task2, task1], pick_action.next_actions([task1, task2]))
+        self.assertEqual([task2, task1], pick_action.next_actions([task1, task2], Arguments([])))
 
     def test_project(self):
         """ Test that a task with a project takes precedence over a task without a project. """
         task1 = todotxt.Task("Task 1")
         task2 = todotxt.Task("Task 2 +Project")
-        self.assertEqual([task2, task1], pick_action.next_actions([task1, task2]))
+        self.assertEqual([task2, task1], pick_action.next_actions([task1, task2], Arguments([])))
 
     def test_projects(self):
         """ Test that a task with more projects takes precedence over a task with less projects. """
         task1 = todotxt.Task("Task 1 +Project")
         task2 = todotxt.Task("Task 2 +Project1 +Project2")
-        self.assertEqual([task2, task1], pick_action.next_actions([task1, task2]))
+        self.assertEqual([task2, task1], pick_action.next_actions([task1, task2], Arguments([])))
 
 
 class FilterTasksTest(unittest.TestCase):
@@ -82,59 +83,78 @@ class FilterTasksTest(unittest.TestCase):
         task1 = todotxt.Task("Todo 1 @work")
         task2 = todotxt.Task("(B) Todo 2 @work")
         task3 = todotxt.Task("(A) Todo 3 @home")
-        self.assertEqual([task2, task1], pick_action.next_actions([task1, task2, task3], contexts={"work"}))
+        arguments = Arguments([])
+        arguments.filters = ["@work"]
+        self.assertEqual([task2, task1], pick_action.next_actions([task1, task2, task3], arguments))
 
     def test_contexts(self):
         """ Test that the next action can be limited to a set of contexts. """
         task1 = todotxt.Task("Todo 1 @work @computer")
         task2 = todotxt.Task("(B) Todo 2 @work @computer")
         task3 = todotxt.Task("(A) Todo 3 @home @computer")
-        self.assertEqual([task2, task1], pick_action.next_actions([task1, task2, task3], contexts={"work", "computer"}))
+        arguments = Arguments([])
+        arguments.filters = ["@work", "@computer"]
+        self.assertEqual([task2, task1], pick_action.next_actions([task1, task2, task3], arguments))
 
     def test_excluded_context(self):
         """ Test that contexts can be excluded. """
         task = todotxt.Task("(A) Todo @computer")
-        self.assertEqual([], pick_action.next_actions([task], excluded_contexts={"computer"}))
+        arguments = Arguments([])
+        arguments.filters = ["-@computer"]
+        self.assertEqual([], pick_action.next_actions([task], arguments))
 
     def test_excluded_contexts(self):
         """ Test that contexts can be excluded. """
         task = todotxt.Task("(A) Todo @computer @work")
-        self.assertEqual([], pick_action.next_actions([task], excluded_contexts={"computer"}))
+        arguments = Arguments([])
+        arguments.filters = ["-@computer"]
+        self.assertEqual([], pick_action.next_actions([task], arguments))
 
     def test_not_excluded_context(self):
         """ Test that a task is not excluded if it doesn't belong to the excluded category. """
         task = todotxt.Task("(A) Todo @computer")
-        self.assertEqual([task], pick_action.next_actions([task], excluded_contexts={"phone"}))
+        arguments = Arguments([])
+        arguments.filters = ["-@phone"]
+        self.assertEqual([task], pick_action.next_actions([task], arguments))
 
     def test_project(self):
         """ Test that the next action can be limited to a specific project. """
         task1 = todotxt.Task("Todo 1 +ProjectX")
         task2 = todotxt.Task("(B) Todo 2 +ProjectX")
         task3 = todotxt.Task("(A) Todo 3 +ProjectY")
-        self.assertEqual([task2, task1], pick_action.next_actions([task1, task2, task3], projects={"ProjectX"}))
+        arguments = Arguments([])
+        arguments.filters = ["+ProjectX"]
+        self.assertEqual([task2, task1], pick_action.next_actions([task1, task2, task3], arguments))
 
     def test_excluded_project(self):
         """ Test that projects can be excluded. """
         task = todotxt.Task("(A) Todo +DogHouse")
-        self.assertEqual([], pick_action.next_actions([task], excluded_projects={"DogHouse"}))
+        arguments = Arguments([])
+        arguments.filters = ["-+DogHouse"]
+        self.assertEqual([], pick_action.next_actions([task], arguments))
 
     def test_excluded_projects(self):
         """ Test that projects can be excluded. """
         task = todotxt.Task("(A) Todo +DogHouse +PaintHouse")
-        self.assertEqual([], pick_action.next_actions([task], excluded_projects={"DogHouse"}))
+        arguments = Arguments([])
+        arguments.filters = ["-+DogHouse"]
+        self.assertEqual([], pick_action.next_actions([task], arguments))
 
     def test_not_excluded_project(self):
         """ Test that a task is not excluded if it doesn't belong to the excluded project. """
         task = todotxt.Task("(A) Todo +DogHouse")
-        self.assertEqual([task], pick_action.next_actions([task], excluded_projects={"PaintHouse"}))
+        arguments = Arguments([])
+        arguments.filters = ["-+PaintHouse"]
+        self.assertEqual([task], pick_action.next_actions([task], arguments))
 
     def test_project_and_context(self):
         """ Test that the next action can be limited to a specific project and context. """
         task1 = todotxt.Task("Todo 1 +ProjectX @office")
         task2 = todotxt.Task("(B) Todo 2 +ProjectX")
         task3 = todotxt.Task("(A) Todo 3 +ProjectY")
-        self.assertEqual([task1],
-                         pick_action.next_actions([task1, task2, task3], projects={"ProjectX"}, contexts={"office"}))
+        arguments = Arguments([])
+        arguments.filters = ["+ProjectX", "@office"]
+        self.assertEqual([task1], pick_action.next_actions([task1, task2, task3], arguments))
 
 
 class IgnoredTasksTest(unittest.TestCase):
@@ -144,17 +164,32 @@ class IgnoredTasksTest(unittest.TestCase):
         """ If there's one completed and one uncompleted task, the uncompleted one is the next action. """
         completed_task = todotxt.Task("x Completed")
         uncompleted_task = todotxt.Task("Todo")
-        self.assertEqual([uncompleted_task], pick_action.next_actions([completed_task, uncompleted_task]))
+        arguments = Arguments([])
+        self.assertEqual([uncompleted_task], pick_action.next_actions([completed_task, uncompleted_task], arguments))
 
     def test_ignore_future_task(self):
         """ Ignore tasks with a start date in the future. """
         future_task = todotxt.Task("(A) 9999-01-01 Start preparing for five-digit years")
         regular_task = todotxt.Task("(B) Look busy")
-        self.assertEqual([regular_task], pick_action.next_actions([future_task, regular_task]))
+        arguments = Arguments([])
+        self.assertEqual([regular_task], pick_action.next_actions([future_task, regular_task], arguments))
 
     def test_ignore_these_tasks(self):
         """ If all tasks are completed or future tasks, there's no next action. """
         completed_task1 = todotxt.Task("x Completed")
         completed_task2 = todotxt.Task("x Completed too")
         future_task = todotxt.Task("(A) 9999-01-01 Start preparing for five-digit years")
-        self.assertEqual([], pick_action.next_actions([completed_task1, completed_task2, future_task]))
+        arguments = Arguments([])
+        self.assertEqual([], pick_action.next_actions([completed_task1, completed_task2, future_task], arguments))
+
+
+class OverdueTasks(unittest.TestCase):
+    """ Unit tests for the overdue filter. """
+    def test_overdue_tasks(self):
+        """ Test that tasks that not overdue are filtered. """
+        no_duedate = todotxt.Task("Task")
+        future_duedate = todotxt.Task("Task due:9999-01-01")
+        overdue = todotxt.Task("Task due:2000-01-01")
+        arguments = Arguments([])
+        arguments.overdue = True
+        self.assertEqual([overdue], pick_action.next_actions([no_duedate, future_duedate, overdue], arguments))
diff --git a/tests/unittests/todotxt/test_task.py b/tests/unittests/todotxt/test_task.py
index efbb5b6..fee0c91 100644
--- a/tests/unittests/todotxt/test_task.py
+++ b/tests/unittests/todotxt/test_task.py
@@ -115,19 +115,39 @@ class DueDateTest(unittest.TestCase):
 
     def test_no_due_date(self):
         """ Test that tasks have no due date by default. """
-        self.assertEqual(None, todotxt.Task("Todo").due_date())
+        task = todotxt.Task("Todo")
+        self.assertEqual(None, task.due_date())
+        self.assertFalse(task.is_overdue())
 
     def test_due_date(self):
         """ Test a valid due date. """
-        self.assertEqual(datetime.date(2018, 1, 2), todotxt.Task("Todo due:2018-01-02").due_date())
+        task = todotxt.Task("Todo due:2018-01-02")
+        self.assertEqual(datetime.date(2018, 1, 2), task.due_date())
+        self.assertTrue(task.is_overdue())
+
+    def test_future_due_date(self):
+        """ Test a future due date. """
+        task = todotxt.Task("Todo due:9999-01-01")
+        self.assertEqual(datetime.date(9999, 1, 1), task.due_date())
+        self.assertFalse(task.is_overdue())
+
+    def test_due_today(self):
+        """ Test a future due date. """
+        task = todotxt.Task("Todo due:{0}".format(datetime.date.today().isoformat()))
+        self.assertEqual(datetime.date.today(), task.due_date())
+        self.assertFalse(task.is_overdue())
 
     def test_invalid_date(self):
         """ Test an invalid due date. """
-        self.assertEqual(None, todotxt.Task("Todo due:2018-01-32").due_date())
+        task = todotxt.Task("Todo due:2018-01-32")
+        self.assertEqual(None, task.due_date())
+        self.assertFalse(task.is_overdue())
 
     def test_no_space_after(self):
         """ Test a due date without a word boundary following it. """
-        self.assertEqual(None, todotxt.Task("Todo due:2018-01-023").due_date())
+        task = todotxt.Task("Todo due:2018-01-023")
+        self.assertEqual(None, task.due_date())
+        self.assertFalse(task.is_overdue())
 
     def test_single_digits(self):
         """ Test a due date with single digits for day and/or month. """