X-Git-Url: https://git.madduck.net/etc/taskwarrior.git/blobdiff_plain/d2e4a33719c765e528de9c22e00e30de2754d8f5..88ecc250bb13b8b338a87a87d838ffe701790a87:/tasklib/task.py diff --git a/tasklib/task.py b/tasklib/task.py index 0666b0f..8a2ea7a 100644 --- a/tasklib/task.py +++ b/tasklib/task.py @@ -19,6 +19,8 @@ VERSION_2_1_0 = six.u('2.1.0') VERSION_2_2_0 = six.u('2.2.0') VERSION_2_3_0 = six.u('2.3.0') VERSION_2_4_0 = six.u('2.4.0') +VERSION_2_4_1 = six.u('2.4.1') +VERSION_2_4_2 = six.u('2.4.2') logger = logging.getLogger(__name__) local_zone = tzlocal.get_localzone() @@ -149,6 +151,24 @@ class SerializingObject(object): def normalize_modified(self, value): return self.datetime_normalizer(value) + def serialize_start(self, value): + return self.timestamp_serializer(value) + + def deserialize_start(self, value): + return self.timestamp_deserializer(value) + + def normalize_start(self, value): + return self.datetime_normalizer(value) + + def serialize_end(self, value): + return self.timestamp_serializer(value) + + def deserialize_end(self, value): + return self.timestamp_deserializer(value) + + def normalize_end(self, value): + return self.datetime_normalizer(value) + def serialize_due(self, value): return self.timestamp_serializer(value) @@ -242,7 +262,7 @@ class SerializingObject(object): def normalize_uuid(self, value): # Enforce sane UUID - if not isinstance(value, six.text_type) or value == '': + if not isinstance(value, six.string_types) or value == '': raise ValueError("UUID must be a valid non-empty string, " "not: {}".format(value)) @@ -385,7 +405,7 @@ class Task(TaskResource): pass @classmethod - def from_input(cls, input_file=sys.stdin, modify=None): + def from_input(cls, input_file=sys.stdin, modify=None, warrior=None): """ Creates a Task object, directly from the stdin, by reading one line. If modify=True, two lines are used, first line interpreted as the @@ -401,13 +421,18 @@ class Task(TaskResource): but defaults to sys.stdin. """ - # TaskWarrior instance is set to None - task = cls(None) - # Detect the hook type if not given directly name = os.path.basename(sys.argv[0]) modify = name.startswith('on-modify') if modify is None else modify + # Create the TaskWarrior instance if none passed + if warrior is None: + hook_parent_dir = os.path.dirname(os.path.dirname(sys.argv[0])) + warrior = TaskWarrior(data_location=hook_parent_dir) + + # TaskWarrior instance is set to None + task = cls(warrior) + # Load the data from the input task._load_data(json.loads(input_file.readline().strip())) @@ -527,8 +552,24 @@ class Task(TaskResource): self.warrior.execute_command([self['uuid'], 'delete']) # Refresh the status again, so that we have updated info stored + self.refresh(only_fields=['status', 'start', 'end']) + + def start(self): + if not self.saved: + raise Task.NotSaved("Task needs to be saved before it can be started") + + # Refresh, and raise exception if task is already completed/deleted self.refresh(only_fields=['status']) + if self.completed: + raise Task.CompletedTask("Cannot start a completed task") + elif self.deleted: + raise Task.DeletedTask("Deleted task cannot be started") + + self.warrior.execute_command([self['uuid'], 'start']) + + # Refresh the status again, so that we have updated info stored + self.refresh(only_fields=['status', 'start']) def done(self): if not self.saved: @@ -545,7 +586,7 @@ class Task(TaskResource): self.warrior.execute_command([self['uuid'], 'done']) # Refresh the status again, so that we have updated info stored - self.refresh(only_fields=['status']) + self.refresh(only_fields=['status', 'start', 'end']) def save(self): if self.saved and not self.modified: @@ -663,7 +704,7 @@ class TaskFilter(SerializingObject): attribute_key = key.split('.')[0] # Since this is user input, we need to normalize before we serialize - value = self._normalize(key, value) + value = self._normalize(attribute_key, value) value = self._serialize(attribute_key, value) # If we are filtering by uuid:, do not use uuid keyword @@ -840,7 +881,7 @@ class TaskWarrior(object): stdout, stderr = [x.decode('utf-8') for x in p.communicate()] if p.returncode and allow_failure: if stderr.strip(): - error_msg = stderr.strip().splitlines()[-1] + error_msg = stderr.strip() else: error_msg = stdout.strip() raise TaskWarriorException(error_msg) @@ -849,9 +890,10 @@ class TaskWarrior(object): def enforce_recurrence(self): # Run arbitrary report command which will trigger generation # of recurrent tasks. - # TODO: Make a version dependant enforcement once - # TW-1531 is handled - self.execute_command(['next'], allow_failure=False) + + # Only necessary for TW up to 2.4.1, fixed in 2.4.2. + if self.version < VERSION_2_4_2: + self.execute_command(['next'], allow_failure=False) def filter_tasks(self, filter_obj): self.enforce_recurrence()