Example #1
0
 def __init__(
     self,
     config_filename="~/.taskrc",
     config_overrides=None,
     marshal=False,
 ):
     super(TaskWarriorShellout, self).__init__(config_filename)
     self.config_overrides = config_overrides if config_overrides else {}
     self._marshal = marshal
     self.config = TaskRc(config_filename, overrides=config_overrides)
Example #2
0
    def __init__(
        self,
        config_filename=TASKRC,
        config_overrides=None,
        marshal=False,
    ):
        super(TaskWarriorShellout, self).__init__(config_filename)
        self.config_overrides = config_overrides if config_overrides else {}
        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'
Example #3
0
    def __init__(self, config_filename=TASKRC, config_overrides=None, marshal=False):
        super(TaskWarriorShellout, self).__init__(config_filename)
        self.config_overrides = config_overrides if config_overrides else {}
        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"
Example #4
0
    def load_config(cls, config_filename=TASKRC, overrides=None):
        """ Load ~/.taskrc into a python dict

        >>> config = TaskWarrior.load_config()
        >>> config['data']['location']
        '/home/threebean/.task'
        >>> config['_forcecolor']
        'yes'

        """
        return TaskRc(config_filename, overrides=overrides)
Example #5
0
    def test_config_overrides(self):
        overrides = {
            'uda': {
                'd': {
                    'type': 'string',
                    'label': 'Delta',
                }
            },
            'alpha': {
                'two': '3',
            }
        }

        taskrc = TaskRc(self.path_to_taskrc, overrides=overrides)

        expected_config = {
            'data': {
                'location': '~/.task'
            },
            'alpha': {
                'one': 'yes',
                'two': '3',
            },
            'beta': {
                'one': 'FALSE',
            },
            'gamma': {
                'one': 'TRUE',
            },
            'uda': {
                'a': {
                    'type': 'numeric',
                    'label': 'Alpha',
                },
                'b': {
                    'type': 'string',
                    'label': 'Beta',
                    'values': 'Strontium-90,Hydrogen-3',
                },
                'd': {
                    'type': 'string',
                    'label': 'Delta',
                }
            }
        }

        self.assertEqual(taskrc, expected_config)
