extended atom syntax: Allow wildcards in all places
authorSebastian Luther <SebastianLuther@gmx.de>
Sat, 24 Jul 2010 18:19:56 +0000 (20:19 +0200)
committerZac Medico <zmedico@gentoo.org>
Sat, 24 Jul 2010 21:07:21 +0000 (14:07 -0700)
pym/portage/dep/__init__.py
pym/portage/package/ebuild/config.py
pym/portage/sets/base.py
pym/portage/tests/dep/testAtom.py
pym/portage/tests/dep/testExtendedAtomDict.py [new file with mode: 0644]
pym/portage/tests/dep/test_best_match_to_list.py

index e6f2362b11f676f5c5d210ee4a9f91c566923ecc..c276edbea1f86a90b78bd554beeb6a40e6d3e2cd 100644 (file)
@@ -33,7 +33,7 @@ import portage.exception
 from portage.exception import InvalidData, InvalidAtom
 from portage.localization import _
 from portage.versions import catpkgsplit, catsplit, \
-       pkgcmp, pkgsplit, ververify, _cat, _pkg, _cp, _cpv
+       pkgcmp, pkgsplit, ververify, _cp, _cpv
 import portage.cache.mappings
 
 if sys.hexversion >= 0x3000000:
@@ -602,15 +602,20 @@ class Atom(_atom_base):
                        blocker = False
                self.__dict__['blocker'] = blocker
                m = _atom_re.match(s)
+               extended_syntax = False
                if m is None:
                        if allow_wildcard:
                                m = _atom_wildcard_re.match(s)
                                if m is None:
                                        raise InvalidAtom(self)
                                op = None
-                               cpv = cp = m.groupdict()['simple']
-                               slot = None
+                               gdict = m.groupdict()
+                               cpv = cp = gdict['simple']
+                               if cpv.find("**") != -1:
+                                       raise InvalidAtom(self)
+                               slot = gdict['slot']
                                use_str = None
+                               extended_syntax = True
                        else:
                                raise InvalidAtom(self)
                elif m.group('op') is not None:
@@ -644,6 +649,7 @@ class Atom(_atom_base):
                self.__dict__['cpv'] = cpv
                self.__dict__['slot'] = slot
                self.__dict__['operator'] = op
+               self.__dict__['extended_syntax'] = extended_syntax
 
                if use_str is not None:
                        use = _use_dep(dep_getusedeps(s))
@@ -748,6 +754,107 @@ class Atom(_atom_base):
                memo[id(self)] = self
                return self
 
+def extended_cp_match(extended_atom, other):
+       """
+       Checks if an extended syntax atom matches the other, non extended atom
+       """
+       my_slot = dep_getslot(extended_atom)
+       if my_slot is not None:
+               extended_atom = extended_atom[:-(len(my_slot)+1)]
+       mysplit = catsplit(extended_atom)
+       my_cat = mysplit[0]
+       my_pkg = mysplit[1]
+
+       other_slot = dep_getslot(other)
+       if other_slot is not None:
+               other = other[:-(len(other_slot)+1)]
+       othersplit = catsplit(other)
+       other_cat = othersplit[0]
+       other_pkg = othersplit[1]
+
+       if my_slot is not None and other_slot is not None and \
+               my_slot != other_slot:
+               return False
+
+       for my_val, other_val in ((my_cat, other_cat), (my_pkg, other_pkg)):
+               if my_val == "*":
+                       continue
+
+               start = 0
+               parts = my_val.split("*")
+               for id, part in enumerate(parts):
+                       if not part:
+                               if id == len(parts)-1:
+                                       start = len(other_val)
+                               continue
+                       start = other_val.find(part, start)
+                       if start == -1:
+                               return False
+
+                       start += len(part)
+
+               if start != len(other_val):
+                       return False
+
+       return True
+
+class ExtendedAtomDict(object):
+       """
+       dict() wrapper that supports extended atoms as keys and allows lookup
+       of a normal cp against other normal cp and extended cp.
+       The value type has to be given to __init__ and is assumed to be the same
+       for all values.
+       """
+       def __init__(self, value_class):
+               self._extended = {}
+               self._normal = {}
+               self._value_class = value_class
+
+       def setdefault(self, cp, default=None):
+               if "*" in cp:
+                       return self._extended.setdefault(cp, default)
+               else:
+                       return self._normal.setdefault(cp, default)
+
+       def get(self, cp):
+               ret = self._value_class()
+               normal_match = self._normal.get(cp)
+               if normal_match is not None:
+                       if hasattr(ret, "update"):
+                               ret.update(normal_match)
+                       elif hasattr(ret, "extend"):
+                               ret.extend(normal_match)
+                       else:
+                               raise NotImplementedError()
+
+               for extended_cp in self._extended:
+                       if extended_cp_match(extended_cp, cp):
+                               if hasattr(ret, "update"):
+                                       ret.update(self._extended[extended_cp])
+                               elif hasattr(ret, "extend"):
+                                       ret.extend(self._extended[extended_cp])
+                               else:
+                                       raise NotImplementedError()
+
+               return ret
+
+       def __setitem__(self, cp, val):
+               if "*" in cp:
+                       self._extended[cp] = val
+               else:
+                       self._normal[cp] = val
+
+       def __getitem__(self, cp):
+               if "*" in cp:
+                       return self._extended[cp]
+               else:
+                       return self._normal[cp]
+
+       def clear(self):
+               self._extended.clear()
+               self._normal.clear()
+
+
 def get_operator(mydep):
        """
        Return the operator used in a depstring.
@@ -945,7 +1052,11 @@ _atom_re = re.compile('^(?P<without_use>(?:' +
        '(?P<op>' + _op + _cpv + ')|' +
        '(?P<star>=' + _cpv + r'\*)|' +
        '(?P<simple>' + _cp + '))(:' + _slot + ')?)(' + _use + ')?$', re.VERBOSE)
-_atom_wildcard_re = re.compile('(?P<simple>((' + _cat + '|\*)/(' + _pkg + '|\*)))$')
+       
+_extended_cat = r'[\w+*][\w+.*-]*'
+_extended_pkg = r'[\w+*][\w+*-]*?'
+
+_atom_wildcard_re = re.compile('(?P<simple>(' + _extended_cat + ')/(' + _extended_pkg + '))(:(?P<slot>' + _slot + '))?$')
 
 def isvalidatom(atom, allow_blockers=False, allow_wildcard=False):
        """
