]>
git.madduck.net Git - etc/taskwarrior.git/blobdiff - tasklib/task.py
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:
import logging
import os
import pytz
import logging
import os
import pytz
import six
import sys
import subprocess
import six
import sys
import subprocess
return ','.join(task['uuid'] for task in value)
def deserialize_depends(self, raw_uuids):
return ','.join(task['uuid'] for task in value)
def deserialize_depends(self, raw_uuids):
- raw_uuids = raw_uuids or '' # Convert None to empty string
- uuids = raw_uuids.split(',')
+ raw_uuids = raw_uuids or [] # Convert None to empty list
+
+ # TW 2.4.4 encodes list of dependencies as a single string
+ if type(raw_uuids) is not list:
+ uuids = raw_uuids.split(',')
+ # TW 2.4.5 and later exports them as a list, no conversion needed
+ else:
+ uuids = raw_uuids
+
return set(self.warrior.tasks.get(uuid=uuid) for uuid in uuids if uuid)
def datetime_normalizer(self, value):
return set(self.warrior.tasks.get(uuid=uuid) for uuid in uuids if uuid)
def datetime_normalizer(self, value):
# are not propagated.
self._original_data = copy.deepcopy(self._data)
# are not propagated.
self._original_data = copy.deepcopy(self._data)
- def _update_data(self, data, update_original=False):
+ def _update_data(self, data, update_original=False, remove_missing=False ):
"""
Low level update of the internal _data dict. Data which are coming as
updates should already be serialized. If update_original is True, the
"""
Low level update of the internal _data dict. Data which are coming as
updates should already be serialized. If update_original is True, the
self._data.update(dict((key, self._deserialize(key, value))
for key, value in data.items()))
self._data.update(dict((key, self._deserialize(key, value))
for key, value in data.items()))
+ # In certain situations, we want to treat missing keys as removals
+ if remove_missing:
+ for key in set(self._data.keys()) - set(data.keys()):
+ self._data[key] = None
+
if update_original:
self._original_data = copy.deepcopy(self._data)
if update_original:
self._original_data = copy.deepcopy(self._data)
+ class ActiveTask(Exception):
+ """
+ Raised when the operation cannot be performed on the active task.
+ """
+ pass
+
class InactiveTask(Exception):
"""
Raised when the operation cannot be performed on an inactive task.
class InactiveTask(Exception):
"""
Raised when the operation cannot be performed on an inactive task.
# If this is a on-modify event, we are provided with additional
# line of input, which provides updated data
if modify:
# If this is a on-modify event, we are provided with additional
# line of input, which provides updated data
if modify:
- task._update_data(json.loads(input_file.readline().strip()))
+ task._update_data(json.loads(input_file.readline().strip()),
+ remove_missing=True)
raise Task.CompletedTask("Cannot start a completed task")
elif self.deleted:
raise Task.DeletedTask("Deleted task cannot be started")
raise Task.CompletedTask("Cannot start a completed task")
elif self.deleted:
raise Task.DeletedTask("Deleted task cannot be started")
+ elif self.active:
+ raise Task.ActiveTask("Task is already active")
self.warrior.execute_command([self['uuid'], 'start'])
self.warrior.execute_command([self['uuid'], 'start'])
elif self.deleted:
raise Task.DeletedTask("Deleted task cannot be completed")
elif self.deleted:
raise Task.DeletedTask("Deleted task cannot be completed")
+ # Older versions of TW do not stop active task at completion
+ if self.warrior.version < VERSION_2_4_0 and self.active:
+ self.stop()
+
self.warrior.execute_command([self['uuid'], 'done'])
# Refresh the status again, so that we have updated info stored
self.warrior.execute_command([self['uuid'], 'done'])
# Refresh the status again, so that we have updated info stored
"task: %s" % '\n'.join(id_lines))
# Circumvent the ID storage, since ID is considered read-only
"task: %s" % '\n'.join(id_lines))
# Circumvent the ID storage, since ID is considered read-only
- self._data['id'] = int(id_lines[0].split(' ')[2].rstrip('.'))
+ identifier = id_lines[0].split(' ')[2].rstrip('.')
+
+ # Identifier can be either ID or UUID for completed tasks
+ try:
+ self._data['id'] = int(identifier)
+ except ValueError:
+ self._data['uuid'] = identifier
# Refreshing is very important here, as not only modification time
# is updated, but arbitrary attribute may have changed due hooks
# Refreshing is very important here, as not only modification time
# is updated, but arbitrary attribute may have changed due hooks
'confirmation': 'no',
'dependency.confirmation': 'no', # See TW-1483 or taskrc man page
'recurrence.confirmation': 'no', # Necessary for modifying R tasks
'confirmation': 'no',
'dependency.confirmation': 'no', # See TW-1483 or taskrc man page
'recurrence.confirmation': 'no', # Necessary for modifying R tasks
+
+ # Defaults to on since 2.4.5, we expect off during parsing
+ 'json.array': 'off',
+
# 2.4.3 onwards supports 0 as infite bulk, otherwise set just
# arbitrary big number which is likely to be large enough
'bulk': 0 if self.version >= VERSION_2_4_3 else 100000,
# 2.4.3 onwards supports 0 as infite bulk, otherwise set just
# arbitrary big number which is likely to be large enough
'bulk': 0 if self.version >= VERSION_2_4_3 else 100000,
stdout, stderr = [x.decode('utf-8') for x in p.communicate()]
return stdout.strip('\n')
stdout, stderr = [x.decode('utf-8') for x in p.communicate()]
return stdout.strip('\n')
+ def get_config(self):
+ raw_output = self.execute_command(
+ ['show'],
+ config_override={'verbose': 'nothing'}
+ )
+
+ config = dict()
+ config_regex = re.compile(r'^(?P<key>[^\s]+)\s+(?P<value>[^\s].+$)')
+
+ for line in raw_output:
+ match = config_regex.match(line)
+ if match:
+ config[match.group('key')] = match.group('value').strip()
+
+ return config
+
def execute_command(self, args, config_override={}, allow_failure=True,
return_all=False):
command_args = self._get_command_args(
def execute_command(self, args, config_override={}, allow_failure=True,
return_all=False):
command_args = self._get_command_args(