Example #6
0
class TaskWarriorShellout(TaskWarriorBase):
    """ Interacts with taskwarrior by invoking shell commands.

    This is currently the supported version and should be considered stable.

    See https://github.com/ralphbean/taskw/pull/15 for discussion
    and https://github.com/ralphbean/taskw/issues/30 for more.
    """

    DEFAULT_CONFIG_OVERRIDES = {
        "json": {"array": "TRUE"},
        "verbose": "nothing",
        "confirmation": "no",
        "dependency": {"confirmation": "no"},
    }

    def __init__(self, config_filename=TASKRC, config_overrides=None, marshal=False):
        super(TaskWarriorShellout, self).__init__(config_filename)
        self.config_overrides = config_overrides if config_overrides else {}
        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)
        return taskw.utils.convert_dict_to_override_args(config_overrides)

    def _execute(self, *args):
        """ Execute a given taskwarrior command with arguments

        Returns a 2-tuple of stdout and stderr (respectively).

        """
        command = (
            ["task", "rc:%s" % self.config_filename]
            + self.get_configuration_override_args()
            + [six.text_type(arg) for arg in args]
        )

        # subprocess is expecting bytestrings only, so nuke unicode if present
        for i in range(len(command)):
            if isinstance(command[i], six.text_type):
                command[i] = command[i].encode("utf-8")

        try:
            proc = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
            stdout, stderr = proc.communicate()
        except OSError as e:
            if "No such file or directory" in e:
                raise OSError("Unable to find the 'task' command-line tool.")
            raise

        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):
        return json.loads(self._execute(*args)[0])

    def _get_task_objects(self, *args):
        json = self._get_json(*args)
        if isinstance(json, dict):
            return self._get_task_object(json)
        value = [self._get_task_object(j) for j in json]
        return value

    def _get_task_object(self, obj):
        if self._marshal:
            return Task(obj, udas=self.config.get_udas())
        return obj

    def _stub_task(self, description, tags=None, **kw):
        """ Given a description, stub out a task dict. """

        # If whitespace is not removed here, TW will do it when we pass the
        # task to it.
        task = {"description": description.strip()}

        # Allow passing "tags" in as part of kw.
        if "tags" in kw and tags is None:
            task["tags"] = tags
            del (kw["tags"])

        if tags is not None:
            task["tags"] = tags

        task.update(kw)

        if self._marshal:
            return Task.from_stub(task, udas=self.config.get_udas())

        return task

    @classmethod
    def can_use(cls):
        """ Returns true if runtime requirements of experimental mode are met
        """
        try:
            return cls.get_version() > LooseVersion("2")
        except OSError:
            # OSError is raised if subprocess.Popen fails to find
            # the executable.
            return False

    @classmethod
    def get_version(cls):
        try:
            taskwarrior_version = subprocess.Popen(["task", "--version"], stdout=subprocess.PIPE).communicate()[0]
        except OSError as e:
            if "No such file or directory" in e:
                raise OSError("Unable to find the 'task' command-line tool.")
            raise
        return LooseVersion(taskwarrior_version.decode())

    def sync(self, init=False):
        if self.get_version() < LooseVersion("2.3"):
            raise UnsupportedVersionException("'sync' requires version 2.3 of taskwarrior or later.")
        if init is True:
            self._execute("sync", "init")
        else:
            self._execute("sync")

    def load_tasks(self, command="all"):
        """ Returns a dictionary of tasks for a list of command."""

        results = dict((db, self._get_task_objects("status:%s" % db, "export")) for db in Command.files(command))

        # 'waiting' tasks are returned separately from 'pending' tasks
        # Here we merge the waiting list back into the pending list.
        if "pending" in results:
            results["pending"].extend(self._get_task_objects("status:waiting", "export"))

        return results

    def filter_tasks(self, filter_dict):
        """ Return a filtered list of tasks from taskwarrior.

        Filter dict should be a dictionary mapping filter constraints
        with their values.  For example, to return only pending tasks,
        you could use::

            {'status': 'pending'}

        Or, to return tasks that have the word "Abjad" in their description
        that are also pending::

            {
                'status': 'pending',
                'description.contains': 'Abjad',
            }

        Filters can be quite complex, and are documented on Taskwarrior's
        website.

        """
        query_args = taskw.utils.encode_query(filter_dict, self.get_version())
        return self._get_task_objects("export", *query_args)

    def get_task(self, **kw):
        task = dict()
        task_id = None
        task_id, task = self._load_task(**kw)
        id = None

        # The ID going back only makes sense if the task is pending.
        if "status" in task:
            if Status.is_pending(task["status"]):
                id = task_id

        return id, task

    def _load_task(self, **kwargs):
        if len(kwargs) > 1:
            raise KeyError("Only one keyword argument may be specified")

        search = []
        for key, value in six.iteritems(kwargs):
            if key not in ["id", "uuid", "description"]:
                search.append("%s:%s" % (key, value))
            elif key == "description" and "(bw)" in value:
                search.append(value[4:])
            else:
                search = [value]

        task = self._get_task_objects("export", *search)

        if task:
            if isinstance(task, list):
                # Multiple items returned from search, return just the 1st
                task = task[0]
            return task["id"], task

        return None, dict()

    def task_add(self, description, tags=None, **kw):
        """ Add a new task.

        Takes any of the keywords allowed by taskwarrior like proj or prior.
        """
        task = self._stub_task(description, tags, **kw)

        # Check if there are annotations, if so remove them from the
        # task and add them after we've added the task.
        annotations = self._extract_annotations_from_task(task)

        # 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"]

        if self._marshal:
            args = taskw.utils.encode_task_experimental(task.serialized())
        else:
            args = taskw.utils.encode_task_experimental(task)

        stdout, stderr = self._execute("add", *args)

        # 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.
        if not "uuid" in added_task:
            raise KeyError("Error encountered while creating task;" "STDOUT: %s; STDERR: %s" % (stdout, stderr))

        if annotations and "uuid" in added_task:
            for annotation in annotations:
                self.task_annotate(added_task, annotation)

        id, added_task = self.get_task(uuid=added_task[six.u("uuid")])
        return added_task

    def task_annotate(self, task, annotation):
        """ Annotates a task. """
        self._execute(task["uuid"], "annotate", "--", annotation)
        id, annotated_task = self.get_task(uuid=task[six.u("uuid")])
        return annotated_task

    def task_denotate(self, task, annotation):
        """ Removes an annotation from a task. """
        self._execute(task["uuid"], "denotate", "--", annotation)
        id, denotated_task = self.get_task(uuid=task[six.u("uuid")])
        return denotated_task

    def task_done(self, **kw):
        if not kw:
            raise KeyError("No key was passed.")

        id, task = self.get_task(**kw)

        if not Status.is_pending(task["status"]):
            raise ValueError("Task is not pending.")

        self._execute(id, "done")
        return self.get_task(uuid=task["uuid"])[1]

    def task_update(self, task):
        if "uuid" not in task:
            raise KeyError("Task must have a UUID.")
        # 'Legacy' causes us to handle this task as if it were an
        # old-style task -- just a standard dictionary
        legacy = True

        if isinstance(task, Task):
            # Let's pre-serialize taskw.task.Task instances
            task_uuid = six.text_type(task["uuid"])
            task = task.serialized_changes(keep=True)
            legacy = False
        else:
            task_uuid = task["uuid"]

        id, original_task = self.get_task(uuid=task_uuid)

        if "id" in task:
            del task["id"]

        task_to_modify = copy.deepcopy(task)

        task_to_modify.pop("uuid", None)
        task_to_modify.pop("id", None)

        # Only handle annotation differences if this is an old-style
        # task, or if the task itself says annotations have changed.
        annotations_to_delete = set()
        annotations_to_create = set()
        if legacy or "annotations" in task_to_modify:
            # Check if there are annotations, if so, look if they are
            # in the existing task, otherwise annotate the task to add them.
            ttm_annotations = taskw.utils.annotation_list_to_comparison_map(
                self._extract_annotations_from_task(task_to_modify)
            )
            original_annotations = taskw.utils.annotation_list_to_comparison_map(
                self._extract_annotations_from_task(original_task)
            )

            new_annotations = set(ttm_annotations.keys())
            existing_annotations = set(original_annotations.keys())

            annotations_to_delete = existing_annotations - new_annotations
            annotations_to_create = new_annotations - existing_annotations

            if "annotations" in task_to_modify:
                del task_to_modify["annotations"]

        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:
            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:
            ttm_annotations.update(original_annotations)
            for annotation_key in annotations_to_create:
                self.task_annotate(original_task, ttm_annotations[annotation_key])
            for annotation_key in annotations_to_delete:
                self.task_denotate(original_task, ttm_annotations[annotation_key])

        return self.get_task(uuid=task_uuid)

    def task_delete(self, **kw):
        """ Marks a task as deleted.  """

        id, task = self.get_task(**kw)

        if task["status"] == Status.DELETED:
            raise ValueError("Task is already deleted.")

        self._execute(id, "delete")
        return self.get_task(uuid=task["uuid"])[1]

    def task_start(self, **kw):
        """ Marks a task as started.  """

        id, task = self.get_task(**kw)

        self._execute(id, "start")
        return self.get_task(uuid=task["uuid"])[1]

    def task_stop(self, **kw):
        """ Marks a task as stopped.  """

        id, task = self.get_task(**kw)

        self._execute(id, "stop")
        return self.get_task(uuid=task["uuid"])[1]

    def task_info(self, **kw):
        id, task = self.get_task(**kw)
        out, err = self._execute(id, "info")
        if err:
            return err
        return out