@@ -1103,30 +1214,29 @@ def best_match_to_list(mypkg, mylist):
                - >=cpv     2
                - <=cpv     2
                - cp        1
-               - */p           0
-               - c/*           0
-               - */*           -1
+               - cp:slot with extended syntax  0
+               - cp with extended syntax       -1
        """
        operator_values = {'=':6, '~':5, '=*':4,
                '>':2, '<':2, '>=':2, '<=':2, None:1}
        maxvalue = -2
        bestm  = None
        for x in match_to_list(mypkg, mylist):
+               if x.extended_syntax:
+                       if dep_getslot(x) is not None:
+                               if maxvalue < 0:
+                                       maxvalue = 0
+                                       bestm = x
+                       else:
+                               if maxvalue < -1:
+                                       maxvalue = -1
+                                       bestm = x
+                       continue
                if dep_getslot(x) is not None:
                        if maxvalue < 3:
                                maxvalue = 3
                                bestm = x
                op_val = operator_values[x.operator]
-               if x.operator is None:
-                       c, p = catsplit(x)
-                       if c == "*":
-                               if p == "*":
-                                       op_val = -1
-                               else:
-                                       op_val = 0
-                       elif p == "*":
-                               op_val = 0
-                               
                if op_val > maxvalue:
                        maxvalue = op_val
                        bestm  = x
@@ -1183,18 +1293,14 @@ def match_from_list(mydep, candidate_list):
                        if cp is None:
                                mysplit = catpkgsplit(remove_slot(x))
                                if mysplit is not None:
-                                       c = mysplit[0]
-                                       p = mysplit[1]
-                               else:
-                                       continue
-                       else:
-                               mysplit = catsplit(cp)
-                               c = mysplit[0]
-                               p = mysplit[1]
+                                       cp = mysplit[0] + '/' + mysplit[1]
+
+                       if cp is None:
+                               continue
 
-                       if cat in (c, "*") and pkg in (p, "*"):
+                       if cp == mycpv or (mydep.extended_syntax and \
+                               extended_cp_match(mycpv, cp)):
                                mylist.append(x)
-                       
 
        elif operator == "=": # Exact match
                for x in candidate_list:
@@ -1270,7 +1376,7 @@ def match_from_list(mydep, candidate_list):
        else:
                raise KeyError(_("Unknown operator: %s") % mydep)
 
-       if slot is not None:
+       if slot is not None and not mydep.extended_syntax:
                candidate_list = mylist
                mylist = []
                for x in candidate_list:
index c7aa42d2094928ddaeb8c72bd1d8a88abf00ddb5..a58cd0c3b5ed4a3300fce3190d69dfde22fddb9f 100644 (file)
@@ -771,11 +771,11 @@ class config(object):
                        self["EROOT"] = target_root
                        self.backup_changes("EROOT")
 
-                       self.pusedict = {}
-                       self.pkeywordsdict = {}
-                       self._plicensedict = {}
-                       self._ppropertiesdict = {}
-                       self.punmaskdict = {}
+                       self.pusedict = portage.dep.ExtendedAtomDict(dict)
+                       self.pkeywordsdict = portage.dep.ExtendedAtomDict(dict)
+                       self._plicensedict = portage.dep.ExtendedAtomDict(dict)
+                       self._ppropertiesdict = portage.dep.ExtendedAtomDict(dict)
+                       self.punmaskdict = portage.dep.ExtendedAtomDict(list)
                        abs_user_config = os.path.join(config_root, USER_CONFIG_PATH)
 
                        # locations for "categories" and "arch.list" files
@@ -922,7 +922,7 @@ class config(object):
                        pkgmasklines = stack_lists(pkgmasklines, incremental=1)
                        pkgunmasklines = stack_lists(pkgunmasklines, incremental=1)
 
-                       self.pmaskdict = {}
+                       self.pmaskdict = portage.dep.ExtendedAtomDict(list)
                        for x in pkgmasklines:
                                self.pmaskdict.setdefault(x.cp, []).append(x)
 
@@ -1497,11 +1497,7 @@ class config(object):
                        has_changed = True
                oldpuse = self.puse
                self.puse = ""
-               cpdict = {}
-               cpdict.update(self.pusedict.get("*/*", {}))
-               cpdict.update(self.pusedict.get(cat+"/*", {}))
-               cpdict.update(self.pusedict.get("*/"+cp.split("/")[1], {}))
-               cpdict.update(self.pusedict.get(cp, {}))
+               cpdict = self.pusedict.get(cp)
                if cpdict:
                        keys = list(cpdict)
                        while keys:
@@ -1714,19 +1710,10 @@ class config(object):
                """
 
                cp = cpv_getkey(cpv)
