metadata: remove shebang from module without __name__ == '__main__' block
[gentoolkit.git] / pym / gentoolkit / metadata.py
1 # Copyright(c) 2009, Gentoo Foundation
2 #
3 # Licensed under the GNU General Public License, v2
4 #
5 # $Header$
6
7 """Provides an easy-to-use python interface to Gentoo's metadata.xml file.
8
9         Example usage:
10                 >>> from gentoolkit.metadata import MetaData
11                 >>> pkg_md = MetaData('/usr/portage/app-misc/gourmet/metadata.xml')
12                 >>> pkg_md
13                 <MetaData '/usr/portage/app-misc/gourmet/metadata.xml'>
14                 >>> pkg_md.herds()
15                 ['no-herd']
16                 >>> for maint in pkg_md.maintainers():
17                 ...     print "{0} ({1})".format(maint.email, maint.name)
18                 ...
19                 nixphoeni@gentoo.org (Joe Sapp)
20                 >>> for flag in pkg_md.use():
21                 ...     print flag.name, "->", flag.description
22                 ...
23                 rtf -> Enable export to RTF
24                 gnome-print -> Enable printing support using gnome-print
25                 >>> upstream = pkg_md.upstream()
26                 >>> upstream
27                 [<_Upstream {'docs': [], 'remoteid': [], 'maintainer':
28                  [<_Maintainer 'Thomas_Hinkle@alumni.brown.edu'>], 'bugtracker': [],
29                  'changelog': []}>]
30                 >>> upstream[0].maintainer[0].name
31                 'Thomas Mills Hinkle'
32 """
33
34 __all__ = ('MetaData',)
35 __docformat__ = 'epytext'
36
37 # =======
38 # Imports
39 # =======
40
41 import os
42 import re
43 import xml.etree.cElementTree as etree
44
45 from portage import settings
46
47 # =======
48 # Classes
49 # =======
50
51 class _Maintainer(object):
52         """An object for representing one maintainer.
53
54         @type email: str or None
55         @ivar email: Maintainer's email address. Used for both Gentoo and upstream.
56         @type name: str or None
57         @ivar name: Maintainer's name. Used for both Gentoo and upstream.
58         @type description: str or None
59         @ivar description: Description of what a maintainer does. Gentoo only.
60         @type restrict: str or None
61         @ivar restrict: e.g. &gt;=portage-2.2 means only maintains versions
62                 of Portage greater than 2.2. Should be DEPEND string with < and >
63                 converted to &lt; and &gt; respectively. 
64         @type status: str or None
65         @ivar status: If set, either 'active' or 'inactive'. Upstream only.
66         """
67
68         def __init__(self, node):
69                 self.email = None
70                 self.name = None
71                 self.description = None
72                 self.restrict = node.get('restrict')
73                 self.status = node.get('status')
74                 maint_attrs = node.getchildren()
75                 for attr in maint_attrs:
76                         setattr(self, attr.tag, attr.text)
77
78         def __repr__(self):
79                 return "<%s %r>" % (self.__class__.__name__, self.email)
80
81
82 class _Useflag(object):
83         """An object for representing one USE flag.
84
85         @todo: Is there any way to have a keyword option to leave in
86                 <pkg> and <cat> for later processing?
87         @type name: str or None
88         @ivar name: USE flag
89         @type restrict: str or None
90         @ivar restrict: e.g. &gt;=portage-2.2 means flag is only avaiable in
91                 versions greater than 2.2
92         @type description: str
93         @ivar description: description of the USE flag
94         """
95
96         def __init__(self, node):
97                 self.name = node.get('name')
98                 self.restrict = node.get('restrict')
99                 _desc = ''
100                 if node.text:
101                         _desc = node.text
102                 for child in node.getchildren():
103                         _desc += child.text if child.text else ''
104                         _desc += child.tail if child.tail else ''
105                 # This takes care of tabs and newlines left from the file
106                 self.description = re.sub('\s+', ' ', _desc)
107
108         def __repr__(self):
109                 return "<%s %r>" % (self.__class__.__name__, self.name)
110
111
112 class _Upstream(object):
113         """An object for representing one package's upstream.
114
115         @type maintainers: list
116         @ivar maintainers: L{_Maintainer} objects for each upstream maintainer
117         @type changelogs: list
118         @ivar changelogs: URLs to upstream's ChangeLog file in str format
119         @type docs: list
120         @ivar docs: Sequence of tuples containing URLs to upstream documentation
121                 in the first slot and 'lang' attribute in the second, e.g.,
122                 [('http.../docs/en/tut.html', None), ('http.../doc/fr/tut.html', 'fr')]
123         @type bugtrackers: list
124         @ivar bugtrackers: URLs to upstream's bugtracker. May also contain an email
125                 address if prepended with 'mailto:'
126         @type remoteids: list
127         @ivar remoteids: Sequence of tuples containing the project's hosting site
128                 name in the first slot and the project's ID name or number for that
129                 site in the second, e.g., [('sourceforge', 'systemrescuecd')]
130         """
131
132         def __init__(self, node):
133                 self.node = node
134                 self.maintainers = self.upstream_maintainers()
135                 self.changelogs = self.upstream_changelogs()
136                 self.docs = self.upstream_documentation()
137                 self.bugtrackers = self.upstream_bugtrackers()
138                 self.remoteids = self.upstream_remoteids()
139
140         def __repr__(self):
141                 return "<%s %r>" % (self.__class__.__name__, self.__dict__)
142
143         def upstream_bugtrackers(self):
144                 """Retrieve upstream bugtracker location from xml node."""
145                 return [e.text for e in self.node.findall('bugs-to')]
146
147         def upstream_changelogs(self):
148                 """Retrieve upstream changelog location from xml node."""
149                 return [e.text for e in self.node.findall('changelog')]
150
151         def upstream_documentation(self):
152                 """Retrieve upstream documentation location from xml node."""
153                 result = []
154                 for elem in self.node.findall('doc'):
155                         lang = elem.get('lang')
156                         result.append((elem.text, lang))
157                 return result
158
159         def upstream_maintainers(self):
160                 """Retrieve upstream maintainer information from xml node."""
161                 return [_Maintainer(m) for m in self.node.findall('maintainer')]
162
163         def upstream_remoteids(self):
164                 """Retrieve upstream remote ID from xml node."""
165                 return [(e.text, e.get('type')) for e in self.node.findall('remote-id')]
166
167
168 class MetaData(object):
169         """Access metadata.xml"""
170
171         def __init__(self, metadata_path):
172                 """Parse a valid metadata.xml file.
173
174                 @type metadata_path: str
175                 @param metadata_path: path to a valid metadata.xml file
176                 @raise IOError: if C{metadata_path} can not be read
177                 """
178
179                 self.metadata_path = metadata_path
180                 self._xml_tree = etree.parse(metadata_path)
181
182                 # Used for caching
183                 self._herdstree = None
184                 self._descriptions = None
185                 self._maintainers = None
186                 self._useflags = None
187                 self._upstream = None
188
189         def __repr__(self):
190                 return "<%s %r>" % (self.__class__.__name__, self.metadata_path)
191
192         def _get_herd_email(self, herd):
193                 """Get a herd's email address.
194
195                 @type herd: str
196                 @param herd: herd whose email you want
197                 @rtype: str or None
198                 @return: email address or None if herd is not in herds.xml
199                 @raise IOError: if $PORTDIR/metadata/herds.xml can not be read
200                 """
201
202                 if self._herdstree is None:
203                         herds_path = os.path.join(settings['PORTDIR'], 'metadata/herds.xml')
204                         try:
205                                 self._herdstree = etree.parse(herds_path)
206                         except IOError:
207                                 # For some trees, herds.xml may not exist. Bug #300108.
208                                 return None
209
210                 # Some special herds are not listed in herds.xml
211                 if herd in ('no-herd', 'maintainer-wanted', 'maintainer-needed'):
212                         return None
213
214                 for node in self._herdstree.getiterator('herd'):
215                         if node.findtext('name') == herd:
216                                 return node.findtext('email')
217
218         def herds(self, include_email=False):
219                 """Return a list of text nodes for <herd>.
220
221                 @type include_email: bool
222                 @keyword include_email: if True, also look up the herd's email
223                 @rtype: list
224                 @return: if include_email is False, return a list of strings;
225                          if include_email is True, return a list of tuples containing:
226                                          [('herd1', 'herd1@gentoo.org'), ('no-herd', None);
227                 """
228
229                 result = []
230                 for elem in self._xml_tree.findall('herd'):
231                         text = elem.text
232                         if text is None:
233                                 text = ''
234                         if include_email:
235                                 herd_mail = self._get_herd_email(text)
236                                 result.append((text, herd_mail))
237                         else:
238                                 result.append(text)
239
240                 return result
241
242         def descriptions(self):
243                 """Return a list of text nodes for <longdescription>.
244
245                 @rtype: list
246                 @return: package description in string format
247                 @todo: Support the C{lang} attribute
248                 """
249
250                 if self._descriptions is not None:
251                         return self._descriptions
252
253                 long_descriptions = self._xml_tree.findall("longdescription")
254                 self._descriptions = [e.text for e in long_descriptions]
255                 return self._descriptions
256
257         def maintainers(self):
258                 """Get maintainers' name, email and description.
259
260                 @rtype: list
261                 @return: a sequence of L{_Maintainer} objects in document order.
262                 """
263
264                 if self._maintainers is not None:
265                         return self._maintainers
266
267                 self._maintainers = []
268                 for node in self._xml_tree.findall('maintainer'):
269                         self._maintainers.append(_Maintainer(node))
270
271                 return self._maintainers
272
273         def use(self):
274                 """Get names and descriptions for USE flags defined in metadata.
275
276                 @rtype: list
277                 @return: a sequence of L{_Useflag} objects in document order.
278                 """
279
280                 if self._useflags is not None:
281                         return self._useflags
282
283                 self._useflags = []
284                 for node in self._xml_tree.getiterator('flag'):
285                         self._useflags.append(_Useflag(node))
286
287                 return self._useflags
288
289         def upstream(self):
290                 """Get upstream contact information.
291
292                 @rtype: list
293                 @return: a sequence of L{_Upstream} objects in document order.
294                 """
295
296                 if self._upstream is not None:
297                         return self._upstream
298
299                 self._upstream = []
300                 for node in self._xml_tree.findall('upstream'):
301                         self._upstream.append(_Upstream(node))
302
303                 return self._upstream
304
305 # vim: set ts=4 sw=4 tw=79: