Repository: pimutils/todoman
Pull request #200 (2017-03-21 02:49:20)

Add support for the `dtstart` field
Basically, according to the spec:

* `dtstart` defines when a todo should/can be started.  
  If it's `NULL`, it can be considered current every day.
* `due` defined when a todo is overdue.

To comply with this, hide todos which have a `dtstart` in the future by default (this is configurable).

1. I'm very much willing to listen to better names for the setting/flag. `today` is the best I could think of and sucks. `started` is a bit ambiguous, because started todos are those with `percent_complete > 0`. `workable` might to but is also really unintuitive.
1. I really prefer to keep this default, but if there's strong opposition (@untitaker?), I can revert it.
1. I'm inclined to remove `--today` since it looks superfluous (unless we revert the default).

This allows on to create far-into-the-future todos, and not have them clutter up the default view. It also makes recurring todos less painful.

Somewhat related to #184. Paves the way for #17.

untitaker: I wouldn't hide todos that start in the future, but only put dtstart into the
default sort order.

On Mon, Mar 20, 2017 at 07:49:20PM -0700, Hugo Osvaldo Barrera wrote:
> Basically, according to the spec:
> 
> * `dtstart` defines when a todo should/can be started.  
>   If it's `NULL`, it can be considered current every day.
> * `due` defined when a todo is overdue.
> 
> To comply with this, hide todos which have a `dtstart` in the future by default (this is configurable).
> 
> 1. I'm very much willing to listen to better names for the setting/flag. `today` is the best I could think of and sucks. `started` is a bit ambiguous, because started todos are those with `percent_complete > 0`. `workable` might to but is also really unintuitive.
> 1. I really prefer to keep this default, but if there's strong opposition (@untitaker?), I can revert it.
> 1. I'm inclined to remove `--today` since it looks superfluous (unless we revert the default).
> 
> This allows on to create far-into-the-future todos, and not have them clutter up the default view. It also makes recurring todos less painful.
> 
> Somewhat related to #184. Paves the way for #17.
> You can view, comment on, or merge this pull request online at:
> 
>   https://github.com/pimutils/todoman/pull/200
> 
> -- Commit Summary --
> 
>   * Add support for the `dtstart` field
>   * Swap start and due in the UI
> 
> -- File Changes --
> 
>     M CHANGELOG.rst (2)
>     M tests/test_cli.py (14)
>     M tests/test_model.py (13)
>     M todoman/cli.py (13)
>     M todoman/confspec.ini (6)
>     M todoman/interactive.py (2)
>     M todoman/model.py (11)
> 
> -- Patch Links --
> 
> https://github.com/pimutils/todoman/pull/200.patch
> https://github.com/pimutils/todoman/pull/200.diff
> 
> -- 
> You are receiving this because you were mentioned.
> Reply to this email directly or view it on GitHub:
> https://github.com/pimutils/todoman/pull/200


Patch:
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 59ff258..1aa2399 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -7,6 +7,8 @@ releases, in reverse chronological order.
 v3.0.0
 ------
 
+* Added a ``today`` setting and flag to show only todos which can be started
+  today (eg: don't start in the future).
 * Add the ``--humanize`` to show friendlier date times (eg: ``in 3 hours``).
 * Dropped ``--urgent`` and introduced ``--priority``
 * Added support for filtering by priority
diff --git a/setup.cfg b/setup.cfg
index 5ac84e5..b7e4789 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,6 +1,2 @@
 [aliases]
 test=pytest
-
-[tool:pytest]
-testpaths = tests
-addopts = --cov=todoman
diff --git a/todoman/cli.py b/todoman/cli.py
index df97901..03d619a 100644
--- a/todoman/cli.py
+++ b/todoman/cli.py
@@ -77,6 +77,14 @@ def _validate_start_date_param(ctx, param, val):
         raise click.BadParameter(e)
 
 
+def _validate_today_param(ctx, param, val):
+    ctx = ctx.find_object(AppContext)
+    if val is not None:
+        return val
+    else:
+        return ctx.config['main']['today']
+
+
 def _sort_callback(ctx, param, val):
     return val.split(',') if val else []
 
@@ -418,6 +426,10 @@ def move(ctx, list, ids):
               help='Only show finished tasks')
 @click.option('--start', default=None, callback=_validate_start_date_param,
               nargs=2, help='Only shows tasks before/after given DATE')
+@click.option('--today', default=None, is_flag=True,
+              callback=_validate_today_param, help='Show only todos which '
+              'should can be started today (eg: start time is not in the '
+              'future).')
 @catch_errors
 def list(ctx, **kwargs):
     """
@@ -433,6 +445,5 @@ def list(ctx, **kwargs):
 
     This is the default action when running `todo'.
     """
-
     todos = ctx.db.todos(**kwargs)
     click.echo(ctx.formatter.compact_multiple(todos))
diff --git a/todoman/confspec.ini b/todoman/confspec.ini
index 75dac7a..0e70b15 100644
--- a/todoman/confspec.ini
+++ b/todoman/confspec.ini
@@ -42,3 +42,9 @@ default_due = integer(default=24)
 # If the value is not specified, the database will be place in the
 # ``XDG_CACHE_HOME`` directory, generally, ``~/.cache``.
 cache_path = cache_path(default='')