-               c, p = catsplit(cp)
-               mask_atoms = []
-               mask_atoms.extend(self.pmaskdict.get("*/*", []))
-               mask_atoms.extend(self.pmaskdict.get(c+"/*", []))
-               mask_atoms.extend(self.pmaskdict.get("*/"+p, []))
-               mask_atoms.extend(self.pmaskdict.get(cp, []))
+               mask_atoms = self.pmaskdict.get(cp)
                if mask_atoms:
                        pkg_list = ["%s:%s" % (cpv, metadata["SLOT"])]
-                       unmask_atoms = []
-                       unmask_atoms.extend(self.punmaskdict.get("*/*", []))
-                       unmask_atoms.extend(self.punmaskdict.get(c+"/*", []))
-                       unmask_atoms.extend(self.punmaskdict.get("*/"+p, []))
-                       unmask_atoms.extend(self.punmaskdict.get(cp, []))
+                       unmask_atoms = self.punmaskdict.get(cp)
                        for x in mask_atoms:
                                if not match_from_list(x, pkg_list):
                                        continue
@@ -1874,12 +1861,7 @@ class config(object):
                """
                accept_license = self._accept_license
                cp = cpv_getkey(cpv)
-               c, p = catsplit(cp)
-               cpdict = {}
-               cpdict.update(self._plicensedict.get("*/*", {}))
-               cpdict.update(self._plicensedict.get(c+"/*", {}))
-               cpdict.update(self._plicensedict.get("*/"+p, {}))
-               cpdict.update(self._plicensedict.get(cp, {}))
+               cpdict = self._plicensedict.get(cp)
                if cpdict:
                        accept_license = list(self._accept_license)
                        cpv_slot = "%s:%s" % (cpv, metadata["SLOT"])
@@ -1959,12 +1941,7 @@ class config(object):
                """
                accept_properties = self._accept_properties
                cp = cpv_getkey(cpv)
-               c, p = catsplit(cp)
-               cpdict = {}
-               cpdict.update(self._ppropertiesdict.get("*/*", {}))
-               cpdict.update(self._ppropertiesdict.get(c+"/*", {}))
-               cpdict.update(self._ppropertiesdict.get("*/"+p, {}))
-               cpdict.update(self._ppropertiesdict.get(cp, {}))
+               cpdict = self._ppropertiesdict.get(cp)
                if cpdict:
                        accept_properties = list(self._accept_properties)
                        cpv_slot = "%s:%s" % (cpv, metadata["SLOT"])
index 108dda3ec42aa7680a9ec94924b579a8c130e15c..a143b8272722f5053f5d86d4e69f477010624a9a 100644 (file)
@@ -2,7 +2,7 @@
 # Distributed under the terms of the GNU General Public License v2
 
 import sys
-from portage.dep import Atom, best_match_to_list, match_from_list
+from portage.dep import Atom, ExtendedAtomDict, best_match_to_list, match_from_list
 from portage.exception import InvalidAtom
 from portage.versions import catsplit, cpv_getkey
 
