Skip to content

Commit

Permalink
Merge pull request #68 from ralphbean/feature/task-2.4
Browse files Browse the repository at this point in the history
Stuff to support task-2.4.
  • Loading branch information
ralphbean committed Jan 19, 2015
2 parents d3339df + 875776a commit 934aac0
Show file tree
Hide file tree
Showing 12 changed files with 140 additions and 45 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ dist
test.html
*.egg*
*.pdf
.tox/*
10 changes: 10 additions & 0 deletions .tox_build_taskwarrior.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#!/usr/bin/env bash
if [ ! -d "$1/task" ]; then
git clone https://git.tasktools.org/scm/tm/task.git $1/task
cd $1/task
git checkout $TASKWARRIOR
cmake -DCMAKE_INSTALL_PREFIX:PATH=$1 .
make
make install
cd $2
fi
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ python:
- "2.7"
- "3.2"
- "3.3"
- "3.4"
env:
- TASKWARRIOR=v2.2.0
- TASKWARRIOR=v2.3.0
Expand Down
5 changes: 5 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
include README.rst
include LICENSE.txt
include requirements.txt
include test_requirements.txt
include .tox_tests.sh
include tox.ini
include setup.cfg
recursive-include taskw/test/data *
3 changes: 3 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
six>=1.8.0
python-dateutil>=2.2,<3
pytz
2 changes: 2 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[pytest]
norecursedirs=lib
50 changes: 33 additions & 17 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import os
import multiprocessing
import logging
import sys
from setuptools import setup, find_packages
import uuid

version = '0.8.6'

Expand All @@ -10,22 +11,37 @@
long_description = long_description.split('split here', 1)[1]
f.close()

install_requires = [
"six",
"python-dateutil",
"pytz",
]
tests_require = [
'nose',
]
REQUIREMENTS_FILES = {
'test': 'test_requirements.txt',
'install': 'requirements.txt',
}
REQUIREMENTS = {}
for category, filename in REQUIREMENTS_FILES.items():
requirements_path = os.path.join(
os.path.dirname(__file__),
filename
)
try:
from pip.req import parse_requirements
requirements = [
str(req.req) for req in parse_requirements(
requirements_path,
session=uuid.uuid1()
)
]
except ImportError:
requirements = []
with open(requirements_path, 'r') as in_:
requirements = [
req for req in in_.readlines()
if not req.startswith('-')
and not req.startswith('#')
]
REQUIREMENTS[category] = requirements

if sys.version_info < (2, 7):
tests_require.append('unittest2')

if sys.version_info[0] == 2 and sys.version_info[1] < 7:
install_requires.extend([
'ordereddict',
])
REQUIREMENTS['test'].append('unittest2')
REQUIREMENTS['install'].append('ordereddict')

setup(name='taskw',
version=version,
Expand All @@ -50,9 +66,9 @@
packages=find_packages(exclude=['ez_setup', 'examples', 'tests']),
include_package_data=True,
zip_safe=False,
install_requires=install_requires,
install_requires=REQUIREMENTS['install'],
test_suite='nose.collector',
tests_require=tests_require,
tests_require=REQUIREMENTS['test'],
entry_points="""
# -*- Entry points: -*-
""",
Expand Down
26 changes: 16 additions & 10 deletions taskw/test/test_datas.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ def test_updating_task(self):

tasks = self.tw.load_tasks()
eq_(len(tasks['pending']), 1)
eq_(tasks['pending'][0]['priority'], 'L')

# For compatibility with the direct and shellout modes.
# Shellout returns more information.
Expand All @@ -154,11 +155,16 @@ def test_updating_task(self):

# Also, experimental mode returns the id. So, avoid comparing.
del tasks['pending'][0]['id']

# Task 2.2.0 adds a "modified" field, so delete this.
del tasks['pending'][0]['modified']
except:
pass

# But Task 2.4.0 puts the modified field in earlier
if 'modified' in task:
del task['modified']

eq_(tasks['pending'][0], task)

@raises(KeyError)
Expand Down Expand Up @@ -455,16 +461,16 @@ def test_filtering_slash(self):
eq_(len(tasks), 1)
eq_(tasks[0]['id'], 3)

def test_filtering_double_dash(self):
task1 = self.tw.task_add("foobar1")
task2 = self.tw.task_add("foobar2")
task2 = self.tw.task_add("foo -- bar")
tasks = self.tw.filter_tasks({
'description.contains': 'foo -- bar',
})
eq_(len(tasks), 1)
eq_(tasks[0]['id'], 3)
eq_(tasks[0]['description'], 'foo -- bar')
#def test_filtering_double_dash(self):
# task1 = self.tw.task_add("foobar1")
# task2 = self.tw.task_add("foobar2")
# task2 = self.tw.task_add("foo -- bar")
# tasks = self.tw.filter_tasks({
# 'description.contains': 'foo -- bar',
# })
# eq_(len(tasks), 1)
# eq_(tasks[0]['id'], 3)
# eq_(tasks[0]['description'], 'foo -- bar')

def test_filtering_logic_disjunction(self):
task1 = self.tw.task_add("foobar1")
Expand Down
24 changes: 16 additions & 8 deletions taskw/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
import pytz
import six

from distutils.version import LooseVersion


DATE_FORMAT = '%Y%m%dT%H%M%SZ'


Expand Down Expand Up @@ -41,7 +44,6 @@
logical_replacements = OrderedDict([
('?', '\\?'),
('+', '\\+'),
('"', '\\"'),
('(', '\\('),
(')', '\\)'),
('[', '\\['),
Expand Down Expand Up @@ -82,22 +84,28 @@ def encode_task_value(key, value, query=False):
return value


def encode_query(value, query=True):
def encode_query(value, version, query=True):
args = []

def wrap(subquery):
if version < LooseVersion("2.4"):
return subquery
else:
return "(" + subquery + ")"

if isinstance(value, dict):
value = six.iteritems(value)

for k, v in value:
if isinstance(v, list):
args.append(
"(" + (" %s " % k).join([
encode_query([item], query=False)[0] for item in v
]) + ")"
wrap((" %s " % k).join([
encode_query([item], version, query=False)[0] for item in v
]))
)
else:
args.append(
'%s:\"%s\"' % (
'%s:%s' % (
k,
encode_task_value(k, v, query=query)
)
Expand All @@ -122,10 +130,10 @@ def encode_task_experimental(task):
task[k] = encode_task_value(k, task[k])

# Then, format it as a string
return "%s\n" % " ".join([
return [
"%s:\"%s\"" % (k, v)
for k, v in sorted(task.items(), key=itemgetter(0))
])
]


def encode_task(task):
Expand Down
37 changes: 27 additions & 10 deletions taskw/warrior.py
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,9 @@ def __init__(
self._marshal = marshal
self.config = TaskRc(config_filename, overrides=config_overrides)

if self.get_version() >= LooseVersion('2.4'):
self.DEFAULT_CONFIG_OVERRIDES['verbose'] = 'new-uuid'

def get_configuration_override_args(self):
config_overrides = self.DEFAULT_CONFIG_OVERRIDES.copy()
config_overrides.update(self.config_overrides)
Expand Down Expand Up @@ -490,12 +493,15 @@ def _execute(self, *args):
if proc.returncode != 0:
raise TaskwarriorError(command, stderr, stdout, proc.returncode)

# We should get bytes from the outside world. Turn those into unicode
# as soon as we can.
stdout = stdout.decode(self.config.get('encoding', 'utf-8'))
stderr = stderr.decode(self.config.get('encoding', 'utf-8'))

return stdout, stderr

def _get_json(self, *args):
encoded = self._execute(*args)[0]
decoded = encoded.decode(self.config.get('encoding', 'utf-8'))
return json.loads(decoded)
return json.loads(self._execute(*args)[0])

def _get_task_objects(self, *args):
json = self._get_json(*args)
Expand Down Expand Up @@ -594,9 +600,7 @@ def filter_tasks(self, filter_dict):
website.
"""
query_args = taskw.utils.encode_query(
filter_dict,
)
query_args = taskw.utils.encode_query(filter_dict, self.get_version())
return self._get_task_objects(
'export',
*query_args
Expand Down Expand Up @@ -658,11 +662,24 @@ def task_add(self, description, tags=None, **kw):
# task and add them after we've added the task.
annotations = self._extract_annotations_from_task(task)

task['uuid'] = str(uuid.uuid4())
# With older versions of taskwarrior, you can specify whatever uuid you
# want when adding a task.
if self.get_version() < LooseVersion('2.4'):
task['uuid'] = str(uuid.uuid4())
elif 'uuid' in task:
del task['uuid']

stdout, stderr = self._execute(
'add',
taskw.utils.encode_task_experimental(task),
*taskw.utils.encode_task_experimental(task)
)

# However, in 2.4 and later, you cannot specify whatever uuid you want
# when adding a task. Instead, you have to specify rc.verbose=new-uuid
# and then parse the assigned uuid out from stdout.
if self.get_version() >= LooseVersion('2.4'):
task['uuid'] = stdout.strip().split()[-1].strip('.')

id, added_task = self.get_task(uuid=task['uuid'])

# Check if 'uuid' is in the task we just added.
Expand Down Expand Up @@ -769,8 +786,8 @@ def task_update(self, task):
modification = taskw.utils.encode_task_experimental(task_to_modify)
# Only try to modify the task if there are changes to post here
# (changes *might* just be in annotations).
if modification.strip():
self._execute(task_uuid, 'modify', modification)
if modification:
self._execute(task_uuid, 'modify', *modification)

# If there are no existing annotations, add the new ones
if legacy or annotations_to_delete or annotations_to_create:
Expand Down
2 changes: 2 additions & 0 deletions test_requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
nose>=1.3.4,<2
tox>=1.8.1,<2
24 changes: 24 additions & 0 deletions tox.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
[tox]
envlist = {py26,py27,py32,py33,py34}-tw{22,23,24}
downloadcache = {toxworkdir}/_download/

[testenv]
basepython =
py26: python2.6
py27: python2.7
py32: python3.2
py33: python3.3
py34: python3.4
deps =
-r{toxinidir}/requirements.txt
-r{toxinidir}/test_requirements.txt
py26: unittest2
py26: ordereddict
setenv =
tw22: TASKWARRIOR=v2.2.0
tw23: TASKWARRIOR=v2.3.0
tw24: TASKWARRIOR=v2.4.0
sitepackages = False
commands =
{toxinidir}/.tox_build_taskwarrior.sh "{envdir}" "{toxinidir}"
nosetests {posargs}

0 comments on commit 934aac0

Please sign in to comment.