]> git.madduck.net Git - etc/taskwarrior.git/blobdiff - docs/index.rst

madduck's git repository

Every one of the projects in this repository is available at the canonical URL git://git.madduck.net/madduck/pub/<projectpath> — see each project's metadata for the exact URL.

All patches and comments are welcome. Please squash your changes to logical commits before using git-format-patch and git-send-email to patches@git.madduck.net. If you'd read over the Git project's submission guidelines and adhered to them, I'd be especially grateful.

SSH access, as well as push access can be individually arranged.

If you use my repositories frequently, consider adding the following snippet to ~/.gitconfig and using the third clone URL listed for each project:

[url "git://git.madduck.net/madduck/"]
  insteadOf = madduck:

tests: Add tests for Task active property
[etc/taskwarrior.git] / docs / index.rst
index 44ef46949b7dc7698800aada024e18745c715371..dfddeb02a812e77bf945bed1199a1cb992f87c56 100644 (file)
@@ -36,6 +36,11 @@ The default location is the same as taskwarrior's::
 
     >>> tw = TaskWarrior(data_location='~/.task', create=True)
 
 
     >>> tw = TaskWarrior(data_location='~/.task', create=True)
 
+The ``TaskWarrior`` instance will also use your .taskrc configuration (so that
+it recognizes the same UDAs as your task binary, uses the same configuration,
+etc.). To override the location of the .taskrc, use
+``taskrc_location=~/some/different/path``.
+
 Creating Tasks
 --------------
 
 Creating Tasks
 --------------
 
@@ -84,7 +89,7 @@ Attributes of task objects are accessible through indices, like so::
     >>> task['id']
     15
     >>> task['due']
     >>> task['id']
     15
     >>> task['due']
-    datetime.datetime(2013, 12, 5, 0, 0)
+    datetime.datetime(2015, 2, 5, 0, 0, tzinfo=<DstTzInfo 'Europe/Berlin' CET+1:00:00 STD>)
     >>> task['tags']
     ['work', 'servers']
 
     >>> task['tags']
     ['work', 'servers']
 
@@ -239,6 +244,105 @@ same Python object::
     >>> task3 == task1
     True
 
     >>> task3 == task1
     True
 