+
+# If set to true, only show todos which can be worked on today; these are todos
+# which have a start date today, or some day in the past.  Todos with no start
+# date are always considered current. Incomplete todos (eg: partially-complete)
+# are also included
+today=boolean(default=False)
diff --git a/todoman/interactive.py b/todoman/interactive.py
index 77a25cb..b0a66ab 100644
--- a/todoman/interactive.py
+++ b/todoman/interactive.py
@@ -52,8 +52,8 @@ class TodoEditor:
         for label, field in [("Summary", self._summary),
                              ("Description", self._description),
                              ("Location", self._location),
-                             ("Due", self._due),
                              ("Start", self._dtstart),
+                             ("Due", self._due),
                              ("Completed", self._completed),
                              ("Priority", self._priority),
                              ]:
diff --git a/todoman/model.py b/todoman/model.py
index 313efbd..c89abc3 100644
--- a/todoman/model.py
+++ b/todoman/model.py
@@ -412,6 +412,7 @@ class Cache:
                 "uid" TEXT,
                 "summary" TEXT,
                 "due" INTEGER,
+                "start" INTEGER,
                 "priority" INTEGER,
                 "created_at" INTEGER,
                 "completed_at" INTEGER,
@@ -501,6 +502,7 @@ class Cache:
                 uid,
                 summary,
                 due,
+                start,
                 priority,
                 created_at,
                 completed_at,
@@ -510,7 +512,7 @@ class Cache:
                 description,
                 location,
                 categories
-            ) VALUES ({}?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
+            ) VALUES ({}?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
             '''
 
         params = (
@@ -518,6 +520,7 @@ class Cache:
             todo.get('uid'),
             todo.get('summary'),
             self._serialize_datetime(todo, 'due'),
+            self._serialize_datetime(todo, 'dtstart'),
             todo.get('priority', 0) or None,
             self._serialize_datetime(todo, 'created'),
             self._serialize_datetime(todo, 'completed'),
@@ -546,7 +549,7 @@ class Cache:
 
     def todos(self, all=False, lists=[], priority=None, location='',
               category='', grep='', sort=[], reverse=True, due=None,
-              done_only=None, start=None):
+              done_only=None, start=None, today=False):
         """
         Returns filtered cached todos, in a specified order.
 
@@ -620,6 +623,9 @@ class Cache:
             else:
                 extra_where.append('AND created_at >= ?')
                 params.append(dt)
+        if today:
+            extra_where.append('AND (start IS NULL OR start <= ?)')
+            params.append(datetime.now().timestamp())
         if sort:
             order = []
             for s in sort:
@@ -674,6 +680,7 @@ class Cache:
         todo.uid = row['uid']
         todo.summary = row['summary']
         todo.due = self._dt_from_db(row['due'])
+        todo.start = self._dt_from_db(row['start'])
         todo.priority = row['priority']
         todo.created_at = self._dt_from_db(row['created_at'])
         todo.completed_at = self._dt_from_db(row['completed_at'])
diff --git a/tox.ini b/tox.ini
index 6e3d1a5..eabcf15 100644
--- a/tox.ini
+++ b/tox.ini
@@ -31,3 +31,6 @@ commands =
 exclude=.tox,build
 application-import-names=todoman,tests
 import-order-style=smarkets
+
+[pytest]
+testpaths = tests


Test patch:
diff --git a/tests/test_cli.py b/tests/test_cli.py
index b0d8b4b..58ab2d4 100644
--- a/tests/test_cli.py
+++ b/tests/test_cli.py
@@ -560,3 +560,34 @@ def test_todo_edit(runner, default_database, todo_factory):
 
     assert not result.exception
     assert 'YARR!' in result.output
+
+
+@freeze_time('2017, 3, 20')
+def test_list_today(tmpdir, runner, todo_factory):
+    todo_factory(summary='started', start=datetime.datetime(2017, 3, 15))
+    todo_factory(summary='nostart')
+    todo_factory(summary='unstarted', start=datetime.datetime(2017, 3, 24))
+
+    result = runner.invoke(cli, ['list', '--today'], catch_exceptions=False)
+
+    assert not result.exception
+    assert 'started' in result.output
+    assert 'nostart' in result.output
+    assert 'unstarted' not in result.output
+
+    result = runner.invoke(cli, ['list'], catch_exceptions=False)
+
+    assert not result.exception
+    assert 'started' in result.output
+    assert 'nostart' in result.output
+    assert 'unstarted' in result.output
+
+    path = tmpdir.join('config')
+    path.write('today = yes\n', 'a')
+
+    result = runner.invoke(cli, ['list'], catch_exceptions=False)
+
+    assert not result.exception
+    assert 'started' in result.output
+    assert 'nostart' in result.output
+    assert 'unstarted' not in result.output
diff --git a/tests/test_model.py b/tests/test_model.py
index 5f3e676..e5f68ae 100644
--- a/tests/test_model.py
+++ b/tests/test_model.py
@@ -268,3 +268,16 @@ def test_clone():
     assert clone.id is None
     assert todo.filename != clone.filename
     assert clone.uid in clone.filename
+
+
+@freeze_time('2017, 3, 20')
+def test_todos_today(tmpdir, runner, todo_factory, default_database):
+    todo_factory(summary='started', start=datetime(2017, 3, 15))
+    todo_factory(summary='nostart')
+    todo_factory(summary='unstarted', start=datetime(2017, 3, 24))
+
+    todos = list(default_database.todos(today=True))
+
+    assert len(todos) == 2
+    for todo in todos:
+        assert 'unstarted' not in todo.summary