Example #7
0
class TaskWarriorShellout(TaskWarriorBase):
    """ Interacts with taskwarrior by invoking shell commands.

    This is currently the supported version and should be considered stable.

    See https://github.com/ralphbean/taskw/pull/15 for discussion
    and https://github.com/ralphbean/taskw/issues/30 for more.
    """
    DEFAULT_CONFIG_OVERRIDES = {
        'json': {
            'array': 'TRUE'
        },
        'verbose': 'nothing',
        'confirmation': 'no',
    }

    def __init__(
        self,
        config_filename="~/.taskrc",
        config_overrides=None,
        marshal=False,
    ):
        super(TaskWarriorShellout, self).__init__(config_filename)
        self.config_overrides = config_overrides if config_overrides else {}
        self._marshal = marshal
        self.config = TaskRc(config_filename, overrides=config_overrides)

    def get_configuration_override_args(self):
        config_overrides = self.DEFAULT_CONFIG_OVERRIDES.copy()
        config_overrides.update(self.config_overrides)
        return taskw.utils.convert_dict_to_override_args(config_overrides)

    def _execute(self, *args):
        """ Execute a given taskwarrior command with arguments

        Returns a 2-tuple of stdout and stderr (respectively).

        """
        command = (
            [
                'task',
                'rc:%s' % self.config_filename,
            ]
            + self.get_configuration_override_args()
            + [six.text_type(arg) for arg in args]
        )

        # subprocess is expecting bytestrings only, so nuke unicode if present
        for i in range(len(command)):
            if isinstance(command[i], six.text_type):
                command[i] = command[i].encode('utf-8')

        proc = subprocess.Popen(
            command,
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
        )
        stdout, stderr = proc.communicate()

        if proc.returncode != 0:
            raise TaskwarriorError(command, stderr, stdout, proc.returncode)

        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)

    def _get_task_objects(self, *args):
        json = self._get_json(*args)
        if isinstance(json, dict):
            return self._get_task_object(json)
        value = [self._get_task_object(j) for j in json]
        return value

    def _get_task_object(self, obj):
        if self._marshal:
            return Task(obj, udas=self.config.get_udas())
        return obj

    def _stub_task(self, description, tags=None, **kw):
        """ Given a description, stub out a task dict. """

        # If whitespace is not removed here, TW will do it when we pass the
        # task to it.
        task = {"description": description.strip()}

        # Allow passing "tags" in as part of kw.
        if 'tags' in kw and tags is None:
            task['tags'] = tags
            del(kw['tags'])

        if tags is not None:
            task['tags'] = tags

        task.update(kw)

        return task

    @classmethod
    def can_use(cls):
        """ Returns true if runtime requirements of experimental mode are met
        """
        try:
            return cls.get_version() > LooseVersion('2')
        except OSError:
            # OSError is raised if subprocess.Popen fails to find
            # the executable.
            return False

    @classmethod
    def get_version(cls):
        taskwarrior_version = subprocess.Popen(
            ['task', '--version'],
            stdout=subprocess.PIPE
        ).communicate()[0]
        return LooseVersion(taskwarrior_version.decode())

    def sync(self, init=False):
        if self.get_version() < LooseVersion('2.3'):
            raise UnsupportedVersionException(
                "'sync' requires version 2.3 of taskwarrior or later."
            )
        if init is True:
            self._execute('sync', 'init')
        else:
            self._execute('sync')

    def load_tasks(self, command='all'):
        """ Returns a dictionary of tasks for a list of command."""

        results = dict(
            (db, self._get_task_objects('status:%s' % db, 'export'))
            for db in Command.files(command)
        )

        # 'waiting' tasks are returned separately from 'pending' tasks
        # Here we merge the waiting list back into the pending list.
        if 'pending' in results:
            results['pending'].extend(
                self._get_task_objects('status:waiting', 'export'))

        return results

    def filter_tasks(self, filter_dict):
        """ Return a filtered list of tasks from taskwarrior.

        Filter dict should be a dictionary mapping filter constraints
        with their values.  For example, to return only pending tasks,
        you could use::

            {'status': 'pending'}

        Or, to return tasks that have the word "Abjad" in their description
        that are also pending::

            {
                'status': 'pending',
                'description.contains': 'Abjad',
            }

        Filters can be quite complex, and are documented on Taskwarrior's
        website.

        """
        query_args = taskw.utils.encode_query(
            filter_dict,
        )
        return self._get_task_objects(
            'export',
            *query_args
        )

    def get_task(self, **kw):
        task = dict()
        task_id = None
        task_id, task = self._load_task(**kw)
        id = None

        # The ID going back only makes sense if the task is pending.
        if 'status' in task:
            if Status.is_pending(task['status']):
                id = task_id

        return id, task

    def _load_task(self, **kwargs):
        if len(kwargs) > 1:
            raise KeyError(
                "Only one keyword argument may be specified"
            )

        search = []
        for key, value in six.iteritems(kwargs):
            if key not in ['id', 'uuid', 'description']:
                search.append(
                    '%s:%s' % (
                        key,
                        value,
                    )
                )
            elif key == 'description' and '(bw)' in value:
                search.append(
                    value[4:]
                )
            else:
                search = [value]

        task = self._get_task_objects('export', *search)

        if task:
            if isinstance(task, list):
                # Multiple items returned from search, return just the 1st
                task = task[0]
            return task['id'], task

        return None, dict()

    def task_add(self, description, tags=None, **kw):
        """ Add a new task.

        Takes any of the keywords allowed by taskwarrior like proj or prior.
        """
        task = self._stub_task(description, tags, **kw)

        # Check if there are annotations, if so remove them from the
        # task and add them after we've added the task.
        annotations = self._extract_annotations_from_task(task)

        task['uuid'] = str(uuid.uuid4())
        stdout, stderr = self._execute(
            'add',
            taskw.utils.encode_task_experimental(task),
        )
        id, added_task = self.get_task(uuid=task['uuid'])

        # Check if 'uuid' is in the task we just added.
        if not 'uuid' in added_task:
            raise KeyError(
                'Error encountered while creating task;'
                'STDOUT: %s; STDERR: %s' % (
                    stdout,
                    stderr,
                )
            )

        if annotations and 'uuid' in added_task:
            for annotation in annotations:
                self.task_annotate(added_task, annotation)

        id, added_task = self.get_task(uuid=added_task[six.u('uuid')])
        return added_task

    def task_annotate(self, task, annotation):
        """ Annotates a task. """
        self._execute(
            task['uuid'],
            'annotate',
            '--',
            annotation
        )
        id, annotated_task = self.get_task(uuid=task[six.u('uuid')])
        return annotated_task

    def task_denotate(self, task, annotation):
        """ Removes an annotation from a task. """
        self._execute(
            task['uuid'],
            'denotate',
            '--',
            annotation
        )
        id, denotated_task = self.get_task(uuid=task[six.u('uuid')])
        return denotated_task

    def task_done(self, **kw):
        if not kw:
            raise KeyError('No key was passed.')

        id, task = self.get_task(**kw)

        if not Status.is_pending(task['status']):
            raise ValueError("Task is not pending.")

        self._execute(id, 'done')
        return self.get_task(uuid=task['uuid'])[1]

    def task_update(self, task):
        if 'uuid' not in task:
            raise KeyError('Task must have a UUID.')
        # 'Legacy' causes us to handle this task as if it were an
        # old-style task -- just a standard dictionary
        legacy = True

        if isinstance(task, Task):
            # Let's pre-serialize taskw.task.Task instances
            task_uuid = six.text_type(task['uuid'])
            task = task.serialized_changes(keep=True)
            legacy = False
        else:
            task_uuid = task['uuid']

        id, original_task = self.get_task(uuid=task_uuid)

        if 'id' in task:
            del task['id']

        task_to_modify = copy.deepcopy(task)

        task_to_modify.pop('uuid', None)
        task_to_modify.pop('id', None)

        # Only handle annotation differences if this is an old-style
        # task, or if the task itself says annotations have changed.
        annotations_to_delete = set()
        annotations_to_create = set()
        if legacy or 'annotations' in task_to_modify:
            # Check if there are annotations, if so, look if they are
            # in the existing task, otherwise annotate the task to add them.
            ttm_annotations = taskw.utils.annotation_list_to_comparison_map(
                self._extract_annotations_from_task(task_to_modify)
            )
            original_annotations = (
                taskw.utils.annotation_list_to_comparison_map(
                    self._extract_annotations_from_task(original_task)
                )
            )

            new_annotations = set(ttm_annotations.keys())
            existing_annotations = set(original_annotations.keys())

            annotations_to_delete = existing_annotations - new_annotations
            annotations_to_create = new_annotations - existing_annotations

            if 'annotations' in task_to_modify:
                del task_to_modify['annotations']

        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 there are no existing annotations, add the new ones
        if legacy or annotations_to_delete or annotations_to_create:
            ttm_annotations.update(original_annotations)
            for annotation_key in annotations_to_create:
                self.task_annotate(
                    original_task,
                    ttm_annotations[annotation_key]
                )
            for annotation_key in annotations_to_delete:
                self.task_denotate(
                    original_task,
                    ttm_annotations[annotation_key]
                )

        return self.get_task(uuid=task_uuid)

    def task_delete(self, **kw):
        """ Marks a task as deleted.  """

        id, task = self.get_task(**kw)

        if task['status'] == Status.DELETED:
            raise ValueError("Task is already deleted.")

        self._execute(id, 'delete')
        return self.get_task(uuid=task['uuid'])[1]

    def task_start(self, **kw):
        """ Marks a task as started.  """

        id, task = self.get_task(**kw)

        self._execute(id, 'start')
        return self.get_task(uuid=task['uuid'])[1]

    def task_stop(self, **kw):
        """ Marks a task as stopped.  """

        id, task = self.get_task(**kw)

        self._execute(id, 'stop')
        return self.get_task(uuid=task['uuid'])[1]

    def task_info(self, **kw):
        id, task = self.get_task(**kw)
        out, err = self._execute(id, 'info')
        if err:
            return err
        return out
