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