Package yum :: Module comps
[hide private]
[frames] | no frames]

Source Code for Module yum.comps

  1  # This program is free software; you can redistribute it and/or modify 
  2  # it under the terms of the GNU General Public License as published by 
  3  # the Free Software Foundation; either version 2 of the License, or 
  4  # (at your option) any later version. 
  5  # 
  6  # This program is distributed in the hope that it will be useful, 
  7  # but WITHOUT ANY WARRANTY; without even the implied warranty of 
  8  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
  9  # GNU Library General Public License for more details. 
 10  # 
 11  # You should have received a copy of the GNU General Public License 
 12  # along with this program; if not, write to the Free Software 
 13  # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 
 14  # Copyright 2005 Duke University 
 15   
 16  import types 
 17  import sys 
 18  from constants import * 
 19  try: 
 20      from xml.etree import cElementTree 
 21  except ImportError: 
 22      import cElementTree 
 23  iterparse = cElementTree.iterparse 
 24  from Errors import CompsException 
 25  #FIXME - compsexception isn't caught ANYWHERE so it's pointless to raise it 
 26  # switch all compsexceptions to grouperrors after api break 
 27  import fnmatch 
 28  import re 
 29  from yum.i18n import to_unicode 
 30  from misc import get_my_lang_code 
 31   
 32  lang_attr = '{http://www.w3.org/XML/1998/namespace}lang' 
