3 # Copyright(c) 2009, Gentoo Foundation
5 # Licensed under the GNU General Public License, v2
9 """Provides an easy-to-use python interface to Gentoo's metadata.xml file.
12 >>> from gentoolkit.metadata import MetaData
13 >>> pkg_md = MetaData('/usr/portage/app-misc/gourmet/metadata.xml')
15 <MetaData '/usr/portage/app-misc/gourmet/metadata.xml'>
18 >>> for maint in pkg_md.maintainers():
19 ... print "{0} ({1})".format(maint.email, maint.name)
21 nixphoeni@gentoo.org (Joe Sapp)
22 >>> for flag in pkg_md.use():
23 ... print flag.name, "->", flag.description
25 rtf -> Enable export to RTF
26 gnome-print -> Enable printing support using gnome-print
27 >>> upstream = pkg_md.upstream()
29 [<_Upstream {'docs': [], 'remoteid': [], 'maintainer':
30 [<_Maintainer 'Thomas_Hinkle@alumni.brown.edu'>], 'bugtracker': [],
32 >>> upstream[0].maintainer[0].name
36 # Move to Imports section after Python-2.6 is stable
39 __all__ = ('MetaData',)
40 __docformat__ = 'epytext'
47 import xml.etree.cElementTree as etree
49 from portage import os, settings
55 class _Maintainer(object):
56 """An object for representing one maintainer.
58 @type email: str or None
59 @ivar email: Maintainer's email address. Used for both Gentoo and upstream.
60 @type name: str or None
61 @ivar name: Maintainer's name. Used for both Gentoo and upstream.
62 @type description: str or None
63 @ivar description: Description of what a maintainer does. Gentoo only.
64 @type restrict: str or None
65 @ivar restrict: e.g. >=portage-2.2 means only maintains versions
66 of Portage greater than 2.2. Should be DEPEND string with < and >
67 converted to < and > respectively.
68 @type status: str or None
69 @ivar status: If set, either 'active' or 'inactive'. Upstream only.
72 def __init__(self, node):
75 self.description = None
76 self.restrict = node.get('restrict')
77 self.status = node.get('status')
78 maint_attrs = node.getchildren()
79 for attr in maint_attrs:
80 setattr(self, attr.tag, attr.text)
83 return "<%s %r>" % (self.__class__.__name__, self.email)
86 class _Useflag(object):
87 """An object for representing one USE flag.
89 @todo: Is there any way to have a keyword option to leave in
90 <pkg> and <cat> for later processing?
91 @type name: str or None
93 @type restrict: str or None
94 @ivar restrict: e.g. >=portage-2.2 means flag is only avaiable in
95 versions greater than 2.2
96 @type description: str
97 @ivar description: description of the USE flag
100 def __init__(self, node):
101 self.name = node.get('name')
102 self.restrict = node.get('restrict')
106 for child in node.getchildren():
107 _desc += child.text if child.text else ''
108 _desc += child.tail if child.tail else ''
109 # This takes care of tabs and newlines left from the file
110 self.description = re.sub('\s+', ' ', _desc)
113 return "<%s %r>" % (self.__class__.__name__, self.name)
116 class _Upstream(object):
117 """An object for representing one package's upstream.
119 @type maintainers: list
120 @ivar maintainers: L{_Maintainer} objects for each upstream maintainer
121 @type changelogs: list
122 @ivar changelogs: URLs to upstream's ChangeLog file in str format
124 @ivar docs: Sequence of tuples containing URLs to upstream documentation
125 in the first slot and 'lang' attribute in the second, e.g.,
126 [('http.../docs/en/tut.html', None), ('http.../doc/fr/tut.html', 'fr')]
127 @type bugtrackers: list
128 @ivar bugtrackers: URLs to upstream's bugtracker. May also contain an email
129 address if prepended with 'mailto:'
130 @type remoteids: list
131 @ivar remoteids: Sequence of tuples containing the project's hosting site
132 name in the first slot and the project's ID name or number for that
133 site in the second, e.g., [('sourceforge', 'systemrescuecd')]
136 def __init__(self, node):
138 self.maintainers = self.upstream_maintainers()
139 self.changelogs = self.upstream_changelogs()
140 self.docs = self.upstream_documentation()
141 self.bugtrackers = self.upstream_bugtrackers()
142 self.remoteids = self.upstream_remoteids()
145 return "<%s %r>" % (self.__class__.__name__, self.__dict__)
147 def upstream_bugtrackers(self):
148 """Retrieve upstream bugtracker location from xml node."""
149 return [e.text for e in self.node.findall('bugs-to')]
151 def upstream_changelogs(self):
152 """Retrieve upstream changelog location from xml node."""
153 return [e.text for e in self.node.findall('changelog')]
155 def upstream_documentation(self):
156 """Retrieve upstream documentation location from xml node."""
158 for elem in self.node.findall('doc'):
159 lang = elem.get('lang')
160 result.append((elem.text, lang))
163 def upstream_maintainers(self):
164 """Retrieve upstream maintainer information from xml node."""
165 return [_Maintainer(m) for m in self.node.findall('maintainer')]
167 def upstream_remoteids(self):
168 """Retrieve upstream remote ID from xml node."""
169 return [(e.text, e.get('type')) for e in self.node.findall('remote-id')]
172 class MetaData(object):
173 """Access metadata.xml"""
175 def __init__(self, metadata_path):
176 """Parse a valid metadata.xml file.
178 @type metadata_path: str
179 @param metadata_path: path to a valid metadata.xml file
180 @raise IOError: if C{metadata_path} can not be read
183 self.metadata_path = metadata_path
184 self._xml_tree = etree.parse(metadata_path)
187 self._herdstree = None
188 self._descriptions = None
189 self._maintainers = None
190 self._useflags = None
191 self._upstream = None
194 return "<%s %r>" % (self.__class__.__name__, self.metadata_path)
196 def _get_herd_email(self, herd):
197 """Get a herd's email address.
200 @param herd: herd whose email you want
202 @return: email address or None if herd is not in herds.xml
203 @raise IOError: if $PORTDIR/metadata/herds.xml can not be read
206 if self._herdstree is None:
207 herds_path = os.path.join(settings['PORTDIR'], 'metadata/herds.xml')
209 self._herdstree = etree.parse(herds_path)
211 # For some trees, herds.xml may not exist. Bug #300108.
214 # Some special herds are not listed in herds.xml
215 if herd in ('no-herd', 'maintainer-wanted', 'maintainer-needed'):
218 for node in self._herdstree.getiterator('herd'):
219 if node.findtext('name') == herd:
220 return node.findtext('email')
222 def herds(self, include_email=False):
223 """Return a list of text nodes for <herd>.
225 @type include_email: bool
226 @keyword include_email: if True, also look up the herd's email
228 @return: if include_email is False, return a list of strings;
229 if include_email is True, return a list of tuples containing:
230 [('herd1', 'herd1@gentoo.org'), ('no-herd', None);
234 for elem in self._xml_tree.findall('herd'):
239 herd_mail = self._get_herd_email(text)
240 result.append((text, herd_mail))
246 def descriptions(self):
247 """Return a list of text nodes for <longdescription>.
250 @return: package description in string format
251 @todo: Support the C{lang} attribute
254 if self._descriptions is not None:
255 return self._descriptions
257 long_descriptions = self._xml_tree.findall("longdescription")
258 self._descriptions = [e.text for e in long_descriptions]
259 return self._descriptions
261 def maintainers(self):
262 """Get maintainers' name, email and description.
265 @return: a sequence of L{_Maintainer} objects in document order.
268 if self._maintainers is not None:
269 return self._maintainers
271 self._maintainers = []
272 for node in self._xml_tree.findall('maintainer'):
273 self._maintainers.append(_Maintainer(node))
275 return self._maintainers
278 """Get names and descriptions for USE flags defined in metadata.
281 @return: a sequence of L{_Useflag} objects in document order.
284 if self._useflags is not None:
285 return self._useflags
288 for node in self._xml_tree.getiterator('flag'):
289 self._useflags.append(_Useflag(node))
291 return self._useflags
294 """Get upstream contact information.
297 @return: a sequence of L{_Upstream} objects in document order.
300 if self._upstream is not None:
301 return self._upstream
304 for node in self._xml_tree.findall('upstream'):
305 self._upstream.append(_Upstream(node))
307 return self._upstream
309 # vim: set ts=4 sw=4 tw=79: