526e51cd3c2697af207d5ff1c7b5ec293cd5b85d
[notmuch] / bindings / python / notmuch / tag.py
1 """
2 This file is part of notmuch.
3
4 Notmuch is free software: you can redistribute it and/or modify it
5 under the terms of the GNU General Public License as published by the
6 Free Software Foundation, either version 3 of the License, or (at your
7 option) any later version.
8
9 Notmuch is distributed in the hope that it will be useful, but WITHOUT
10 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12 for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with notmuch.  If not, see <http://www.gnu.org/licenses/>.
16
17 Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>'
18 """
19 from ctypes import c_char_p
20 from notmuch.globals import (
21     nmlib,
22     Python3StringMixIn,
23     NullPointerError,
24     NotInitializedError,
25     NotmuchTagsP,
26 )
27
28
29 class Tags(Python3StringMixIn):
30     """Represents a list of notmuch tags
31
32     This object provides an iterator over a list of notmuch tags (which
33     are unicode instances).
34
35     Do note that the underlying library only provides a one-time
36     iterator (it cannot reset the iterator to the start). Thus iterating
37     over the function will "exhaust" the list of tags, and a subsequent
38     iteration attempt will raise a :exc:`NotInitializedError`.
39     Also note, that any function that uses iteration (nearly all) will
40     also exhaust the tags. So both::
41
42       for tag in tags: print tag
43
44     as well as::
45
46        number_of_tags = len(tags)
47
48     and even a simple::
49
50        #str() iterates over all tags to construct a space separated list
51        print(str(tags))
52
53     will "exhaust" the Tags. If you need to re-iterate over a list of
54     tags you will need to retrieve a new :class:`Tags` object.
55     """
56
57     #notmuch_tags_get
58     _get = nmlib.notmuch_tags_get
59     _get.argtypes = [NotmuchTagsP]
60     _get.restype = c_char_p
61
62     def __init__(self, tags_p, parent=None):
63         """
64         :param tags_p: A pointer to an underlying *notmuch_tags_t*
65              structure. These are not publically exposed, so a user
66              will almost never instantiate a :class:`Tags` object
67              herself. They are usually handed back as a result,
68              e.g. in :meth:`Database.get_all_tags`.  *tags_p* must be
69              valid, we will raise an :exc:`NullPointerError` if it is
70              `None`.
71         :type tags_p: :class:`ctypes.c_void_p`
72         :param parent: The parent object (ie :class:`Database` or
73              :class:`Message` these tags are derived from, and saves a
74              reference to it, so we can automatically delete the db object
75              once all derived objects are dead.
76         :TODO: Make the iterator optionally work more than once by
77                cache the tags in the Python object(?)
78         """
79         if not tags_p:
80             raise NullPointerError()
81
82         self._tags = tags_p
83         #save reference to parent object so we keep it alive
84         self._parent = parent
85
86     def __iter__(self):
87         """ Make Tags an iterator """
88         return self
89
90     _valid = nmlib.notmuch_tags_valid
91     _valid.argtypes = [NotmuchTagsP]
92     _valid.restype = bool
93
94     _move_to_next = nmlib.notmuch_tags_move_to_next
95     _move_to_next.argtypes = [NotmuchTagsP]
96     _move_to_next.restype = None
97
98     def __next__(self):
99         if not self._tags:
100             raise NotInitializedError()
101         if not self._valid(self._tags):
102             self._tags = None
103             raise StopIteration
104         tag = Tags._get(self._tags).decode('UTF-8')
105         self._move_to_next(self._tags)
106         return tag
107     next = __next__ # python2.x iterator protocol compatibility
108
109     def __nonzero__(self):
110         """Implement bool(Tags) check that can be repeatedly used
111
112         If __nonzero__ is not implemented, "if Tags()"
113         will implicitly call __len__, using up our iterator, so it is
114         important that this function is defined.
115
116         :returns: True if the Tags() iterator has at least one more Tag
117             left."""
118         return self._valid(self._tags) > 0
119
120     def __unicode__(self):
121         """string representation of :class:`Tags`: a space separated list of tags
122
123         .. note::
124
125             As this iterates over the tags, we will not be able to iterate over
126             them again (as in retrieve them)! If the tags have been exhausted
127             already, this will raise a :exc:`NotInitializedError`on subsequent
128             attempts.
129         """
130         return " ".join(self)
131
132     _destroy = nmlib.notmuch_tags_destroy
133     _destroy.argtypes = [NotmuchTagsP]
134     _destroy.restype = None
135
136     def __del__(self):
137         """Close and free the notmuch tags"""
138         if self._tags is not None:
139             self._destroy(self._tags)