예제 #1
0
파일: TodoList.py 프로젝트: netimen/topydo
    def __init__(self, p_todostrings):
        """
        Should be given a list of strings, each element a single todo string.
        The string will be parsed.
        """
        # initialize these first because the constructor calls add_list
        self._tododict = {}  # hash(todo) to todo lookup
        self._depgraph = DirectedGraph()

        super(TodoList, self).__init__(p_todostrings)
예제 #2
0
    def setUp(self):
        super().setUp()

        self.graph = DirectedGraph()

        self.graph.add_edge(1, 2, 1)
        self.graph.add_edge(2, 4, "Test")
        self.graph.add_edge(4, 3)
        self.graph.add_edge(4, 6)
        self.graph.add_edge(6, 2)
        self.graph.add_edge(1, 3)
        self.graph.add_edge(3, 5)
예제 #3
0
    def __init__(self, p_todostrings):
        """
        Should be given a list of strings, each element a single todo string.
        The string will be parsed.
        """
        self._todos = []
        self._tododict = {}  # hash(todo) to todo lookup
        self._depgraph = DirectedGraph()
        self._todo_id_map = {}
        self._id_todo_map = {}

        self.add_list(p_todostrings)
        self.dirty = False
예제 #4
0
파일: TodoList.py 프로젝트: netimen/topydo
    def __init__(self, p_todostrings):
        """
        Should be given a list of strings, each element a single todo string.
        The string will be parsed.
        """
        # initialize these first because the constructor calls add_list
        self._tododict = {} # hash(todo) to todo lookup
        self._depgraph = DirectedGraph()

        super(TodoList, self).__init__(p_todostrings)
