Repository: pimutils/todoman
Pull request #227 (2017-03-31 16:39:42)

Rename --today to --startable
This name is a lot more descriptive and not as confusing.


Patch:
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index ca217ce..a1410da 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -4,6 +4,13 @@ Changelog
 This file contains a brief summary of new features and dependency changes or
 releases, in reverse chronological order.
 
+v3.1.0
+------
+
+* Rename ``--today`` flag to ``--startable``.
+* Illegal start dates (eg: start dates that are not before the due date) are
+  ignored and are removed when saving an edited todo.
+
 v3.0.0
 ------
 
diff --git a/todoman/cli.py b/todoman/cli.py
index bad8ead..bc3a17f 100644
--- a/todoman/cli.py
+++ b/todoman/cli.py
@@ -95,12 +95,12 @@ def _validate_start_date_param(ctx, param, val):
         raise click.BadParameter(e)
 
 
-def _validate_today_param(ctx, param, val):
+def _validate_startable_param(ctx, param, val):
     ctx = ctx.find_object(AppContext)
     if val is not None:
         return val
     else:
-        return ctx.config['main']['today']
+        return ctx.config['main']['startable']
 
 
 def _validate_todos(ctx, param, val):
@@ -445,8 +445,8 @@ 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 '
+@click.option('--startable', default=None, is_flag=True,
+              callback=_validate_startable_param, help='Show only todos which '
               'should can be started today (eg: start time is not in the '
               'future).')
 @catch_errors
diff --git a/todoman/confspec.ini b/todoman/confspec.ini
index 0e70b15..1bd0581 100644
--- a/todoman/confspec.ini
+++ b/todoman/confspec.ini
@@ -43,8 +43,8 @@ default_due = integer(default=24)
 # ``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)
+# If set to true, only show todos which are currently startable; 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.
+startable=boolean(default=False)
diff --git a/todoman/model.py b/todoman/model.py
index 9e3bcf5..b66c677 100644
--- a/todoman/model.py
+++ b/todoman/model.py
@@ -515,12 +515,18 @@ class Cache:
             ) VALUES ({}?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
             '''
 
+        due = self._serialize_datetime(todo, 'due')
+        start = self._serialize_datetime(todo, 'dtstart')
+
+        if start and due:
+            start = None if start >= due else start
+
         params = (
             file_path,
             todo.get('uid'),
             todo.get('summary'),
-            self._serialize_datetime(todo, 'due'),
-            self._serialize_datetime(todo, 'dtstart'),
+            due,
+            start,
             todo.get('priority', 0) or None,
             self._serialize_datetime(todo, 'created'),
             self._serialize_datetime(todo, 'completed'),
@@ -549,7 +555,7 @@ class Cache:
 
     def todos(self, all=False, lists=[], priority=None, location='',
               category='', grep='', sort=[], reverse=True, due=None,
-              done_only=None, start=None, today=False):
+              done_only=None, start=None, startable=False):
         """
         Returns filtered cached todos, in a specified order.
 
@@ -623,7 +629,7 @@ class Cache:
             else:
                 extra_where.append('AND start >= ?')
                 params.append(dt)
-        if today:
+        if startable:
             extra_where.append('AND (start IS NULL OR start <= ?)')
             params.append(datetime.now().timestamp())
         if sort:


Test patch:
diff --git a/tests/test_cli.py b/tests/test_cli.py
index 31a4202..383a9cd 100644
--- a/tests/test_cli.py
+++ b/tests/test_cli.py
@@ -634,19 +634,27 @@ def test_todo_edit(runner, default_database, todo_factory):
 
 
 @freeze_time('2017, 3, 20')
-def test_list_today(tmpdir, runner, todo_factory):
+def test_list_startable(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)
+    result = runner.invoke(
+        cli,
+        ['list', '--startable'],
+        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)
+    result = runner.invoke(
+        cli,
+        ['list'],
+        catch_exceptions=False,
+    )
 
     assert not result.exception
     assert 'started' in result.output
@@ -654,7 +662,7 @@ def test_list_today(tmpdir, runner, todo_factory):
     assert 'unstarted' in result.output
 
     path = tmpdir.join('config')
-    path.write('today = yes\n', 'a')
+    path.write('startable = yes\n', 'a')
 
     result = runner.invoke(cli, ['list'], catch_exceptions=False)
 
diff --git a/tests/test_model.py b/tests/test_model.py
index 5d006bf..4c0f61d 100644
--- a/tests/test_model.py
+++ b/tests/test_model.py
@@ -269,12 +269,12 @@ def test_clone():
 
 
 @freeze_time('2017, 3, 20')
-def test_todos_today(tmpdir, runner, todo_factory, todos):
+def test_todos_startable(tmpdir, runner, todo_factory, todos):
     todo_factory(summary='started', start=datetime(2017, 3, 15))
     todo_factory(summary='nostart')
     todo_factory(summary='unstarted', start=datetime(2017, 3, 24))
 
-    todos = list(todos(today=True))
+    todos = list(todos(startable=True))
 
     assert len(todos) == 2
     for todo in todos:
@@ -302,3 +302,15 @@ def test_hide_cancelled(todos, todo_factory):
 
     assert len(list(todos())) == 0
     assert len(list(todos(all=True))) == 1
+
+
+def test_illegal_start_suppression(create, default_database, todos):
+    create(
+        'test.ics',
+        'SUMMARY:Start doing stuff\n'
+        'DUE;VALUE=DATE-TIME;TZID=CET:20170331T120000\n'
+        'DTSTART;VALUE=DATE-TIME;TZID=CET:20170331T140000\n'
+    )
+    todo = next(todos())
+    assert todo.start is None
+    assert todo.due == datetime(2017, 3, 31, 12, tzinfo=tzoffset(None, 7200))