33 34 -def parse_boolean(strng):
35 if BOOLEAN_STATES.has_key(strng.lower()): 36 return BOOLEAN_STATES[strng.lower()] 37 else: 38 return False
39
40 -def parse_number(strng):
41 return int(strng)
42
43 -class CompsObj(object):
44 """ Group/Category helper object. """ 45 46 # Could be the same as ui_name?
47 - def __str__(self):
48 """ Return the "name" of the object for the C locale. """ 49 return self.name
50 51 @property
52 - def ui_name(self):
53 """ Return the "name" of the object for the current locale. """ 54 return self.nameByLang(get_my_lang_code())
55 56 @property
57 - def ui_description(self):
58 """ Return the "description" of the object for the current locale. """ 59 return self.descriptionByLang(get_my_lang_code())
60
61 - def __cmp__(self, other):
62 if other is None: 63 return 1 64 65 if self.display_order > other.display_order: 66 return 1 67 if self.display_order < other.display_order: 68 return -1 69 70 return cmp(self.ui_name, other.ui_name)
71
72 - def _expand_languages(self, lang):
73 import gettext 74 languages = [lang] 75 76 if 'C' not in languages: 77 languages.append('C') 78 79 # now normalize and expand the languages 80 nelangs = [] 81 for lang in languages: 82 for nelang in gettext._expand_lang(lang): 83 if nelang not in nelangs: 84 nelangs.append(nelang) 85 return nelangs
86
87 - def nameByLang(self, lang):
88 89 for langcode in self._expand_languages(lang): 90 if langcode in self.translated_name: 91 return to_unicode(self.translated_name[langcode]) 92 93 return to_unicode(self.name)
94
95 - def descriptionByLang(self, lang):
96 for langcode in self._expand_languages(lang): 97 if langcode in self.translated_description: 98 return to_unicode(self.translated_description[langcode]) 99 return to_unicode(self.description)
100
101 102 -class Group(CompsObj):
103 """ Group object parsed from group data in each repo. and merged. """ 104
105 - def __init__(self, elem=None):
106 self.user_visible = True 107 self.default = False 108 self.selected = False 109 self.name = "" 110 self.description = "" 111 self.translated_name = {} 112 self.translated_description = {} 113 self.mandatory_packages = {} 114 self.optional_packages = {} 115 self.default_packages = {} 116 self.conditional_packages = {} 117 self.langonly = None ## what the hell is this? 118 self.groupid = None 119 self.display_order = 1024 120 self.installed = False 121 self.toremove = False 122 123 if elem: 124 self.parse(elem)
125
126 - def _packageiter(self):
127 # Gah, FIXME: real iterator/class 128 lst = self.mandatory_packages.keys() + \ 129 self.optional_packages.keys() + \ 130 self.default_packages.keys() + \ 131 self.conditional_packages.keys() 132 133 return lst
134 135 packages = property(_packageiter) 136
137 - def parse(self, elem):
138 for child in elem: 139 140 if child.tag == 'id': 141 myid = child.text 142 if self.groupid is not None: 143 raise CompsException 144 self.groupid = myid 145 146 elif child.tag == 'name': 147 text = child.text 148 if text: 149 text = text.encode('utf8') 150 151 lang = child.attrib.get(lang_attr) 152 if lang: 153 self.translated_name[lang] = text 154 else: 155 self.name = text 156 157 158 elif child.tag == 'description': 159 text = child.text 160 if text: 161 text = text.encode('utf8') 162 163 lang = child.attrib.get(lang_attr) 164 if lang: 165 self.translated_description[lang] = text 166 else: 167 if text: 168 self.description = text 169 170 elif child.tag == 'uservisible': 171 self.user_visible = parse_boolean(child.text) 172 173 elif child.tag == 'display_order': 174 self.display_order = parse_number(child.text) 175 176 elif child.tag == 'default': 177 self.default = parse_boolean(child.text) 178 179 elif child.tag in ['langonly', 'lang_only']: 180 text = child.text 181 if self.langonly is not None: 182 raise CompsException 183 self.langonly = text 184 185 elif child.tag == 'packagelist': 186 self.parse_package_list(child)
187
188 - def parse_package_list(self, packagelist_elem):
189 for child in packagelist_elem: 190 if child.tag == 'packagereq': 191 genre = child.attrib.get('type') 192 if not genre: 193 genre = u'mandatory' 194 195 if genre not in ('mandatory', 'default', 'optional', 'conditional'): 196 # just ignore bad package lines 197 continue 198 199 package = child.text 200 if genre == 'mandatory': 201 self.mandatory_packages[package] = 1 202 elif genre == 'default': 203 self.default_packages[package] = 1 204 elif genre == 'optional': 205 self.optional_packages[package] = 1 206 elif genre == 'conditional': 207 self.conditional_packages[package] = child.attrib.get('requires')
208 209 210
211 - def add(self, obj):
212 """Add another group object to this object""" 213 214 # we only need package lists and any translation that we don't already 215 # have 216 217 for pkg in obj.mandatory_packages: 218 self.mandatory_packages[pkg] = 1 219 for pkg in obj.default_packages: 220 self.default_packages[pkg] = 1 221 for pkg in obj.optional_packages: 222 self.optional_packages[pkg] = 1 223 for pkg in obj.conditional_packages: 224 self.conditional_packages[pkg] = obj.conditional_packages[pkg] 225 226 # Handle cases where a comps.xml without name & decription tags 227 # has been setup first, so the name & decription for this object is blank. 228 229 230 if self.name == '' and obj.name != '': 231 self.name = obj.name 232 233 if self.description == '' and obj.description != '': 234 self.description = obj.description 235 236 # name and description translations 237 for lang in obj.translated_name: 238 if not self.translated_name.has_key(lang): 239 self.translated_name[lang] = obj.translated_name[lang] 240 241 for lang in obj.translated_description: 242 if not self.translated_description.has_key(lang): 243 self.translated_description[lang] = obj.translated_description[lang]
244
245 - def xml(self):
246 """write out an xml stanza for the group object""" 247 msg =""" 248 <group> 249 <id>%s</id> 250 <default>%s</default> 251 <uservisible>%s</uservisible> 252 <display_order>%s</display_order>\n""" % (self.groupid, str(self.default).lower(), 253 str(self.user_visible).lower(), self.display_order) 254 255 if self.langonly: 256 msg += """ <langonly>%s</langonly>""" % self.langonly 257 258 msg +=""" <name>%s</name>\n""" % self.name 259 for (lang, val) in sorted(self.translated_name.items()): 260 msg += """ <name xml:lang="%s">%s</name>\n""" % (lang, val) 261 262 msg += """ <description>%s</description>\n""" % self.description 263 for (lang, val) in sorted(self.translated_description.items()): 264 msg += """ <description xml:lang="%s">%s</description>\n""" % (lang, val) 265 266 msg += """ <packagelist>\n""" 267 for pkg in sorted(self.mandatory_packages): 268 msg += """ <packagereq type="mandatory">%s</packagereq>\n""" % pkg 269 for pkg in sorted(self.default_packages): 270 msg += """ <packagereq type="default">%s</packagereq>\n""" % pkg 271 for pkg in sorted(self.optional_packages): 272 msg += """ <packagereq type="optional">%s</packagereq>\n""" % pkg 273 for (pkg, req) in sorted(self.conditional_packages.items()): 274 msg += """ <packagereq type="conditional" requires="%s">%s</packagereq>\n""" % (req, pkg) 275 msg += """ </packagelist>\n""" 276 msg += """ </group>""" 277 278 return msg
279
280 281 -class Category(CompsObj):
282 """ Category object parsed from group data in each repo. and merged. """ 283
284 - def __init__(self, elem=None):
285 self.name = "" 286 self.categoryid = None 287 self.description = "" 288 self.translated_name = {} 289 self.translated_description = {} 290 self.display_order = 1024 291 self._groups = {} 292 293 if elem: 294 self.parse(elem)
295
296 - def _groupiter(self):
297 return self._groups.keys()
298 299 groups = property(_groupiter) 300
301 - def parse(self, elem):
302 for child in elem: 303 if child.tag == 'id': 304 myid = child.text 305 if self.categoryid is not None: 306 raise CompsException 307 self.categoryid = myid 308 309 elif child.tag == 'name': 310 text = child.text 311 if text: 312 text = text.encode('utf8') 313 314 lang = child.attrib.get(lang_attr) 315 if lang: 316 self.translated_name[lang] = text 317 else: 318 self.name = text 319 320 elif child.tag == 'description': 321 text = child.text 322 if text: 323 text = text.encode('utf8') 324 325 lang = child.attrib.get(lang_attr) 326 if lang: 327 self.translated_description[lang] = text 328 else: 329 self.description = text 330 331 elif child.tag == 'grouplist': 332 self.parse_group_list(child) 333 334 elif child.tag == 'display_order': 335 self.display_order = parse_number(child.text)
336
337 - def parse_group_list(self, grouplist_elem):
338 for child in grouplist_elem: 339 if child.tag == 'groupid': 340 groupid = child.text 341 self._groups[groupid] = 1
342
343 - def add(self, obj):
344 """Add another category object to this object""" 345 346 for grp in obj.groups: 347 self._groups[grp] = 1 348 349 # name and description translations 350 for lang in obj.translated_name: 351 if not self.translated_name.has_key(lang): 352 self.translated_name[lang] = obj.translated_name[lang] 353 354 for lang in obj.translated_description: 355 if not self.translated_description.has_key(lang): 356 self.translated_description[lang] = obj.translated_description[lang]
357
358 - def xml(self):
359 """write out an xml stanza for the category object""" 360 msg =""" 361 <category> 362 <id>%s</id> 363 <display_order>%s</display_order>\n""" % (self.categoryid, self.display_order) 364 365 msg +=""" <name>%s</name>\n""" % self.name 366 for (lang, val) in self.translated_name.items(): 367 msg += """ <name xml:lang="%s">%s</name>\n""" % (lang, val) 368 369 msg += """ <description>%s</description>\n""" % self.description 370 for (lang, val) in self.translated_description.items(): 371 msg += """ <description xml:lang="%s">%s</description>\n""" % (lang, val) 372 373 msg += """ <grouplist>\n""" 374 for grp in self.groups: 375 msg += """ <groupid>%s</groupid>\n""" % grp 376 msg += """ </grouplist>\n""" 377 msg += """ </category>\n""" 378 379 return msg
380
381 382 -class Comps(object):
383 - def __init__(self, overwrite_groups=False):
384 self._groups = {} 385 self._categories = {} 386 self.compscount = 0 387 self.overwrite_groups = overwrite_groups 388 self.compiled = False # have groups been compiled into avail/installed
389 # lists, yet. 390 391
392 - def get_groups(self):
393 grps = self._groups.values() 394 grps.sort(key=lambda x: (x.display_order, x.name)) 395 return grps
396
397 - def get_categories(self):
398 cats = self._categories.values() 399 cats.sort(key=lambda x: (x.display_order, x.name)) 400 return cats
401 402 groups = property(get_groups) 403 categories = property(get_categories) 404
405 - def has_group(self, grpid):
406 exists = self.return_groups(grpid) 407 408 if exists: 409 return True 410 411 return False
412
413 - def return_group(self, grpid):
414 """Return the first group which matches""" 415 grps = self.return_groups(grpid) 416 if grps: 417 return grps[0] 418 419 return None
420
421 - def return_groups(self, group_pattern, case_sensitive=False):
422 """return all groups which match either by glob or exact match""" 423 returns = {} 424 425 for item in group_pattern.split(','): 426 item = item.strip() 427 if self._groups.has_key(item): 428 thisgroup = self._groups[item] 429 returns[thisgroup.groupid] = thisgroup 430 continue 431 432 if case_sensitive: 433 match = re.compile(fnmatch.translate(item)).match 434 else: 435 match = re.compile(fnmatch.translate(item), flags=re.I).match 436 437 done = False 438 for group in self.groups: 439 for name in group.name, group.groupid, group.ui_name: 440 if match(name): 441 done = True 442 returns[group.groupid] = group 443 break 444 if done: 445 continue 446 447 # If we didn't match to anything in the current locale, try others 448 for group in self.groups: 449 for name in group.translated_name.values(): 450 if match(name): 451 returns[group.groupid] = group 452 break 453 454 return returns.values()
455 456 # This is close to returnPackages() etc. API ... need to std. these names 457 # the above return_groups uses different, but equal, API.
458 - def return_categories(self, pattern, ignore_case=True):
459 """return all categories which match either by glob or exact match""" 460 returns = {} 461 462 for item in pattern.split(','): 463 item = item.strip() 464 if item in self._categories: 465 cat = self._categories[item] 466 returns[cat.categoryid] = cat 467 continue 468 469 if not ignore_case: 470 match = re.compile(fnmatch.translate(item)).match 471 else: 472 match = re.compile(fnmatch.translate(item), flags=re.I).match 473 474 done = False 475 for cat in self.categories: 476 for name in cat.name, cat.categoryid, cat.ui_name: 477 if match(name): 478 done = True 479 returns[cat.categoryid] = cat 480 break 481 if done: 482 continue 483 484 for cat in self.categories: 485 for name in cat.translated_name.values(): 486 if match(name): 487 returns[cat.categoryid] = cat 488 break 489 490 return returns.values()
491
492 - def add_group(self, group):
493 if self._groups.has_key(group.groupid): 494 thatgroup = self._groups[group.groupid] 495 thatgroup.add(group) 496 else: 497 self._groups[group.groupid] = group
498
499 - def add_category(self, category):
500 if self._categories.has_key(category.categoryid): 501 thatcat = self._categories[category.categoryid] 502 thatcat.add(category) 503 else: 504 self._categories[category.categoryid] = category
505
506 - def add(self, srcfile = None):
507 if not srcfile: 508 raise CompsException 509 510 if type(srcfile) in types.StringTypes: 511 # srcfile is a filename string 512 infile = open(srcfile, 'rt') 513 else: 514 # srcfile is a file object 515 infile = srcfile 516 517 self.compscount += 1 518 self.compiled = False 519 520 parser = iterparse(infile) 521 try: 522 for event, elem in parser: 523 if elem.tag == "group": 524 group = Group(elem) 525 self.add_group(group) 526 if elem.tag == "category": 527 category = Category(elem) 528 self.add_category(category) 529 except SyntaxError, e: 530 raise CompsException, "comps file is empty/damaged" 531 532 del parser
533
534 - def compile(self, pkgtuplist):
535 """ compile the groups into installed/available groups """ 536 537 # convert the tuple list to a simple dict of pkgnames 538 inst_pkg_names = {} 539 for (n,a,e,v,r) in pkgtuplist: 540 inst_pkg_names[n] = 1 541 542 543 for group in self.groups: 544 # if there are mandatory packages in the group, then make sure 545 # they're all installed. if any are missing, then the group 546 # isn't installed. 547 if len(group.mandatory_packages) > 0: 548 group.installed = True 549 for pkgname in group.mandatory_packages: 550 if pkgname not in inst_pkg_names: 551 group.installed = False 552 break 553 # if it doesn't have any of those then see if it has ANY of the 554 # optional/default packages installed. 555 # If so - then the group is installed 556 else: 557 check_pkgs = group.optional_packages.keys() + group.default_packages.keys() + group.conditional_packages.keys() 558 group.installed = False 559 for pkgname in check_pkgs: 560 if inst_pkg_names.has_key(pkgname): 561 group.installed = True 562 break 563 564 self.compiled = True
565
566 - def xml(self):
567 """returns the xml of the comps files in this class, merged""" 568 569 if not self._groups and not self._categories: 570 return "" 571 572 msg = """<?xml version="1.0" encoding="UTF-8"?> 573 <!DOCTYPE comps PUBLIC "-//Red Hat, Inc.//DTD Comps info//EN" "comps.dtd"> 574 <comps> 575 """ 576 577 for g in self.get_groups(): 578 msg += g.xml() 579 for c in self.get_categories(): 580 msg += c.xml() 581 582 msg += """\n</comps>\n""" 583 584 return msg
585
586 587 588 -def main():
589 590 try: 591 print sys.argv[1] 592 p = Comps() 593 for srcfile in sys.argv[1:]: 594 p.add(srcfile) 595 596 for group in p.groups: 597 print group 598 for pkg in group.packages: 599 print ' ' + pkg 600 601 for category in p.categories: 602 print category.name 603 for group in category.groups: 604 print ' ' + group 605 606 except IOError: 607 print >> sys.stderr, "newcomps.py: No such file:\'%s\'" % sys.argv[1] 608 sys.exit(1)
609 610 if __name__ == '__main__': 611 main() 612