+Accessing original values
+-------------------------
+
+To access the saved state of the Task, use dict-like access using the
+``original`` attribute:
+
+    >>> t = Task(tw, description="tidy up")
+    >>> t.save()
+    >>> t['description'] = "tidy up the kitchen and bathroom"
+    >>> t['description']
+    "tidy up the kitchen and bathroom"
+    >>> t.original['description']
+    "tidy up"
+
+When you save the task, original values are refreshed to reflect the
+saved state of the task:
+
+    >>> t.save()
+    >>> t.original['description']
+    "tidy up the kitchen and bathroom"
+
+Dealing with dates and time
+---------------------------
+
+Any timestamp-like attributes of the tasks are converted to timezone-aware
+datetime objects. To achieve this, Tasklib leverages ``pytz`` Python module,
+which brings the Olsen timezone databaze to Python.
+
+This shields you from annoying details of Daylight Saving Time shifts
+or conversion between different timezones. For example, to list all the
+tasks which are due midnight if you're currently in Berlin:
+
+    >>> myzone = pytz.timezone('Europe/Berlin')
+    >>> midnight = myzone.localize(datetime(2015,2,2,0,0,0))
+    >>> tw.tasks.filter(due__before=midnight)
+
+However, this is still a little bit tedious. That's why TaskWarrior object
+is capable of automatic timezone detection, using the ``tzlocal`` Python
+module. If your system timezone is set to 'Europe/Berlin', following example
+will work the same way as the previous one:
+
+    >>> tw.tasks.filter(due__before=datetime(2015,2,2,0,0,0))
+
+You can also use simple dates when filtering:
+
+    >>> tw.tasks.filter(due__before=date(2015,2,2))
+
+In such case, a 00:00:00 is used as the time component.
+
+Of course, you can use datetime naive objects when initializing Task object
+or assigning values to datetime atrributes:
+
+    >>> t = Task(tw, description="Buy new shoes", due=date(2015,2,5))
+    >>> t['due']
+    datetime.datetime(2015, 2, 5, 0, 0, tzinfo=<DstTzInfo 'Europe/Berlin' CET+1:00:00 STD>)
+    >>> t['due'] = date(2015,2,6,15,15,15)
+    >>> t['due']
+    datetime.datetime(2015, 2, 6, 15, 15, 15, tzinfo=<DstTzInfo 'Europe/Berlin' CET+1:00:00 STD>)
+
+However, since timezone-aware and timezone-naive datetimes are not comparable
+in Python, this can cause some unexpected behaviour:
+
+    >>> from datetime import datetime
+    >>> now = datetime.now()
+    >>> t = Task(tw, description="take out the trash now") 
+    >>> t['due'] = now
+    >>> now
+    datetime.datetime(2015, 2, 1, 19, 44, 4, 770001)
+    >>> t['due']
+    datetime.datetime(2015, 2, 1, 19, 44, 4, 770001, tzinfo=<DstTzInfo 'Europe/Berlin' CET+1:00:00 STD>)
+    >>> t['due'] == now
+    Traceback (most recent call last):
+      File "<stdin>", line 1, in <module>
+      TypeError: can't compare offset-naive and offset-aware datetimes
+
+If you want to compare datetime aware value with datetime naive value, you need
+to localize the naive value first:
+
+    >>> from datetime import datetime
+    >>> from tasklib.task import local_zone
+    >>> now = local_zone.localize(datetime.now())
+    >>> t['due'] = now
+    >>> now
+    datetime.datetime(2015, 2, 1, 19, 44, 4, 770001, tzinfo=<DstTzInfo 'Europe/Berlin' CET+1:00:00 STD>)
+    >>> t['due'] == now
+    True
+
+Also, note that it does not matter whether the timezone aware datetime objects
+are set in the same timezone:
+
+    >>> import pytz
+    >>> t['due']
+    datetime.datetime(2015, 2, 1, 19, 44, 4, 770001, tzinfo=<DstTzInfo 'Europe/Berlin' CET+1:00:00 STD>)
+    >>> now.astimezone(pytz.utc)
+    datetime.datetime(2015, 2, 1, 18, 44, 4, 770001, tzinfo=<UTC>)
+    >>> t['due'] == now.astimezone(pytz.utc)
+    True
+
+
 Working with annotations
 ------------------------
 
 Working with annotations
 ------------------------
 
@@ -253,7 +357,7 @@ Annotations have only defined ``entry`` and ``description`` values::
 
     >>> annotation = annotated_task['annotations'][0]
     >>> annotation['entry']
 
     >>> annotation = annotated_task['annotations'][0]
     >>> annotation['entry']
-    datetime.datetime(2015, 1, 3, 21, 13, 55)
+    datetime.datetime(2015, 1, 3, 21, 13, 55, tzinfo=<DstTzInfo 'Europe/Berlin' CET+1:00:00 STD>)
     >>> annotation['description']
     u'Yeah, I am annotated!'
 
     >>> annotation['description']
     u'Yeah, I am annotated!'
 
@@ -298,30 +402,76 @@ You can use ``config_override`` keyword argument to specify a dictionary of conf
 Setting custom configuration values
 -----------------------------------
 
 Setting custom configuration values
 -----------------------------------
 
-By default, TaskWarrior does not use any of configuration values stored in
-your .taskrc. To see what configuration values are passed to each executed
+By default, TaskWarrior uses configuration values stored in your .taskrc.
+To see what configuration value overrides are passed to each executed
 task command, have a peek into ``config`` attribute of ``TaskWarrior`` object::
 
     >>> tw.config
     {'confirmation': 'no', 'data.location': '/home/tbabej/.task'}
 
 task command, have a peek into ``config`` attribute of ``TaskWarrior`` object::
 
     >>> tw.config
     {'confirmation': 'no', 'data.location': '/home/tbabej/.task'}
 
-To pass your own configuration, you just need to update this dictionary::
+To pass your own configuration overrides, you just need to update this dictionary::
 
     >>> tw.config.update({'hooks': 'off'})  # tasklib will not trigger hooks
 
 
     >>> tw.config.update({'hooks': 'off'})  # tasklib will not trigger hooks
 