Example #8
0
 def setUp(self):
     self.path_to_taskrc = os.path.join(
         os.path.dirname(__file__),
         'data/default.taskrc',
     )
     self.taskrc = TaskRc(self.path_to_taskrc)
Example #9
0
class TestTaskRc(TestCase):
    def setUp(self):
        self.path_to_taskrc = os.path.join(
            os.path.dirname(__file__),
            'data/default.taskrc',
        )
        self.taskrc = TaskRc(self.path_to_taskrc)

    def test_taskrc_parsing(self):
        expected_config = {
            'data': {
                'location': '~/.task'
            },
            'alpha': {
                'one': 'yes',
                'two': '2',
            },
            'beta': {
                'one': 'FALSE',
            },
            'gamma': {
                'one': 'TRUE',
            },
            'uda': {
                'a': {
                    'type': 'numeric',
                    'label': 'Alpha',
                },
                'b': {
                    'type': 'string',
                    'label': 'Beta',
                    'values': 'Strontium-90,Hydrogen-3',
                }
            }
        }

        self.assertEqual(self.taskrc, expected_config)

    def test_get_udas(self):
        expected_udas = {
            'a': NumericField(label='Alpha'),
            'b': ChoiceField(
                label='Beta',
                choices=['Strontium-90', 'Hydrogen-3'],
            ),
        }
        actual_udas = self.taskrc.get_udas()

        self.assertEqual(actual_udas, expected_udas)

    def test_config_overrides(self):
        overrides = {
            'uda': {
                'd': {
                    'type': 'string',
                    'label': 'Delta',
                }
            },
            'alpha': {
                'two': '3',
            }
        }

        taskrc = TaskRc(self.path_to_taskrc, overrides=overrides)

        expected_config = {
            'data': {
                'location': '~/.task'
            },
            'alpha': {
                'one': 'yes',
                'two': '3',
            },
            'beta': {
                'one': 'FALSE',
            },
            'gamma': {
                'one': 'TRUE',
            },
            'uda': {
                'a': {
                    'type': 'numeric',
                    'label': 'Alpha',
                },
                'b': {
                    'type': 'string',
                    'label': 'Beta',
                    'values': 'Strontium-90,Hydrogen-3',
                },
                'd': {
                    'type': 'string',
                    'label': 'Delta',
                }
            }
        }

        self.assertEqual(taskrc, expected_config)