예제 #5
0
파일: TodoList.py 프로젝트: netimen/topydo
class TodoList(TodoListBase):
    """
    Provides operations for a todo list, such as adding items, removing them,
    etc.

    The list is usually a complete list found in the program's input (e.g. a
    todo.txt file), not an arbitrary set of todo items.
    """

    def __init__(self, p_todostrings):
        """
        Should be given a list of strings, each element a single todo string.
        The string will be parsed.
        """
        # initialize these first because the constructor calls add_list
        self._tododict = {} # hash(todo) to todo lookup
        self._depgraph = DirectedGraph()

        super(TodoList, self).__init__(p_todostrings)

    def todo_by_dep_id(self, p_dep_id):
        """
        Returns the todo that has the id tag set to the value p_dep_id.
        There is only one such task, the behavior is undefined when a tag has
        more than one id tag.
        """
        hits = [t for t in self._todos if t.tag_value('id') == p_dep_id]

        return hits[0] if len(hits) else None

    def _maintain_dep_graph(self, p_todo):
        """
        Makes sure that the dependency graph is consistent according to the
        given todo.
        """

        dep_id = p_todo.tag_value('id')
        # maintain dependency graph
        if dep_id:
            self._depgraph.add_node(hash(p_todo))

            # connect all tasks we have in memory so far that refer to this
            # task
            for dep in \
                [dep for dep in self._todos if dep.has_tag('p', dep_id)]:

                self._depgraph.add_edge(hash(p_todo), hash(dep), dep_id)

        for child in p_todo.tag_values('p'):
            parent = self.todo_by_dep_id(child)
            if parent:
                self._depgraph.add_edge(hash(parent), hash(p_todo), child)

    def add_todos(self, p_todos):
        for todo in p_todos:
            self._todos.append(todo)
            self._tododict[hash(todo)] = todo
            self._maintain_dep_graph(todo)

        self._update_todo_ids()
        self._update_parent_cache()
        self.dirty = True

    def delete(self, p_todo):
        """ Deletes a todo item from the list. """
        try:
            number = self._todos.index(p_todo)

            for child in self.children(p_todo):
                self.remove_dependency(p_todo, child)

            for parent in self.parents(p_todo):
                self.remove_dependency(parent, p_todo)

            del self._todos[number]
            self._update_todo_ids()

            self.dirty = True
        except ValueError:
            # todo item couldn't be found, ignore
            pass

    def add_dependency(self, p_from_todo, p_to_todo):
        """ Adds a dependency from task 1 to task 2. """
        def find_next_id():
            """
            Find a new unused ID.
            Unused means that no task has it as an 'id' value or as a 'p'
            value.
            """
            def id_exists(p_id):
                """
                Returns True if there exists a todo with the given parent
                ID.
                """
                for todo in self._todos:
                    if todo.has_tag('id', str(p_id)):
                        return True

                return False

            new_id = 1
            while id_exists(new_id):
                new_id += 1

            return str(new_id)

        def append_projects_to_subtodo():
            """
            Appends projects in the parent todo item that are not present in
            the sub todo item.
            """
            if config().append_parent_projects():
                for project in p_from_todo.projects() - p_to_todo.projects():
                    self.append(p_to_todo, "+{}".format(project))

        def append_contexts_to_subtodo():
            """
            Appends contexts in the parent todo item that are not present in
            the sub todo item.
            """
            if config().append_parent_contexts():
                for context in p_from_todo.contexts() - p_to_todo.contexts():
                    self.append(p_to_todo, "@{}".format(context))

        if p_from_todo != p_to_todo and not self._depgraph.has_edge(
            hash(p_from_todo), hash(p_to_todo)):

            dep_id = None
            if p_from_todo.has_tag('id'):
                dep_id = p_from_todo.tag_value('id')
            else:
                dep_id = find_next_id()
                p_from_todo.set_tag('id', dep_id)

            p_to_todo.add_tag('p', dep_id)
            self._depgraph.add_edge(hash(p_from_todo), hash(p_to_todo), dep_id)
            self._update_parent_cache()
            append_projects_to_subtodo()
            append_contexts_to_subtodo()
            self.dirty = True

    def remove_dependency(self, p_from_todo, p_to_todo):
        """ Removes a dependency between two todos. """
        dep_id = p_from_todo.tag_value('id')

        if dep_id:
            p_to_todo.remove_tag('p', dep_id)
            self._depgraph.remove_edge(hash(p_from_todo), hash(p_to_todo))
            self._update_parent_cache()

            if not self.children(p_from_todo, True):
                p_from_todo.remove_tag('id')

            self.dirty = True

    def parents(self, p_todo, p_only_direct=False):
        """
        Returns a list of parent todos that (in)directly depend on the
        given todo.
        """
        parents = self._depgraph.incoming_neighbors(
            hash(p_todo), not p_only_direct)
        return [self._tododict[parent] for parent in parents]

    def children(self, p_todo, p_only_direct=False):
        """
        Returns a list of child todos that the given todo (in)directly depends
        on.
        """
        children = \
            self._depgraph.outgoing_neighbors(hash(p_todo), not p_only_direct)
        return [self._tododict[child] for child in children]

    def clean_dependencies(self):
        """
        Cleans the dependency graph.

        This is achieved by performing a transitive reduction on the dependency
        graph and removing unused dependency ids from the graph (in that
        order).
        """
        def clean_by_tag(tag_name):
            """ Generic function to handle 'p' and 'id' tags. """
            for todo in [todo for todo in self._todos
                if todo.has_tag(tag_name)]:

                value = todo.tag_value(tag_name)
                if not self._depgraph.has_edge_id(value):
                    todo.remove_tag(tag_name, value)
                    self.dirty = True

        self._depgraph.transitively_reduce()
        clean_by_tag('p')
        clean_by_tag('id')

    def _update_parent_cache(self):
        """
        Sets the attribute to the list of parents, such that others may access
        it outside this todo list.
        This is used for calculating the average importance, that requires
        access to a todo's parents.
        """

        for todo in self._todos:
            todo.attributes['parents'] = self.parents(todo)
