]> git.madduck.net Git - etc/taskwarrior.git/commitdiff

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:

Initial commit
authorRob Golding <rob@robgolding.com>
Mon, 1 Apr 2013 15:50:18 +0000 (16:50 +0100)
committerRob Golding <rob@robgolding.com>
Mon, 1 Apr 2013 15:50:18 +0000 (16:50 +0100)
.gitignore [new file with mode: 0644]
LICENSE [new file with mode: 0644]
MANIFEST.in [new file with mode: 0644]
README.rst [new file with mode: 0644]
setup.py [new file with mode: 0644]
tasklib/__init__.py [new file with mode: 0644]
tasklib/task.py [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..f430d38
--- /dev/null
@@ -0,0 +1,5 @@
+*.pyc
+*~
+tasklib.egg-info
+/dist
+/build
diff --git a/LICENSE b/LICENSE
new file mode 100644 (file)
index 0000000..87b8f3e
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,24 @@
+Copyright (c) 2013, Rob Golding. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright
+      notice, this list of conditions and the following disclaimer in the
+      documentation and/or other materials provided with the distribution.
+    * Neither the name of Rob Golding, nor the names of its contributors may
+      be used to endorse or promote products derived from this software
+      without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644 (file)
index 0000000..c1a7121
--- /dev/null
@@ -0,0 +1,2 @@
+include LICENSE
+include README.md
diff --git a/README.rst b/README.rst
new file mode 100644 (file)
index 0000000..6cf60d6
--- /dev/null
@@ -0,0 +1,34 @@
+tasklib
+=======
+
+A Python library for interacting with taskwarrior_ databases.
+
+Requirements
+------------
+
+Before installing ``tasklib``, you'll need to install taskwarrior_.
+
+Installation
+------------
+
+Install via pip::
+
+    pip install tasklib
+
+Usage
+-----
+
+.. source-code:
+
+    >>> from tasklib.task import TaskWarrior, PENDING
+
+    >>> tw = TaskWarrior('/home/rob/.task')
+    >>> tasks = tw.get_tasks(status=PENDING)
+    >>> tasks
+    ['Tidy the house', 'Learn German']
+    >>> type(tasks[0])
+    <class 'tasklib.task.Task'>
+    >>> task[0].done()
+
+
+.. _taskwarrior: http://taskwarrior.org
diff --git a/setup.py b/setup.py
new file mode 100644 (file)
index 0000000..5dc5315
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,22 @@
+from setuptools import setup, find_packages
+
+setup(
+    name='tasklib',
+    version='0.1',
+    description='Python Task Warrior library',
+    long_description=open('README.md').read(),
+    author='Rob Golding',
+    author_email='rob@robgolding.com',
+    license='BSD',
+    url='https://github.com/robgolding63/tasklib',
+    download_url='https://github.com/robgolding63/tasklib/downloads',
+    packages=find_packages(),
+    include_package_data=True,
+    classifiers=[
+        'Development Status :: 3 - Alpha',
+        'License :: OSI Approved :: BSD License',
+        'Topic :: Software Development :: Libraries :: Python Modules',
+        'Programming Language :: Python',
+        'Intended Audience :: Developers',
+    ],
+)
diff --git a/tasklib/__init__.py b/tasklib/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/tasklib/task.py b/tasklib/task.py
new file mode 100644 (file)
index 0000000..f74e0d2
--- /dev/null
@@ -0,0 +1,103 @@
+import json
+import os
+import subprocess
+import tempfile
+import uuid
+
+
+PENDING = 'pending'
+
+
+class TaskWarriorException(Exception):
+    pass
+
+
+class Task(object):
+
+    def __init__(self, warrior, data={}):
+        self.warrior = warrior
+        self._data = data
+
+    def __getitem__(self, key):
+        return self._data.get(key)
+
+    def __setitem__(self, key, val):
+        self._data[key] = val
+
+    def __unicode__(self):
+        return self._data.get('description')
+
+    def regenerate_uuid(self):
+        self['uuid'] = str(uuid.uuid4())
+
+    def delete(self):
+        self.warrior.delete_task(self['uuid'])
+
+    def done(self):
+        self.warrior.complete_task(self['uuid'])
+
+    def save(self, delete_first=True):
+        if self['uuid'] and delete_first:
+            self.delete()
+        if not self['uuid'] or delete_first:
+            self.regenerate_uuid()
+        self.warrior.import_tasks([self._data])
+
+    __repr__ = __unicode__
+
+
+class TaskWarrior(object):
+
+    def __init__(self, data_location='~/.task', create=True):
+        if not os.path.exists(data_location):
+            os.makedirs(data_location)
+        self.config = {
+            'data.location': os.path.expanduser(data_location),
+        }
+
+    def _generate_command(self, command):
+        args = ['task', 'rc:/']
+        for item in self.config.items():
+            args.append('rc.{0}={1}'.format(*item))
+        args.append(command)
+        return ' '.join(args)
+
+    def _execute(self, command):
+        p = subprocess.Popen(self._generate_command(command), shell=True,
+                             stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+        stdout, stderr = p.communicate()
+        if p.returncode:
+            raise TaskWarriorException(stderr.strip())
+        return stdout.strip().split('\n')
+
+    def get_tasks(self, project=None, status=PENDING):
+        command = 'export status:{0}'.format(status)
+        if project is not None:
+            command += ' project:{0}'.format(project)
+        tasks = []
+        for line in self._execute(command):
+            if line:
+                tasks.append(Task(self, json.loads(line.strip(','))))
+        return tasks
+
+    def get_task(self, task_id):
+        command = '{0} export'.format(task_id)
+        return Task(self, json.loads(self._execute(command)[0]))
+
+    def add_task(self, description, project=None):
+        args = ['add', description]
+        if project is not None:
+            args.append('project:{0}'.format(project))
+        self._execute(' '.join(args))
+
+    def delete_task(self, task_id):
+        self._execute('{0} rc.confirmation:no delete'.format(task_id))
+
+    def complete_task(self, task_id):
+        self._execute('{0} done'.format(task_id))
+
+    def import_tasks(self, tasks):
+        fd, path = tempfile.mkstemp()
+        with open(path, 'w') as f:
+            f.write(json.dumps(tasks))
+        self._execute('import {0}'.format(path))