def get_taskwarrior_config(path):
    try:
        return TaskRc(path)
    except IOError:
        return {}
Example #11
0
 def setUp(self):
     self.path_to_taskrc = os.path.join(
         os.path.dirname(__file__),
         'data/default.taskrc',
     )
     self.taskrc = TaskRc(self.path_to_taskrc)
Example #12
0
class TestTaskRc(TestCase):
    def setUp(self):
        self.path_to_taskrc = os.path.join(
            os.path.dirname(__file__),
            'data/default.taskrc',
        )
        self.taskrc = TaskRc(self.path_to_taskrc)

    def test_taskrc_parsing(self):
        expected_config = {
            'data': {
                'location': '~/.task'
            },
            'alpha': {
                'one': 'yes',
                'two': '2',
            },
            'beta': {
                'one': 'FALSE',
            },
            'gamma': {
                'one': 'TRUE',
            },
            'uda': {
                'a': {
                    'type': 'numeric',
                    'label': 'Alpha',
                },
                'b': {
                    'type': 'string',
                    'label': 'Beta',
                    'values': 'Strontium-90,Hydrogen-3',
                }
            }
        }

        self.assertEqual(self.taskrc, expected_config)

    def test_get_udas(self):
        expected_udas = {
            'a': NumericField(label='Alpha'),
            'b': ChoiceField(
                label='Beta',
                choices=['Strontium-90', 'Hydrogen-3'],
            ),
        }
        actual_udas = self.taskrc.get_udas()

        self.assertEqual(actual_udas, expected_udas)

    def test_config_overrides(self):
        overrides = {
            'uda': {
                'd': {
                    'type': 'string',
                    'label': 'Delta',
                }
            },
            'alpha': {
                'two': '3',
            }
        }

        taskrc = TaskRc(self.path_to_taskrc, overrides=overrides)

        expected_config = {
            'data': {
                'location': '~/.task'
            },
            'alpha': {
                'one': 'yes',
                'two': '3',
            },
            'beta': {
                'one': 'FALSE',
            },
            'gamma': {
                'one': 'TRUE',
            },
            'uda': {
                'a': {
                    'type': 'numeric',
                    'label': 'Alpha',
                },
                'b': {
                    'type': 'string',
                    'label': 'Beta',
                    'values': 'Strontium-90,Hydrogen-3',
                },
                'd': {
                    'type': 'string',
                    'label': 'Delta',
                }
            }
        }

        self.assertEqual(taskrc, expected_config)