예제 #6
0
class GraphTest(TopydoTest):
    def setUp(self):
        super().setUp()

        self.graph = DirectedGraph()

        self.graph.add_edge(1, 2, 1)
        self.graph.add_edge(2, 4, "Test")
        self.graph.add_edge(4, 3)
        self.graph.add_edge(4, 6)
        self.graph.add_edge(6, 2)
        self.graph.add_edge(1, 3)
        self.graph.add_edge(3, 5)

        #         1
        #       /   \
        #      v     v
        #    />2   />3
        #   /  |  /  |
        #  /   v /   v
        # 6 <- 4     5

    def test_has_nodes(self):
        for i in range(1, 7):
            self.assertTrue(self.graph.has_node(i))

    def test_has_edge_ids(self):
        self.assertTrue(self.graph.has_edge_id(1))
        self.assertTrue(self.graph.has_edge_id("Test"))
        self.assertFalse(self.graph.has_edge_id("1"))

    def test_incoming_neighbors1(self):
        self.assertEqual(self.graph.incoming_neighbors(1), set())

    def test_edge_id_of_nonexistent_edge(self):
        self.assertFalse(self.graph.edge_id(1, 6))

    def test_incoming_neighbors2(self):
        self.assertEqual(self.graph.incoming_neighbors(2), set([1, 6]))

    def test_incoming_neighbors3(self):
        self.assertEqual(self.graph.incoming_neighbors(1, True), set())

    def test_incoming_neighbors4(self):
        self.assertEqual(self.graph.incoming_neighbors(5, True),
                         set([1, 2, 3, 4, 6]))

    def test_outgoing_neighbors1(self):
        self.assertEqual(self.graph.outgoing_neighbors(1), set([2, 3]))

    def test_outgoing_neighbors2(self):
        self.assertEqual(self.graph.outgoing_neighbors(2), set([4]))

    def test_outgoing_neighbors3(self):
        self.assertEqual(self.graph.outgoing_neighbors(1, True),
                         set([2, 3, 4, 5, 6]))

    def test_outgoing_neighbors4(self):
        self.assertEqual(self.graph.outgoing_neighbors(3), set([5]))

    def test_outgoing_neighbors5(self):
        self.assertEqual(self.graph.outgoing_neighbors(5), set([]))

    def test_remove_edge1(self):
        self.graph.remove_edge(1, 2)

        self.assertFalse(self.graph.has_path(1, 4))
        self.assertTrue(self.graph.has_path(2, 4))
        self.assertFalse(self.graph.has_edge_id(1))

    def test_remove_edge2(self):
        self.graph.remove_edge(3, 5, True)

        self.assertFalse(self.graph.has_path(1, 5))
        self.assertFalse(self.graph.has_node(5))

    def test_remove_edge3(self):
        self.graph.remove_edge(3, 5, False)

        self.assertFalse(self.graph.has_path(1, 5))
        self.assertTrue(self.graph.has_node(5))

    def test_remove_edge4(self):
        """ Remove non-existing edge. """
        self.graph.remove_edge(4, 5)

    def test_remove_edge5(self):
        self.graph.remove_edge(3, 5, True)

        self.assertFalse(self.graph.has_path(1, 5))
        self.assertFalse(self.graph.has_node(5))

    def test_remove_edge6(self):
        self.graph.remove_edge(1, 3, True)

        self.assertTrue(self.graph.has_path(1, 5))

    def test_remove_node1(self):
        self.graph.remove_node(2)

        self.assertTrue(self.graph.has_node(1))
        self.assertTrue(self.graph.has_node(4))
        self.assertTrue(self.graph.has_node(6))
        self.assertFalse(self.graph.has_node(2))

        self.assertFalse(self.graph.has_edge(2, 4))
        self.assertFalse(self.graph.has_edge(1, 2))

    def test_remove_node2(self):
        self.graph.remove_node(3, True)

        self.assertFalse(self.graph.has_node(5))
        self.assertFalse(self.graph.has_edge(1, 3))
        self.assertFalse(self.graph.has_edge(3, 5))
        self.assertFalse(self.graph.has_path(1, 5))

    def test_remove_node3(self):
        self.graph.remove_node(3, False)

        self.assertTrue(self.graph.has_node(5))
        self.assertFalse(self.graph.has_edge(1, 3))
        self.assertFalse(self.graph.has_edge(3, 5))
        self.assertFalse(self.graph.has_path(1, 5))

    def test_transitive_reduce1(self):
        self.graph.transitively_reduce()

        self.assertTrue(self.graph.has_edge(4, 3))
        self.assertFalse(self.graph.has_edge(1, 3))

    def test_add_double_edge(self):
        self.graph.add_edge(1, 3)
        self.graph.remove_edge(1, 3)

        # the one and only edge must be removed now
        self.assertFalse(self.graph.has_edge(1, 3))

    def test_add_double_edge_with_id(self):
        self.graph.add_edge(1, 3, "Dummy")
        self.assertFalse(self.graph.has_edge_id("Dummy"))

        self.graph.remove_edge(1, 3)

        # the one and only edge must be removed now
        self.assertFalse(self.graph.has_edge(1, 3))

    def test_str_output(self):
        out = 'digraph g {\n  1\n  1 -> 2 [label="1"]\n  1 -> 3\n  2\n  2 -> 4 [label="Test"]\n  3\n  3 -> 5\n  4\n  4 -> 3\n  4 -> 6\n  5\n  6\n  6 -> 2\n}\n'
        self.assertEqual(str(self.graph), out)

    def test_dot_output_without_labels(self):
        out = 'digraph g {\n  1\n  1 -> 2\n  1 -> 3\n  2\n  2 -> 4\n  3\n  3 -> 5\n  4\n  4 -> 3\n  4 -> 6\n  5\n  6\n  6 -> 2\n}\n'
        self.assertEqual(self.graph.dot(False), out)