@@ -21,7 +21,7 @@ class PackageSet(object):
        
        def __init__(self):
                self._atoms = set()
-               self._atommap = {}
+               self._atommap = ExtendedAtomDict(set)
                self._loaded = False
                self._loading = False
                self.errors = []
@@ -140,15 +140,9 @@ class PackageSet(object):
                """
                cpv_slot_list = [pkg]
                cp = cpv_getkey(pkg.cpv)
-               c, p = catsplit(cp)
                self._load() # make sure the atoms are loaded
 
-               atoms = set()
-               atoms.update(self._atommap.get("*/*", set()))
-               atoms.update(self._atommap.get(c+"/*", set()))
-               atoms.update(self._atommap.get("*/"+p, set()))
-               atoms.update(self._atommap.get(cp, set()))
-
+               atoms = self._atommap.get(cp)
                if atoms:
                        for atom in atoms:
                                if match_from_list(atom, cpv_slot_list):
index 091cef3a21319f07d25efe8e0921855c83ca35db..a4972905c4c8929abe143dfab870bebb662b15da 100644 (file)
@@ -27,7 +27,11 @@ class TestAtom(TestCase):
                          ( "sys-apps/*",
                                (None,  'sys-apps/*', None, None, None), True ),
                          ( "*/portage",
-                               (None,  '*/portage', None, None, None), True )
+                               (None,  '*/portage', None, None, None), True ),
+                         ( "s*s-*/portage:1",
+                               (None,  's*s-*/portage', None, '1', None), True ),
+                         ( "*/po*ge:2",
+                               (None,  '*/po*ge', None, '2', None), True ),
                ]
                
                tests_xfail = [
diff --git a/pym/portage/tests/dep/testExtendedAtomDict.py b/pym/portage/tests/dep/testExtendedAtomDict.py
new file mode 100644 (file)
index 0000000..702bec1
--- /dev/null
@@ -0,0 +1,18 @@
+# test_isvalidatom.py -- Portage Unit Testing Functionality
+# Copyright 2006 Gentoo Foundation
+# Distributed under the terms of the GNU General Public License v2
+
+from portage.tests import TestCase
+from portage.dep import ExtendedAtomDict
+
+class TestExtendedAtomDict(TestCase):
+
+       def testExtendedAtomDict(self):
+               d = ExtendedAtomDict(dict)
+               d["*/*"] = { "test1": "x" }
+               d["dev-libs/*"] = { "test2": "y" }
+               d.setdefault("sys-apps/portage", {})["test3"] = "z"
+               self.assertEqual(d.get("dev-libs/A"), { "test1": "x", "test2": "y" })
+               self.assertEqual(d.get("sys-apps/portage"), { "test1": "x", "test3": "z" })
+               self.assertEqual(d["dev-libs/*"], { "test2": "y" })
+               self.assertEqual(d["sys-apps/portage"], { "test3": "z" })
index c5f4fbbc0487d8fde91bff1625f528db8393a950..d050adc51147f81843d8e3dcb51be1931e0b0974 100644 (file)
@@ -31,12 +31,12 @@ class Test_best_match_to_list(TestCase):
                                                [Atom("=dev-libs/A-1:0")]),
                                        ("dev-libs/A-1", [Atom("dev-libs/*", allow_wildcard=True), Atom("=dev-libs/A-1:0")], \
                                                [Atom("=dev-libs/A-1:0"), Atom("dev-libs/*", allow_wildcard=True)]),
-                                       ("dev-libs/A-1", [Atom("*/*", allow_wildcard=True), Atom("dev-libs/*", allow_wildcard=True), \
+                                       ("dev-libs/A-1:0", [Atom("dev-*/*", allow_wildcard=True), Atom("dev-*/*:0", allow_wildcard=True),\
                                                Atom("dev-libs/A"), Atom("<=dev-libs/A-2"), Atom("dev-libs/A:0"), \
                                                Atom("=dev-libs/A-1*"), Atom("~dev-libs/A-1"), Atom("=dev-libs/A-1")], \
                                                [Atom("=dev-libs/A-1"), Atom("~dev-libs/A-1"), Atom("=dev-libs/A-1*"), \
                                                Atom("dev-libs/A:0"), Atom("<=dev-libs/A-2"), Atom("dev-libs/A"), \
-                                               Atom("dev-libs/*", allow_wildcard=True), Atom("*/*", allow_wildcard=True)])
+                                               Atom("dev-*/*:0", allow_wildcard=True), Atom("dev-*/*", allow_wildcard=True)])
                                ]
 
                for pkg, atom_list, result in tests: