#! /usr/bin/python -tt
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Library General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
# Copyright 2005 Duke University
import types
import sys
from yum.constants import *
from yum.Errors import CompsException
#FIXME - compsexception isn't caught ANYWHERE so it's pointless to raise it
# switch all compsexceptions to grouperrors after api break
import fnmatch
import re
from yum.i18n import to_unicode
from yum.misc import get_my_lang_code
from yum.misc import cElementTree_iterparse as iterparse
lang_attr = '{http://www.w3.org/XML/1998/namespace}lang'
def parse_boolean(strng):
return BOOLEAN_STATES.get(strng.lower(), False)
def parse_number(strng):
return int(strng)
class CompsObj(object):
""" Group/Category helper object. """
# Could be the same as ui_name?
def __str__(self):
""" Return the "name" of the object for the C locale. """
return self.name
@property
def compsid(self):
""" Return the "id": categoryid, groupid, environmentid. """
for idT in ('categoryid', 'groupid', 'environmentid'):
if hasattr(self, idT):
return getattr(self, idT)
return None
@property
def ui_name(self):
""" Return the "name" of the object for the current locale. """
return self.nameByLang(get_my_lang_code())
@property
def ui_description(self):
""" Return the "description" of the object for the current locale. """
return self.descriptionByLang(get_my_lang_code())
def __cmp__(self, other):
if other is None:
return 1
if self.display_order > other.display_order:
return 1
if self.display_order < other.display_order:
return -1
return cmp(self.ui_name, other.ui_name)
def _expand_languages(self, lang):
import gettext
languages = [lang]
if 'C' not in languages:
languages.append('C')
# now normalize and expand the languages
nelangs = []
for lang in languages:
for nelang in gettext._expand_lang(lang):
if nelang not in nelangs:
nelangs.append(nelang)
return nelangs
def nameByLang(self, lang):
for langcode in self._expand_languages(lang):
if langcode in self.translated_name:
return to_unicode(self.translated_name[langcode])
return to_unicode(self.name)
def descriptionByLang(self, lang):
for langcode in self._expand_languages(lang):
if langcode in self.translated_description:
return to_unicode(self.translated_description[langcode])
return to_unicode(self.description)
class Group(CompsObj):
""" Group object parsed from group data in each repo. and merged. """
def __init__(self, elem=None):
self.user_visible = True
self.default = False
self.selected = False
self.name = ""
self.description = ""
self.translated_name = {}
self.translated_description = {}
self.mandatory_packages = {}
self.optional_packages = {}
self.default_packages = {}
self.conditional_packages = {}
self.langonly = None
self.groupid = None
self.display_order = 1024
self.installed = False
self.toremove = False
self._weak = False
if elem:
self.parse(elem)
def _packageiter(self):
# Gah, FIXME: real iterator/class
lst = self.mandatory_packages.keys() + \
self.optional_packages.keys() + \
self.default_packages.keys() + \
self.conditional_packages.keys()
return lst
packages = property(_packageiter)
def parse(self, elem):
for child in elem:
if child.tag == 'id':
myid = child.text
if self.groupid is not None:
raise CompsException
self.groupid = myid
elif child.tag == 'name':
text = child.text
if text:
text = text.encode('utf8')
lang = child.attrib.get(lang_attr)
if lang:
self.translated_name[lang] = text
else:
self.name = text
elif child.tag == 'description':
text = child.text
if text:
text = text.encode('utf8')
lang = child.attrib.get(lang_attr)
if lang:
self.translated_description[lang] = text
else:
if text:
self.description = text
elif child.tag == 'uservisible':
self.user_visible = parse_boolean(child.text)
elif child.tag == 'display_order':
self.display_order = parse_number(child.text)
elif child.tag == 'default':
self.default = parse_boolean(child.text)
elif child.tag in ['langonly', 'lang_only']:
text = child.text
if self.langonly is not None:
raise CompsException
self.langonly = text
elif child.tag == 'packagelist':
self.parse_package_list(child)
def parse_package_list(self, packagelist_elem):
for child in packagelist_elem:
if child.tag == 'packagereq':
genre = child.attrib.get('type')
if not genre:
genre = u'mandatory'
if genre not in ('mandatory', 'default', 'optional', 'conditional'):
# just ignore bad package lines
continue
package = child.text
if not package:
# Ignore this too, or we end up doing:
# .searchNevra(name=None)
continue
if genre == 'mandatory':
self.mandatory_packages[package] = 1
elif genre == 'default':
self.default_packages[package] = 1
elif genre == 'optional':
self.optional_packages[package] = 1
elif genre == 'conditional':
self.conditional_packages[package] = child.attrib.get('requires')
def add(self, obj):
"""Add another group object to this object"""
# we only need package lists and any translation that we don't already
# have
for pkg in obj.mandatory_packages:
self.mandatory_packages[pkg] = 1
for pkg in obj.default_packages:
self.default_packages[pkg] = 1
for pkg in obj.optional_packages:
self.optional_packages[pkg] = 1
for pkg in obj.conditional_packages:
self.conditional_packages[pkg] = obj.conditional_packages[pkg]
# Handle cases where a comps.xml without name & decription tags
# has been setup first, so the name & decription for this object is blank.
if self.name == '' and obj.name != '':
self.name = obj.name
if self.description == '' and obj.description != '':
self.description = obj.description
# name and description translations
for lang in obj.translated_name:
if lang not in self.translated_name:
self.translated_name[lang] = obj.translated_name[lang]
for lang in obj.translated_description:
if lang not in self.translated_description:
self.translated_description[lang] = obj.translated_description[lang]
def xml(self):
"""write out an xml stanza for the group object"""
msg ="""
%s
%s
%s
%s\n""" % (self.groupid, str(self.default).lower(),
str(self.user_visible).lower(), self.display_order)
if self.langonly:
msg += """ %s""" % self.langonly
msg +=""" %s\n""" % self.name
for (lang, val) in sorted(self.translated_name.items()):
msg += """ %s\n""" % (lang, val)
msg += """ %s\n""" % self.description
for (lang, val) in sorted(self.translated_description.items()):
msg += """ %s\n""" % (lang, val)
msg += """ \n"""
for pkg in sorted(self.mandatory_packages):
msg += """ %s\n""" % pkg
for pkg in sorted(self.default_packages):
msg += """ %s\n""" % pkg
for pkg in sorted(self.optional_packages):
msg += """ %s\n""" % pkg
for (pkg, req) in sorted(self.conditional_packages.items()):
msg += """ %s\n""" % (req, pkg)
msg += """ \n"""
msg += """ """
return msg
class Environment(CompsObj):
""" Environment object parsed from group data in each repo, and merged """
def __init__(self, elem=None):
self.name = ""
self.environmentid = None
self.description = ""
self.translated_name = {}
self.translated_description = {}
self.display_order = 1024
self.langonly = None
self.installed = False
self._groups = {}
self._options = {}
self._defaultoptions = {}
self._weak = False
if elem:
self.parse(elem)
def _allgroupiter(self):
lst = self._groups.keys() + \
self._options.keys()
return lst
allgroups = property(_allgroupiter)
def _groupiter(self):
return self._groups.keys()
groups = property(_groupiter)
def _optioniter(self):
return self._options.keys()
options = property(_optioniter)
def _defaultoptioniter(self):
return self._defaultoptions.keys()
defaultoptions = property(_defaultoptioniter)
def parse(self, elem):
for child in elem:
if child.tag == 'id':
myid = child.text
if self.environmentid is not None:
raise CompsException
self.environmentid = myid
elif child.tag == 'name':
text = child.text
if text:
text = text.encode('utf8')
lang = child.attrib.get(lang_attr)
if lang:
self.translated_name[lang] = text
else:
self.name = text
elif child.tag == 'description':
text = child.text
if text:
text = text.encode('utf8')
lang = child.attrib.get(lang_attr)
if lang:
self.translated_description[lang] = text
else:
self.description = text
elif child.tag == 'grouplist':
self.parse_group_list(child)
elif child.tag == 'optionlist':
self.parse_option_list(child)
elif child.tag == 'display_order':
self.display_order = parse_number(child.text)
def parse_group_list(self, grouplist_elem):
for child in grouplist_elem:
if child.tag == 'groupid':
groupid = child.text
self._groups[groupid] = 1
def parse_option_list(self, optionlist_elem):
for child in optionlist_elem:
if child.tag == 'groupid':
optionid = child.text
self._options[optionid] = 1
defopt = child.attrib.get('default')
if defopt:
default = parse_boolean(defopt)
else:
default = False
if default:
self._defaultoptions[optionid] = 1
def add(self, obj):
"""Add another environment object to this object"""
for grp in obj.groups:
self._groups[grp] = 1
for grp in obj.defaultoptions:
self._defaultoptions[grp] = 1
for grp in obj.options:
self._options[grp] = 1
# name and description translations
for lang in obj.translated_name:
if lang not in self.translated_name:
self.translated_name[lang] = obj.translated_name[lang]
for lang in obj.translated_description:
if lang not in self.translated_description:
self.translated_description[lang] = obj.translated_description[lang]
def xml(self):
"""write out an xml stanza for the environment object"""
msg ="""
%s
%s\n""" % (self.environmentid, self.display_order)
msg +=""" %s\n""" % self.name
for (lang, val) in self.translated_name.items():
msg += """ %s\n""" % (lang, val)
msg += """ %s\n""" % self.description
for (lang, val) in self.translated_description.items():
msg += """ %s\n""" % (lang, val)
msg += """ \n"""
for grp in self.groups:
msg += """ %s\n""" % grp
msg += """ \n"""
msg += """ \n"""
for grp in self.options:
if grp in self.defaultoptions:
msg += """ %s\n""" % grp
else:
msg += """ %s\n""" % grp
msg += """ \n"""
msg += """ \n"""
return msg
class Category(CompsObj):
""" Category object parsed from group data in each repo. and merged. """
def __init__(self, elem=None):
self.name = ""
self.categoryid = None
self.description = ""
self.translated_name = {}
self.translated_description = {}
self.display_order = 1024
self._groups = {}
if elem:
self.parse(elem)
def _groupiter(self):
return self._groups.keys()
groups = property(_groupiter)
def parse(self, elem):
for child in elem:
if child.tag == 'id':
myid = child.text
if self.categoryid is not None:
raise CompsException
self.categoryid = myid
elif child.tag == 'name':
text = child.text
if text:
text = text.encode('utf8')
lang = child.attrib.get(lang_attr)
if lang:
self.translated_name[lang] = text
else:
self.name = text
elif child.tag == 'description':
text = child.text
if text:
text = text.encode('utf8')
lang = child.attrib.get(lang_attr)
if lang:
self.translated_description[lang] = text
else:
self.description = text
elif child.tag == 'grouplist':
self.parse_group_list(child)
elif child.tag == 'display_order':
self.display_order = parse_number(child.text)
def parse_group_list(self, grouplist_elem):
for child in grouplist_elem:
if child.tag == 'groupid':
groupid = child.text
self._groups[groupid] = 1
def add(self, obj):
"""Add another category object to this object"""
for grp in obj.groups:
self._groups[grp] = 1
# name and description translations
for lang in obj.translated_name:
if lang not in self.translated_name:
self.translated_name[lang] = obj.translated_name[lang]
for lang in obj.translated_description:
if lang not in self.translated_description:
self.translated_description[lang] = obj.translated_description[lang]
def xml(self):
"""write out an xml stanza for the category object"""
msg ="""
%s
%s\n""" % (self.categoryid, self.display_order)
msg +=""" %s\n""" % self.name
for (lang, val) in self.translated_name.items():
msg += """ %s\n""" % (lang, val)
msg += """ %s\n""" % self.description
for (lang, val) in self.translated_description.items():
msg += """ %s\n""" % (lang, val)
msg += """ \n"""
for grp in self.groups:
msg += """ %s\n""" % grp
msg += """ \n"""
msg += """ \n"""
return msg
class Langpacks(CompsObj):
def __init__(self, elem=None):
self.langpacks = []
self.name = "" # prevent CompsObj.__str__() throwing an AttributeError
if elem is not None:
self.parse(elem)
def __getitem__(self, indx):
return self.langpacks[indx]
def __iter__(self):
for i in self.langpacks:
yield i
def __len__(self):
return len(self.langpacks)
def add(self, name, install):
langpack = {
"name": name,
"install": install,
}
self.langpacks.append(langpack)
def parse(self, elem):
for child in elem:
if child.tag == "match":
langpack = {
"name": child.attrib.get("name"),
"install": child.attrib.get("install"),
}
self.langpacks.append(langpack)
else:
raise CompsException("Unexpected element in : %s" % child.tag)
self.name = elem.attrib.get("name")
self.install = elem.attrib.get("install")
def xml(self):
"""write out an xml stanza for the Langpacks object"""
if not self.langpacks:
return ''
msg = ' \n'
for i in self:
msg += ' \n' % (i["name"], i["install"])
msg += ' \n'
return msg
class Comps(object):
def __init__(self, overwrite_groups=False):
self._groups = {}
self._environments = {}
self._categories = {}
self._langpacks = Langpacks()
self.compscount = 0
self.overwrite_groups = overwrite_groups
self.compiled = False # have groups been compiled into avail/installed
# lists, yet.
def get_groups(self):
grps = self._groups.values()
grps.sort(key=lambda x: (x.display_order, x.name))
return grps
def get_environments(self):
environments = self._environments.values()
environments.sort(key=lambda x: (x.display_order, x.name))
return environments
def get_categories(self):
cats = self._categories.values()
cats.sort(key=lambda x: (x.display_order, x.name))
return cats
def get_langpacks(self):
return self._langpacks
groups = property(get_groups)
environments = property(get_environments)
categories = property(get_categories)
langpacks = property(get_langpacks)
def has_group(self, grpid):
exists = self.return_groups(grpid)
if exists:
return True
return False
def return_group(self, grpid):
"""Return the first group which matches"""
grps = self.return_groups(grpid)
if grps:
return grps[0]
return None
def return_groups(self, group_pattern, case_sensitive=False):
"""return all groups which match either by glob or exact match"""
returns = {}
if not group_pattern:
return []
for item in group_pattern.split(','):
item = item.strip()
if item in self._groups:
thisgroup = self._groups[item]
returns[thisgroup.groupid] = thisgroup
continue
if case_sensitive:
match = re.compile(fnmatch.translate(item)).match
else:
match = re.compile(fnmatch.translate(item), flags=re.I).match
done = False
for group in self.groups:
for name in group.name, group.groupid, group.ui_name:
if match(name):
done = True
returns[group.groupid] = group
break
if done:
continue
# If we didn't match to anything in the current locale, try others
for group in self.groups:
for name in group.translated_name.values():
if match(name):
returns[group.groupid] = group
break
return returns.values()
def has_environment(self, environmentid):
exists = self.return_environments(environmentid)
if exists:
return True
return False
def return_environment(self, environmentid):
"""Return the first group which matches"""
environments = self.return_environments(environmentid)
if environments:
return environments[0]
return None
def return_environments(self, env_pattern, case_sensitive=False):
"""return all environments which match either by glob or exact match"""
returns = {}
if not env_pattern:
return []
for item in env_pattern.split(','):
item = item.strip()
if item in self._environments:
env = self._environments[item]
returns[env.environmentid] = env
continue
if case_sensitive:
match = re.compile(fnmatch.translate(item)).match
else:
match = re.compile(fnmatch.translate(item), flags=re.I).match
done = False
for env in self.environments:
for name in env.name, env.environmentid, env.ui_name:
if match(name):
done = True
returns[env.environmentid] = env
break
if done:
continue
# If we didn't match to anything in the current locale, try others
for env in self.environments:
for name in env.translated_name.values():
if match(name):
returns[env.environmentid] = env
break
return returns.values()
# This is close to returnPackages() etc. API ... need to std. these names
# the above return_groups uses different, but equal, API.
def return_categories(self, pattern, ignore_case=True):
"""return all categories which match either by glob or exact match"""
returns = {}
for item in pattern.split(','):
item = item.strip()
if item in self._categories:
cat = self._categories[item]
returns[cat.categoryid] = cat
continue
if not ignore_case:
match = re.compile(fnmatch.translate(item)).match
else:
match = re.compile(fnmatch.translate(item), flags=re.I).match
done = False
for cat in self.categories:
for name in cat.name, cat.categoryid, cat.ui_name:
if match(name):
done = True
returns[cat.categoryid] = cat
break
if done:
continue
for cat in self.categories:
for name in cat.translated_name.values():
if match(name):
returns[cat.categoryid] = cat
break
return returns.values()
def add_group(self, group):
if group.groupid in self._groups:
thatgroup = self._groups[group.groupid]
if thatgroup._weak:
# If what we had was weak, use this one and merge the weak one.
tmp = group
group = thatgroup
thatgroup = self._groups[group.groupid] = tmp
thatgroup.add(group)
else:
self._groups[group.groupid] = group
def add_environment(self, environment):
if environment.environmentid in self._environments:
env = self._environments[environment.environmentid]
if env._weak:
# If what we had was weak, use this one and merge the weak one.
tmp = environment
environment = env
env = self._environments[environment.environmentid] = tmp
env.add(environment)
else:
self._environments[environment.environmentid] = environment
def add_category(self, category):
if category.categoryid in self._categories:
thatcat = self._categories[category.categoryid]
thatcat.add(category)
else:
self._categories[category.categoryid] = category
def add_langpack(self, name, install):
self._langpacks.add(name, install)
def add(self, srcfile = None):
if not srcfile:
raise CompsException
if type(srcfile) in types.StringTypes:
# srcfile is a filename string
try:
infile = open(srcfile, 'rt')
except IOError, e:
raise CompsException, 'open(%s): #%u %s' % (srcfile, e.errno, e.strerror)
else:
# srcfile is a file object
infile = srcfile
self.compscount += 1
self.compiled = False
parser = iterparse(infile)
try:
for event, elem in parser:
if elem.tag == "group":
group = Group(elem)
self.add_group(group)
if elem.tag == "environment":
environment = Environment(elem)
self.add_environment(environment)
if elem.tag == "category":
category = Category(elem)
self.add_category(category)
if elem.tag == "langpacks":
self._langpacks.parse(elem)
except SyntaxError, e:
raise CompsException, "comps file is empty/damaged"
del parser
def compile(self, pkgtuplist):
""" compile the groups into installed/available groups """
# convert the tuple list to a simple dict of pkgnames
inst_pkg_names = {}
for (n,a,e,v,r) in pkgtuplist:
inst_pkg_names[n] = 1
for group in self.groups:
# if there are mandatory packages in the group, then make sure
# they're all installed. if any are missing, then the group
# isn't installed.
if len(group.mandatory_packages) > 0:
group.installed = True
for pkgname in group.mandatory_packages:
if pkgname not in inst_pkg_names:
group.installed = False
break
# if it doesn't have any of those then see if it has ANY of the
# optional/default packages installed.
# If so - then the group is installed
else:
check_pkgs = group.optional_packages.keys() + group.default_packages.keys() + group.conditional_packages.keys()
group.installed = False
for pkgname in check_pkgs:
if pkgname in inst_pkg_names:
group.installed = True
break
# Now do basically the same thing for evgroups.
inst_grp_names = {}
for group in self.groups:
inst_grp_names[group.groupid] = group.installed
for evgroup in self.environments:
if evgroup.groups:
evgroup.installed = True
for grpname in evgroup.groups:
if not inst_grp_names.get(grpname):
evgroup.installed = False
break
else:
evgroup.installed = False
for grpname in evgroup.options:
if grpname in inst_grp_names:
evgroup.installed = True
break
self.compiled = True
def xml(self):
"""returns the xml of the comps files in this class, merged"""
if not self._groups and not self._categories and \
not self._environments and not len(self._langpacks):
return ""
msg = """
"""
for g in self.get_groups():
msg += g.xml()
for c in self.get_categories():
msg += c.xml()
for e in self.get_environments():
msg += e.xml()
msg += self.get_langpacks().xml()
msg += """\n\n"""
return msg
def main():
try:
print sys.argv[1]
p = Comps()
for srcfile in sys.argv[1:]:
p.add(srcfile)
print
print "===== GROUPS ====="
for group in p.groups:
print "%s (id: %s)" % (group, group.groupid)
for pkg in group.packages:
print ' ' + pkg
print
print "===== ENVIRONMENTS ====="
for environment in p.environments:
print "%s (id: %s)" % (environment.name, environment.environmentid)
for group in environment.groups:
print ' ' + group
for group in environment.options:
print ' *' + group
print
print "===== CATEGORIES ====="
for category in p.categories:
print "%s (id: %s)" % (category.name, category.categoryid)
for group in category.groups:
print ' ' + group
print
print "===== LANGPACKS ====="
for langpack in p.langpacks:
print ' %s (%s)' % (langpack["name"], langpack["install"])
except IOError:
print >> sys.stderr, "newcomps.py: No such file:\'%s\'" % sys.argv[1]
sys.exit(1)
if __name__ == '__main__':
main()