From: Rob Golding Date: Mon, 1 Apr 2013 15:50:18 +0000 (+0100) Subject: Initial commit X-Git-Url: https://git.madduck.net/etc/taskwarrior.git/commitdiff_plain/e17985e0d4a7ce2eb83c580c71e2f7c1af7f093b?ds=sidebyside Initial commit --- e17985e0d4a7ce2eb83c580c71e2f7c1af7f093b diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f430d38 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +*.pyc +*~ +tasklib.egg-info +/dist +/build diff --git a/LICENSE b/LICENSE new file mode 100644 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 index 0000000..c1a7121 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,2 @@ +include LICENSE +include README.md diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..6cf60d6 --- /dev/null +++ b/README.rst @@ -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]) + + >>> task[0].done() + + +.. _taskwarrior: http://taskwarrior.org diff --git a/setup.py b/setup.py new file mode 100644 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 index 0000000..e69de29 diff --git a/tasklib/task.py b/tasklib/task.py new file mode 100644 index 0000000..f74e0d2 --- /dev/null +++ b/tasklib/task.py @@ -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))