예제 #7
0
class TodoList(TodoListBase):
    """
    Provides operations for a todo list, such as adding items, removing them,
    etc.

    The list is usually a complete list found in the program's input (e.g. a
    todo.txt file), not an arbitrary set of todo items.
    """
    def __init__(self, p_todostrings):
        """
        Should be given a list of strings, each element a single todo string.
        The string will be parsed.
        """
        self._todos = []
        self._tododict = {}  # hash(todo) to todo lookup
        self._depgraph = DirectedGraph()
        self._todo_id_map = {}
        self._id_todo_map = {}

        self.add_list(p_todostrings)
        self.dirty = False

    def todo_by_dep_id(self, p_dep_id):
        """
        Returns the todo that has the id tag set to the value p_dep_id.
        There is only one such task, the behavior is undefined when a tag has
        more than one id tag.
        """
        hits = [t for t in self._todos if t.tag_value('id') == p_dep_id]

        return hits[0] if len(hits) else None

    def _maintain_dep_graph(self, p_todo):
        """
        Makes sure that the dependency graph is consistent according to the
        given todo.
        """

        dep_id = p_todo.tag_value('id')
        # maintain dependency graph
        if dep_id:
            self._depgraph.add_node(hash(p_todo))

            # connect all tasks we have in memory so far that refer to this
            # task
            for dep in \
                [dep for dep in self._todos if dep.has_tag('p', dep_id)]:

                self._depgraph.add_edge(hash(p_todo), hash(dep), dep_id)

        for child in p_todo.tag_values('p'):
            parent = self.todo_by_dep_id(child)
            if parent:
                self._depgraph.add_edge(hash(parent), hash(p_todo), child)

    def add_todos(self, p_todos):
        for todo in p_todos:
            self._todos.append(todo)
            self._tododict[hash(todo)] = todo
            self._maintain_dep_graph(todo)

        self._update_todo_ids()
        self._update_parent_cache()
        self.dirty = True

    def delete(self, p_todo):
        """ Deletes a todo item from the list. """
        try:
            number = self._todos.index(p_todo)

            for child in self.children(p_todo):
                self.remove_dependency(p_todo, child)

            for parent in self.parents(p_todo):
                self.remove_dependency(parent, p_todo)

            del self._todos[number]
            self._update_todo_ids()

            self.dirty = True
        except ValueError:
            # todo item couldn't be found, ignore
            pass

    def add_dependency(self, p_from_todo, p_to_todo):
        """ Adds a dependency from task 1 to task 2. """
        def find_next_id():
            """
            Find a new unused ID.
            Unused means that no task has it as an 'id' value or as a 'p'
            value.
            """
            def id_exists(p_id):
                """
                Returns True if there exists a todo with the given parent
                ID.
                """
                for todo in self._todos:
                    if todo.has_tag('id', str(p_id)):
                        return True

                return False

            new_id = 1
            while id_exists(new_id):
                new_id += 1

            return str(new_id)

        def append_projects_to_subtodo():
            """
            Appends projects in the parent todo item that are not present in
            the sub todo item.
            """
            if config().append_parent_projects():
                for project in p_from_todo.projects() - p_to_todo.projects():
                    self.append(p_to_todo, "+{}".format(project))

        if p_from_todo != p_to_todo and not self._depgraph.has_edge(
                hash(p_from_todo), hash(p_to_todo)):

            dep_id = None
            if p_from_todo.has_tag('id'):
                dep_id = p_from_todo.tag_value('id')
            else:
                dep_id = find_next_id()
                p_from_todo.set_tag('id', dep_id)

            p_to_todo.add_tag('p', dep_id)
            self._depgraph.add_edge(hash(p_from_todo), hash(p_to_todo), dep_id)
            self._update_parent_cache()
            append_projects_to_subtodo()
            self.dirty = True

    def remove_dependency(self, p_from_todo, p_to_todo):
        """ Removes a dependency between two todos. """
        dep_id = p_from_todo.tag_value('id')

        if dep_id:
            p_to_todo.remove_tag('p', dep_id)
            self._depgraph.remove_edge(hash(p_from_todo), hash(p_to_todo))
            self._update_parent_cache()

            if not self.children(p_from_todo, True):
                p_from_todo.remove_tag('id')

            self.dirty = True

    def parents(self, p_todo, p_only_direct=False):
        """
        Returns a list of parent todos that (in)directly depend on the
        given todo.
        """
        parents = self._depgraph.incoming_neighbors(hash(p_todo),
                                                    not p_only_direct)
        return [self._tododict[parent] for parent in parents]

    def children(self, p_todo, p_only_direct=False):
        """
        Returns a list of child todos that the given todo (in)directly depends
        on.
        """
        children = \
            self._depgraph.outgoing_neighbors(hash(p_todo), not p_only_direct)
        return [self._tododict[child] for child in children]

    def clean_dependencies(self):
        """
        Cleans the dependency graph.

        This is achieved by performing a transitive reduction on the dependency
        graph and removing unused dependency ids from the graph (in that
        order).
        """
        def clean_by_tag(tag_name):
            """ Generic function to handle 'p' and 'id' tags. """
            for todo in [
                    todo for todo in self._todos if todo.has_tag(tag_name)
            ]:

                value = todo.tag_value(tag_name)
                if not self._depgraph.has_edge_id(value):
                    todo.remove_tag(tag_name, value)
                    self.dirty = True

        self._depgraph.transitively_reduce()
        clean_by_tag('p')
        clean_by_tag('id')

    def _update_parent_cache(self):
        """
        Sets the attribute to the list of parents, such that others may access
        it outside this todo list.
        This is used for calculating the average importance, that requires
        access to a todo's parents.
        """

        for todo in self._todos:
            todo.attributes['parents'] = self.parents(todo)