+Creating hook scripts
+---------------------
+
+From version 2.4.0, TaskWarrior has support for hook scripts. Tasklib provides
+some very useful helpers to write those. With tasklib, writing these becomes
+a breeze::
+
+    #!/usr/bin/python
+
+    from tasklib.task import Task
+    task = Task.from_input()
+    # ... <custom logic>
+    print task.export_data()
+
+For example, plugin which would assign the priority "H" to any task containing
+three exclamation marks in the description, would go like this::
+
+    #!/usr/bin/python
+
+    from tasklib.task import Task
+    task = Task.from_input()
+
+    if "!!!" in task['description']:
+        task['priority'] = "H"
+
+    print task.export_data()
+
+Tasklib can automatically detect whether it's running in the ``on-modify`` event,
+which provides more input than ``on-add`` event and reads the data accordingly.
+
+This means the example above works both for ``on-add`` and ``on-modify`` events!
+
+Consenquently, you can create just one hook file for both ``on-add`` and
+``on-modify`` events, and you just need to create a symlink for the other one.
+This removes the need for maintaining two copies of the same code base and/or
+boilerplate code.
+
+In ``on-modify`` events, tasklib loads both the original version and the modified
+version of the task to the returned ``Task`` object. To access the original data
+(in read-only manner), use ``original`` dict-like attribute:
+
+    >>> t = Task.from_input()
+    >>> t['description']
+    "Modified description"
+    >>> t.original['description']
+    "Original description"
+
 Working with UDAs
 -----------------
 
 Working with UDAs
 -----------------
 
-Since TaskWarrior does not read your .taskrc, you need to define any UDAs
-in the TaskWarrior's config dictionary, as described above.
-
-Let us demonstrate this on the same example as in the TaskWarrior's docs::
+Since TaskWarrior does read your .taskrc, you need not to define any UDAs
+in the TaskWarrior's config dictionary, as described above. Suppose we have
+a estimate UDA in the .taskrc::
 
 
-    >>> tw = TaskWarrior()
-    >>> tw.config.update({'uda.estimate.type': 'numeric'})
+    uda.estimate.type = numeric
 
 
-Now we can filter and create tasks using the estimate UDA::
+We can simply filter and create tasks using the estimate UDA out of the box::
 
 
+    >>> tw = TaskWarrior()
     >>> task = Task(tw, description="Long task", estimate=1000)
     >>> task.save()
     >>> task['id']
     >>> task = Task(tw, description="Long task", estimate=1000)
     >>> task.save()
     >>> task['id']
@@ -332,7 +482,7 @@ This is saved as UDA in the TaskWarrior::
     $ task 1 export
     {"id":1,"description":"Long task","estimate":1000, ...}
 
     $ task 1 export
     {"id":1,"description":"Long task","estimate":1000, ...}
 
-As long as ``TaskWarrior``'s config is updated, we can approach UDAs as built in attributes::
+We can also speficy UDAs as arguments in the TaskFilter::
 
     >>> tw.tasks.filter(estimate=1000)
     Long task
 
     >>> tw.tasks.filter(estimate=1000)
     Long task
@@ -340,9 +490,16 @@ As long as ``TaskWarrior``'s config is updated, we can approach UDAs as built in
 Syncing
 -------
 
 Syncing
 -------
 
-Syncing is not directly supported by tasklib, but it can be made to work in a similiar way
-as the UDAs. First we need to update the ``config`` dictionary by the values required for
-sync to work, and then we can run the sync command using the ``execute_command()`` method::
+If you have configurated the needed config variables in your .taskrc, syncing
+is as easy as::
+
+    >>> tw = TaskWarrior()
+    >>> tw.execute_command(['sync'])
+
+If you want to use non-standard server/credentials, you'll need to provide configuration
+overrides to the ``TaskWarrior`` instance. Update the ``config`` dictionary with the
+values you desire to override, and then we can run the sync command using
+the ``execute_command()`` method::
 
     >>> tw = TaskWarrior()
     >>> sync_config = {
 
     >>> tw = TaskWarrior()
     >>> sync_config = {