aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlexander Bersenev <bay@hackerdom.ru>2014-02-17 17:57:05 +0600
committerAlexander Bersenev <bay@hackerdom.ru>2014-02-17 17:57:05 +0600
commit6563293d18daed502ccdb663f3c72b4bae5fe23a (patch)
treed0a7d53a7c137feb4073c963408829f88ea75c92 /portage_with_autodep/pym/portage/dep/__init__.py
parentupdated portage to 2.2.8-r1 (diff)
downloadautodep-master.tar.gz
autodep-master.tar.bz2
autodep-master.zip
updated portage to 2.2.8-r1HEADmaster
Diffstat (limited to 'portage_with_autodep/pym/portage/dep/__init__.py')
-rw-r--r--portage_with_autodep/pym/portage/dep/__init__.py740
1 files changed, 509 insertions, 231 deletions
diff --git a/portage_with_autodep/pym/portage/dep/__init__.py b/portage_with_autodep/pym/portage/dep/__init__.py
index 152af0a..798903f 100644
--- a/portage_with_autodep/pym/portage/dep/__init__.py
+++ b/portage_with_autodep/pym/portage/dep/__init__.py
@@ -1,7 +1,9 @@
# deps.py -- Portage dependency resolution functions
-# Copyright 2003-2012 Gentoo Foundation
+# Copyright 2003-2013 Gentoo Foundation
# Distributed under the terms of the GNU General Public License v2
+from __future__ import unicode_literals
+
__all__ = [
'Atom', 'best_match_to_list', 'cpvequal',
'dep_getcpv', 'dep_getkey', 'dep_getslot',
@@ -13,20 +15,6 @@ __all__ = [
'_repo_separator', '_slot_separator',
]
-# DEPEND SYNTAX:
-#
-# 'use?' only affects the immediately following word!
-# Nesting is the only legal way to form multiple '[!]use?' requirements.
-#
-# Where: 'a' and 'b' are use flags, and 'z' is a depend atom.
-#
-# "a? z" -- If 'a' in [use], then b is valid.
-# "a? ( z )" -- Syntax with parenthesis.
-# "a? b? z" -- Deprecated.
-# "a? ( b? z )" -- Valid
-# "a? ( b? ( z ) ) -- Valid
-#
-
import re, sys
import warnings
from itertools import chain
@@ -36,23 +24,164 @@ portage.proxy.lazyimport.lazyimport(globals(),
'portage.util:cmp_sort_key,writemsg',
)
-from portage import _unicode_decode
-from portage.eapi import eapi_has_slot_deps, eapi_has_src_uri_arrows, \
- eapi_has_use_deps, eapi_has_strong_blocks, eapi_has_use_dep_defaults, \
- eapi_has_repo_deps, eapi_allows_dots_in_PN, eapi_allows_dots_in_use_flags
+from portage import _encodings, _unicode_decode, _unicode_encode
+from portage.eapi import _get_eapi_attrs
from portage.exception import InvalidAtom, InvalidData, InvalidDependString
from portage.localization import _
from portage.versions import catpkgsplit, catsplit, \
- vercmp, ververify, _cp, _cpv, _pkg_str, _unknown_repo
+ vercmp, ververify, _cp, _cpv, _pkg_str, _slot, _unknown_repo, _vr
import portage.cache.mappings
if sys.hexversion >= 0x3000000:
basestring = str
+ _unicode = str
+else:
+ _unicode = unicode
+
+# \w is [a-zA-Z0-9_]
+
+# PMS 3.1.3: A slot name may contain any of the characters [A-Za-z0-9+_.-].
+# It must not begin with a hyphen or a dot.
+_slot_separator = ":"
+# loosly match SLOT, which may have an optional ABI part
+_slot_loose = r'([\w+./*=-]+)'
+
+_use = r'\[.*\]'
+_op = r'([=~]|[><]=?)'
+
+_repo_separator = "::"
+_repo_name = r'[\w][\w-]*'
+_repo_name_re = re.compile('^' + _repo_name + '$', re.UNICODE)
+_repo = r'(?:' + _repo_separator + '(' + _repo_name + ')' + ')?'
+
+_extended_cat = r'[\w+*][\w+.*-]*'
+
+_slot_dep_re_cache = {}
+
+def _get_slot_dep_re(eapi_attrs):
+ cache_key = eapi_attrs.slot_operator
+ slot_re = _slot_dep_re_cache.get(cache_key)
+ if slot_re is not None:
+ return slot_re
+
+ if eapi_attrs.slot_operator:
+ slot_re = _slot + r'?(\*|=|/' + _slot + r'=?)?'
+ else:
+ slot_re = _slot
+
+ slot_re = re.compile('^' + slot_re + '$', re.VERBOSE | re.UNICODE)
+
+ _slot_dep_re_cache[cache_key] = slot_re
+ return slot_re
+
+def _match_slot(atom, pkg):
+ if pkg.slot == atom.slot:
+ if not atom.sub_slot:
+ return True
+ elif atom.sub_slot == pkg.sub_slot:
+ return True
+ return False
+
+_atom_re_cache = {}
+
+def _get_atom_re(eapi_attrs):
+ cache_key = eapi_attrs.dots_in_PN
+ atom_re = _atom_re_cache.get(cache_key)
+ if atom_re is not None:
+ return atom_re
+
+ if eapi_attrs.dots_in_PN:
+ cp_re = _cp['dots_allowed_in_PN']
+ cpv_re = _cpv['dots_allowed_in_PN']
+ else:
+ cp_re = _cp['dots_disallowed_in_PN']
+ cpv_re = _cpv['dots_disallowed_in_PN']
+
+ atom_re = re.compile('^(?P<without_use>(?:' +
+ '(?P<op>' + _op + cpv_re + ')|' +
+ '(?P<star>=' + cpv_re + r'\*)|' +
+ '(?P<simple>' + cp_re + '))' +
+ '(' + _slot_separator + _slot_loose + ')?' +
+ _repo + ')(' + _use + ')?$', re.VERBOSE | re.UNICODE)
+
+ _atom_re_cache[cache_key] = atom_re
+ return atom_re
+
+_atom_wildcard_re_cache = {}
+
+def _get_atom_wildcard_re(eapi_attrs):
+ cache_key = eapi_attrs.dots_in_PN
+ atom_re = _atom_wildcard_re_cache.get(cache_key)
+ if atom_re is not None:
+ return atom_re
+
+ if eapi_attrs.dots_in_PN:
+ pkg_re = r'[\w+*][\w+.*-]*?'
+ else:
+ pkg_re = r'[\w+*][\w+*-]*?'
+
+ atom_re = re.compile(r'((?P<simple>(' +
+ _extended_cat + r')/(' + pkg_re + r'(-' + _vr + ')?))' + \
+ '|(?P<star>=((' + _extended_cat + r')/(' + pkg_re + r'))-(?P<version>\*\w+\*)))' + \
+ '(:(?P<slot>' + _slot_loose + r'))?(' +
+ _repo_separator + r'(?P<repo>' + _repo_name + r'))?$', re.UNICODE)
+
+ _atom_wildcard_re_cache[cache_key] = atom_re
+ return atom_re
+
+_usedep_re_cache = {}
+
+def _get_usedep_re(eapi_attrs):
+ """
+ @param eapi_attrs: The EAPI attributes from _get_eapi_attrs
+ @type eapi_attrs: _eapi_attrs
+ @rtype: regular expression object
+ @return: A regular expression object that matches valid USE deps for the
+ given eapi.
+ """
+ cache_key = eapi_attrs.dots_in_use_flags
+ usedep_re = _usedep_re_cache.get(cache_key)
+ if usedep_re is not None:
+ return usedep_re
+
+ if eapi_attrs.dots_in_use_flags:
+ _flag_re = r'[A-Za-z0-9][A-Za-z0-9+_@.-]*'
+ else:
+ _flag_re = r'[A-Za-z0-9][A-Za-z0-9+_@-]*'
+
+ usedep_re = re.compile(r'^(?P<prefix>[!-]?)(?P<flag>' +
+ _flag_re + r')(?P<default>(\(\+\)|\(\-\))?)(?P<suffix>[?=]?)$')
+
+ _usedep_re_cache[cache_key] = usedep_re
+ return usedep_re
-# Api consumers included in portage should set this to True.
-# Once the relevant api changes are in a portage release with
-# stable keywords, make these warnings unconditional.
-_internal_warnings = False
+_useflag_re_cache = {}
+
+def _get_useflag_re(eapi):
+ """
+ When eapi is None then validation is not as strict, since we want the
+ same to work for multiple EAPIs that may have slightly different rules.
+ @param eapi: The EAPI
+ @type eapi: String or None
+ @rtype: regular expression object
+ @return: A regular expression object that matches valid USE flags for the
+ given eapi.
+ """
+ eapi_attrs = _get_eapi_attrs(eapi)
+ cache_key = eapi_attrs.dots_in_use_flags
+ useflag_re = _useflag_re_cache.get(cache_key)
+ if useflag_re is not None:
+ return useflag_re
+
+ if eapi_attrs.dots_in_use_flags:
+ flag_re = r'[A-Za-z0-9][A-Za-z0-9+_@.-]*'
+ else:
+ flag_re = r'[A-Za-z0-9][A-Za-z0-9+_@-]*'
+
+ useflag_re = re.compile(r'^' + flag_re + r'$')
+
+ _useflag_re_cache[cache_key] = useflag_re
+ return useflag_re
def cpvequal(cpv1, cpv2):
"""
@@ -109,7 +238,7 @@ def strip_empty(myarr):
('portage.dep.strip_empty',), DeprecationWarning, stacklevel=2)
return [x for x in myarr if x]
-def paren_reduce(mystr):
+def paren_reduce(mystr, _deprecation_warn=True):
"""
Take a string and convert all paren enclosed entities into sublists and
split the list elements by spaces. All redundant brackets are removed.
@@ -123,7 +252,7 @@ def paren_reduce(mystr):
@rtype: Array
@return: The reduced string in an array
"""
- if _internal_warnings:
+ if portage._internal_caller and _deprecation_warn:
warnings.warn(_("%s is deprecated and will be removed without replacement.") % \
('portage.dep.paren_reduce',), DeprecationWarning, stacklevel=2)
mysplit = mystr.split()
@@ -215,7 +344,7 @@ class paren_normalize(list):
"""Take a dependency structure as returned by paren_reduce or use_reduce
and generate an equivalent structure that has no redundant lists."""
def __init__(self, src):
- if _internal_warnings:
+ if portage._internal_caller:
warnings.warn(_("%s is deprecated and will be removed without replacement.") % \
('portage.dep.paren_normalize',), DeprecationWarning, stacklevel=2)
list.__init__(self)
@@ -250,7 +379,7 @@ class paren_normalize(list):
self._zap_parens(x, dest)
return dest
-def paren_enclose(mylist, unevaluated_atom=False):
+def paren_enclose(mylist, unevaluated_atom=False, opconvert=False):
"""
Convert a list to a string with sublists enclosed with parens.
@@ -267,7 +396,10 @@ def paren_enclose(mylist, unevaluated_atom=False):
mystrparts = []
for x in mylist:
if isinstance(x, list):
- mystrparts.append("( "+paren_enclose(x)+" )")
+ if opconvert and x and x[0] == "||":
+ mystrparts.append("%s ( %s )" % (x[0], paren_enclose(x[1:])))
+ else:
+ mystrparts.append("( %s )" % paren_enclose(x))
else:
if unevaluated_atom:
x = getattr(x, 'unevaluated_atom', x)
@@ -308,7 +440,7 @@ def use_reduce(depstr, uselist=[], masklist=[], matchall=False, excludeall=[], i
@return: The use reduced depend array
"""
if isinstance(depstr, list):
- if _internal_warnings:
+ if portage._internal_caller:
warnings.warn(_("Passing paren_reduced dep arrays to %s is deprecated. " + \
"Pass the original dep string instead.") % \
('portage.dep.use_reduce',), DeprecationWarning, stacklevel=2)
@@ -320,6 +452,7 @@ def use_reduce(depstr, uselist=[], masklist=[], matchall=False, excludeall=[], i
if matchall and matchnone:
raise ValueError("portage.dep.use_reduce: 'matchall' and 'matchnone' are mutually exclusive")
+ eapi_attrs = _get_eapi_attrs(eapi)
useflag_re = _get_useflag_re(eapi)
def is_active(conditional):
@@ -535,7 +668,7 @@ def use_reduce(depstr, uselist=[], masklist=[], matchall=False, excludeall=[], i
if not is_src_uri:
raise InvalidDependString(
_("SRC_URI arrow are only allowed in SRC_URI: token %s") % (pos+1,))
- if eapi is None or not eapi_has_src_uri_arrows(eapi):
+ if not eapi_attrs.src_uri_arrows:
raise InvalidDependString(
_("SRC_URI arrow not allowed in EAPI %s: token %s") % (eapi, pos+1))
need_simple_token = True
@@ -608,7 +741,7 @@ def dep_opconvert(deplist):
@return:
The new list with the new ordering
"""
- if _internal_warnings:
+ if portage._internal_caller:
warnings.warn(_("%s is deprecated. Use %s with the opconvert parameter set to True instead.") % \
('portage.dep.dep_opconvert', 'portage.dep.use_reduce'), DeprecationWarning, stacklevel=2)
@@ -639,7 +772,7 @@ def flatten(mylist):
@rtype: List
@return: A single list containing only non-list elements.
"""
- if _internal_warnings:
+ if portage._internal_caller:
warnings.warn(_("%s is deprecated and will be removed without replacement.") % \
('portage.dep.flatten',), DeprecationWarning, stacklevel=2)
@@ -651,30 +784,9 @@ def flatten(mylist):
newlist.append(x)
return newlist
-
-_usedep_re = {
- "dots_disallowed_in_use_flags": re.compile("^(?P<prefix>[!-]?)(?P<flag>[A-Za-z0-9][A-Za-z0-9+_@-]*)(?P<default>(\(\+\)|\(\-\))?)(?P<suffix>[?=]?)$"),
- "dots_allowed_in_use_flags": re.compile("^(?P<prefix>[!-]?)(?P<flag>[A-Za-z0-9][A-Za-z0-9+_@.-]*)(?P<default>(\(\+\)|\(\-\))?)(?P<suffix>[?=]?)$"),
-}
-
-def _get_usedep_re(eapi):
- """
- When eapi is None then validation is not as strict, since we want the
- same to work for multiple EAPIs that may have slightly different rules.
- @param eapi: The EAPI
- @type eapi: String or None
- @rtype: regular expression object
- @return: A regular expression object that matches valid USE deps for the
- given eapi.
- """
- if eapi is None or eapi_allows_dots_in_use_flags(eapi):
- return _usedep_re["dots_allowed_in_use_flags"]
- else:
- return _usedep_re["dots_disallowed_in_use_flags"]
-
class _use_dep(object):
- __slots__ = ("__weakref__", "eapi", "conditional", "missing_enabled", "missing_disabled",
+ __slots__ = ("_eapi_attrs", "conditional", "missing_enabled", "missing_disabled",
"disabled", "enabled", "tokens", "required")
class _conditionals_class(object):
@@ -700,10 +812,10 @@ class _use_dep(object):
'not_equal': '!%s=',
}
- def __init__(self, use, eapi, enabled_flags=None, disabled_flags=None, missing_enabled=None, \
+ def __init__(self, use, eapi_attrs, enabled_flags=None, disabled_flags=None, missing_enabled=None,
missing_disabled=None, conditional=None, required=None):
- self.eapi = eapi
+ self._eapi_attrs = eapi_attrs
if enabled_flags is not None:
#A shortcut for the classe's own methods.
@@ -732,7 +844,7 @@ class _use_dep(object):
no_default = set()
conditional = {}
- usedep_re = _get_usedep_re(self.eapi)
+ usedep_re = _get_usedep_re(self._eapi_attrs)
for x in use:
m = usedep_re.match(x)
@@ -800,6 +912,14 @@ class _use_dep(object):
return ""
return "[%s]" % (",".join(self.tokens),)
+ if sys.hexversion < 0x3000000:
+
+ __unicode__ = __str__
+
+ def __str__(self):
+ return _unicode_encode(self.__unicode__(),
+ encoding=_encodings['content'], errors='backslashreplace')
+
def __repr__(self):
return "portage.dep._use_dep(%s)" % repr(self.tokens)
@@ -835,7 +955,7 @@ class _use_dep(object):
disabled_flags = set(self.disabled)
tokens = []
- usedep_re = _get_usedep_re(self.eapi)
+ usedep_re = _get_usedep_re(self._eapi_attrs)
for x in self.tokens:
m = usedep_re.match(x)
@@ -871,7 +991,7 @@ class _use_dep(object):
else:
tokens.append(x)
- return _use_dep(tokens, self.eapi, enabled_flags=enabled_flags, disabled_flags=disabled_flags, \
+ return _use_dep(tokens, self._eapi_attrs, enabled_flags=enabled_flags, disabled_flags=disabled_flags,
missing_enabled=self.missing_enabled, missing_disabled=self.missing_disabled, required=self.required)
def violated_conditionals(self, other_use, is_valid_flag, parent_use=None):
@@ -893,7 +1013,7 @@ class _use_dep(object):
def validate_flag(flag):
return is_valid_flag(flag) or flag in all_defaults
- usedep_re = _get_usedep_re(self.eapi)
+ usedep_re = _get_usedep_re(self._eapi_attrs)
for x in self.tokens:
m = usedep_re.match(x)
@@ -985,7 +1105,7 @@ class _use_dep(object):
tokens.append(x)
conditional.setdefault("disabled", set()).add(flag)
- return _use_dep(tokens, self.eapi, enabled_flags=enabled_flags, disabled_flags=disabled_flags, \
+ return _use_dep(tokens, self._eapi_attrs, enabled_flags=enabled_flags, disabled_flags=disabled_flags,
missing_enabled=self.missing_enabled, missing_disabled=self.missing_disabled, \
conditional=conditional, required=self.required)
@@ -1005,7 +1125,7 @@ class _use_dep(object):
missing_disabled = self.missing_disabled
tokens = []
- usedep_re = _get_usedep_re(self.eapi)
+ usedep_re = _get_usedep_re(self._eapi_attrs)
for x in self.tokens:
m = usedep_re.match(x)
@@ -1041,15 +1161,10 @@ class _use_dep(object):
else:
tokens.append(x)
- return _use_dep(tokens, self.eapi, enabled_flags=enabled_flags, disabled_flags=disabled_flags, \
+ return _use_dep(tokens, self._eapi_attrs, enabled_flags=enabled_flags, disabled_flags=disabled_flags,
missing_enabled=missing_enabled, missing_disabled=missing_disabled, required=self.required)
-if sys.hexversion < 0x3000000:
- _atom_base = unicode
-else:
- _atom_base = str
-
-class Atom(_atom_base):
+class Atom(_unicode):
"""
For compatibility with existing atom string manipulation code, this
@@ -1068,51 +1183,72 @@ class Atom(_atom_base):
def __init__(self, forbid_overlap=False):
self.overlap = self._overlap(forbid=forbid_overlap)
- def __new__(cls, s, unevaluated_atom=None, allow_wildcard=False, allow_repo=False,
+ def __new__(cls, s, unevaluated_atom=None, allow_wildcard=False, allow_repo=None,
_use=None, eapi=None, is_valid_flag=None):
- return _atom_base.__new__(cls, s)
+ return _unicode.__new__(cls, s)
- def __init__(self, s, unevaluated_atom=None, allow_wildcard=False, allow_repo=False,
+ def __init__(self, s, unevaluated_atom=None, allow_wildcard=False, allow_repo=None,
_use=None, eapi=None, is_valid_flag=None):
if isinstance(s, Atom):
# This is an efficiency assertion, to ensure that the Atom
# constructor is not called redundantly.
raise TypeError(_("Expected %s, got %s") % \
- (_atom_base, type(s)))
+ (_unicode, type(s)))
- if not isinstance(s, _atom_base):
- # Avoid TypeError from _atom_base.__init__ with PyPy.
+ if not isinstance(s, _unicode):
+ # Avoid TypeError from _unicode.__init__ with PyPy.
s = _unicode_decode(s)
- _atom_base.__init__(s)
+ _unicode.__init__(s)
+
+ eapi_attrs = _get_eapi_attrs(eapi)
+ atom_re = _get_atom_re(eapi_attrs)
- atom_re = _get_atom_re(eapi)
- if eapi_has_repo_deps(eapi):
- allow_repo = True
+ self.__dict__['eapi'] = eapi
+ if eapi is not None:
+ # Ignore allow_repo when eapi is specified.
+ allow_repo = eapi_attrs.repo_deps
+ else:
+ if allow_repo is None:
+ allow_repo = True
+ blocker_prefix = ""
if "!" == s[:1]:
blocker = self._blocker(forbid_overlap=("!" == s[1:2]))
if blocker.overlap.forbid:
+ blocker_prefix = s[:2]
s = s[2:]
else:
+ blocker_prefix = s[:1]
s = s[1:]
else:
blocker = False
self.__dict__['blocker'] = blocker
m = atom_re.match(s)
extended_syntax = False
+ extended_version = None
if m is None:
if allow_wildcard:
- m = _get_atom_wildcard_re(eapi).match(s)
+ atom_re = _get_atom_wildcard_re(eapi_attrs)
+ m = atom_re.match(s)
if m is None:
raise InvalidAtom(self)
- op = None
gdict = m.groupdict()
- cpv = cp = gdict['simple']
+ if m.group('star') is not None:
+ op = '=*'
+ base = atom_re.groupindex['star']
+ cp = m.group(base + 1)
+ cpv = m.group('star')[1:]
+ extended_version = m.group(base + 4)
+ else:
+ op = None
+ cpv = cp = m.group('simple')
+ if m.group(atom_re.groupindex['simple'] + 3) is not None:
+ raise InvalidAtom(self)
if cpv.find("**") != -1:
raise InvalidAtom(self)
- slot = gdict['slot']
- repo = gdict['repo']
+ slot = m.group('slot')
+ repo = m.group('repo')
use_str = None
extended_syntax = True
else:
@@ -1155,9 +1291,38 @@ class Atom(_atom_base):
except InvalidData:
# plain cp, wildcard, or something
self.__dict__['cpv'] = cpv
- self.__dict__['version'] = None
+ self.__dict__['version'] = extended_version
self.__dict__['repo'] = repo
- self.__dict__['slot'] = slot
+ if slot is None:
+ self.__dict__['slot'] = None
+ self.__dict__['sub_slot'] = None
+ self.__dict__['slot_operator'] = None
+ else:
+ slot_re = _get_slot_dep_re(eapi_attrs)
+ slot_match = slot_re.match(slot)
+ if slot_match is None:
+ raise InvalidAtom(self)
+ if eapi_attrs.slot_operator:
+ self.__dict__['slot'] = slot_match.group(1)
+ sub_slot = slot_match.group(2)
+ if sub_slot is not None:
+ sub_slot = sub_slot.lstrip("/")
+ if sub_slot in ("*", "="):
+ self.__dict__['sub_slot'] = None
+ self.__dict__['slot_operator'] = sub_slot
+ else:
+ slot_operator = None
+ if sub_slot is not None and sub_slot[-1:] == "=":
+ slot_operator = sub_slot[-1:]
+ sub_slot = sub_slot[:-1]
+ self.__dict__['sub_slot'] = sub_slot
+ self.__dict__['slot_operator'] = slot_operator
+ if self.slot is not None and self.slot_operator == "*":
+ raise InvalidAtom(self)
+ else:
+ self.__dict__['slot'] = slot
+ self.__dict__['sub_slot'] = None
+ self.__dict__['slot_operator'] = None
self.__dict__['operator'] = op
self.__dict__['extended_syntax'] = extended_syntax
@@ -1168,16 +1333,19 @@ class Atom(_atom_base):
if _use is not None:
use = _use
else:
- use = _use_dep(use_str[1:-1].split(","), eapi)
- without_use = Atom(m.group('without_use'), allow_repo=allow_repo)
+ use = _use_dep(use_str[1:-1].split(","), eapi_attrs)
+ without_use = Atom(blocker_prefix + m.group('without_use'),
+ allow_repo=allow_repo)
else:
use = None
if unevaluated_atom is not None and \
unevaluated_atom.use is not None:
# unevaluated_atom.use is used for IUSE checks when matching
# packages, so it must not propagate to without_use
- without_use = Atom(s, allow_wildcard=allow_wildcard,
- allow_repo=allow_repo)
+ without_use = Atom(_unicode(self),
+ allow_wildcard=allow_wildcard,
+ allow_repo=allow_repo,
+ eapi=eapi)
else:
without_use = self
@@ -1193,16 +1361,16 @@ class Atom(_atom_base):
if not isinstance(eapi, basestring):
raise TypeError('expected eapi argument of ' + \
'%s, got %s: %s' % (basestring, type(eapi), eapi,))
- if self.slot and not eapi_has_slot_deps(eapi):
+ if self.slot and not eapi_attrs.slot_deps:
raise InvalidAtom(
_("Slot deps are not allowed in EAPI %s: '%s'") \
% (eapi, self), category='EAPI.incompatible')
if self.use:
- if not eapi_has_use_deps(eapi):
+ if not eapi_attrs.use_deps:
raise InvalidAtom(
_("Use deps are not allowed in EAPI %s: '%s'") \
% (eapi, self), category='EAPI.incompatible')
- elif not eapi_has_use_dep_defaults(eapi) and \
+ elif not eapi_attrs.use_dep_defaults and \
(self.use.missing_enabled or self.use.missing_disabled):
raise InvalidAtom(
_("Use dep defaults are not allowed in EAPI %s: '%s'") \
@@ -1225,12 +1393,21 @@ class Atom(_atom_base):
"conditional '%s' in atom '%s' is not in IUSE") \
% (flag, conditional_str % flag, self)
raise InvalidAtom(msg, category='IUSE.missing')
- if self.blocker and self.blocker.overlap.forbid and not eapi_has_strong_blocks(eapi):
+ if self.blocker and self.blocker.overlap.forbid and not eapi_attrs.strong_blocks:
raise InvalidAtom(
_("Strong blocks are not allowed in EAPI %s: '%s'") \
% (eapi, self), category='EAPI.incompatible')
@property
+ def slot_operator_built(self):
+ """
+ Returns True if slot_operator == "=" and sub_slot is not None.
+ NOTE: foo/bar:2= is unbuilt and returns False, whereas foo/bar:2/2=
+ is built and returns True.
+ """
+ return self.slot_operator == "=" and self.sub_slot is not None
+
+ @property
def without_repo(self):
if self.repo is None:
return self
@@ -1239,18 +1416,29 @@ class Atom(_atom_base):
@property
def without_slot(self):
- if self.slot is None:
+ if self.slot is None and self.slot_operator is None:
return self
- return Atom(self.replace(_slot_separator + self.slot, '', 1),
+ atom = remove_slot(self)
+ if self.repo is not None:
+ atom += _repo_separator + self.repo
+ if self.use is not None:
+ atom += _unicode(self.use)
+ return Atom(atom,
allow_repo=True, allow_wildcard=True)
def with_repo(self, repo):
atom = remove_slot(self)
- if self.slot is not None:
- atom += _slot_separator + self.slot
+ if self.slot is not None or self.slot_operator is not None:
+ atom += _slot_separator
+ if self.slot is not None:
+ atom += self.slot
+ if self.sub_slot is not None:
+ atom += "/%s" % self.sub_slot
+ if self.slot_operator is not None:
+ atom += self.slot_operator
atom += _repo_separator + repo
if self.use is not None:
- atom += str(self.use)
+ atom += _unicode(self.use)
return Atom(atom, allow_repo=True, allow_wildcard=True)
def with_slot(self, slot):
@@ -1258,7 +1446,7 @@ class Atom(_atom_base):
if self.repo is not None:
atom += _repo_separator + self.repo
if self.use is not None:
- atom += str(self.use)
+ atom += _unicode(self.use)
return Atom(atom, allow_repo=True, allow_wildcard=True)
def __setattr__(self, name, value):
@@ -1307,10 +1495,16 @@ class Atom(_atom_base):
if not (self.use and self.use.conditional):
return self
atom = remove_slot(self)
- if self.slot:
- atom += ":%s" % self.slot
+ if self.slot is not None or self.slot_operator is not None:
+ atom += _slot_separator
+ if self.slot is not None:
+ atom += self.slot
+ if self.sub_slot is not None:
+ atom += "/%s" % self.sub_slot
+ if self.slot_operator is not None:
+ atom += self.slot_operator
use_dep = self.use.evaluate_conditionals(use)
- atom += str(use_dep)
+ atom += _unicode(use_dep)
return Atom(atom, unevaluated_atom=self, allow_repo=(self.repo is not None), _use=use_dep)
def violated_conditionals(self, other_use, is_valid_flag, parent_use=None):
@@ -1329,20 +1523,32 @@ class Atom(_atom_base):
if not self.use:
return self
atom = remove_slot(self)
- if self.slot:
- atom += ":%s" % self.slot
+ if self.slot is not None or self.slot_operator is not None:
+ atom += _slot_separator
+ if self.slot is not None:
+ atom += self.slot
+ if self.sub_slot is not None:
+ atom += "/%s" % self.sub_slot
+ if self.slot_operator is not None:
+ atom += self.slot_operator
use_dep = self.use.violated_conditionals(other_use, is_valid_flag, parent_use)
- atom += str(use_dep)
+ atom += _unicode(use_dep)
return Atom(atom, unevaluated_atom=self, allow_repo=(self.repo is not None), _use=use_dep)
def _eval_qa_conditionals(self, use_mask, use_force):
if not (self.use and self.use.conditional):
return self
atom = remove_slot(self)
- if self.slot:
- atom += ":%s" % self.slot
+ if self.slot is not None or self.slot_operator is not None:
+ atom += _slot_separator
+ if self.slot is not None:
+ atom += self.slot
+ if self.sub_slot is not None:
+ atom += "/%s" % self.sub_slot
+ if self.slot_operator is not None:
+ atom += self.slot_operator
use_dep = self.use._eval_qa_conditionals(use_mask, use_force)
- atom += str(use_dep)
+ atom += _unicode(use_dep)
return Atom(atom, unevaluated_atom=self, allow_repo=(self.repo is not None), _use=use_dep)
def __copy__(self):
@@ -1366,7 +1572,7 @@ def extended_cp_match(extended_cp, other_cp):
extended_cp_re = _extended_cp_re_cache.get(extended_cp)
if extended_cp_re is None:
extended_cp_re = re.compile("^" + re.escape(extended_cp).replace(
- r'\*', '[^/]*') + "$")
+ r'\*', '[^/]*') + "$", re.UNICODE)
_extended_cp_re_cache[extended_cp] = extended_cp_re
return extended_cp_re.match(other_cp) is not None
@@ -1644,77 +1850,8 @@ def dep_getusedeps( depend ):
open_bracket = depend.find( '[', open_bracket+1 )
return tuple(use_list)
-# \w is [a-zA-Z0-9_]
-
-# 2.1.3 A slot name may contain any of the characters [A-Za-z0-9+_.-].
-# It must not begin with a hyphen or a dot.
-_slot_separator = ":"
-_slot = r'([\w+][\w+.-]*)'
-_slot_re = re.compile('^' + _slot + '$', re.VERBOSE)
-
-_use = r'\[.*\]'
-_op = r'([=~]|[><]=?)'
-_repo_separator = "::"
-_repo_name = r'[\w][\w-]*'
-_repo = r'(?:' + _repo_separator + '(' + _repo_name + ')' + ')?'
-
-_atom_re = {
- "dots_disallowed_in_PN": re.compile('^(?P<without_use>(?:' +
- '(?P<op>' + _op + _cpv['dots_disallowed_in_PN'] + ')|' +
- '(?P<star>=' + _cpv['dots_disallowed_in_PN'] + r'\*)|' +
- '(?P<simple>' + _cp['dots_disallowed_in_PN'] + '))' +
- '(' + _slot_separator + _slot + ')?' + _repo + ')(' + _use + ')?$', re.VERBOSE),
- "dots_allowed_in_PN": re.compile('^(?P<without_use>(?:' +
- '(?P<op>' + _op + _cpv['dots_allowed_in_PN'] + ')|' +
- '(?P<star>=' + _cpv['dots_allowed_in_PN'] + r'\*)|' +
- '(?P<simple>' + _cp['dots_allowed_in_PN'] + '))' +
- '(' + _slot_separator + _slot + ')?' + _repo + ')(' + _use + ')?$', re.VERBOSE),
-}
-
-def _get_atom_re(eapi):
- if eapi is None or eapi_allows_dots_in_PN(eapi):
- return _atom_re["dots_allowed_in_PN"]
- else:
- return _atom_re["dots_disallowed_in_PN"]
-
-_extended_cat = r'[\w+*][\w+.*-]*'
-_extended_pkg = {
- "dots_disallowed_in_PN": r'[\w+*][\w+*-]*?',
- "dots_allowed_in_PN": r'[\w+*][\w+.*-]*?',
-}
-
-_atom_wildcard_re = {
- "dots_disallowed_in_PN": re.compile('(?P<simple>(' + _extended_cat + ')/(' + _extended_pkg['dots_disallowed_in_PN'] + '))(:(?P<slot>' + _slot + '))?(' + _repo_separator + '(?P<repo>' + _repo_name + '))?$'),
- "dots_allowed_in_PN": re.compile('(?P<simple>(' + _extended_cat + ')/(' + _extended_pkg['dots_allowed_in_PN'] + '))(:(?P<slot>' + _slot + '))?(' + _repo_separator + '(?P<repo>' + _repo_name + '))?$'),
-}
-
-def _get_atom_wildcard_re(eapi):
- if eapi is None or eapi_allows_dots_in_PN(eapi):
- return _atom_wildcard_re["dots_allowed_in_PN"]
- else:
- return _atom_wildcard_re["dots_disallowed_in_PN"]
-
-_useflag_re = {
- "dots_disallowed_in_use_flags": re.compile(r'^[A-Za-z0-9][A-Za-z0-9+_@-]*$'),
- "dots_allowed_in_use_flags": re.compile(r'^[A-Za-z0-9][A-Za-z0-9+_@.-]*$'),
-}
-
-def _get_useflag_re(eapi):
- """
- When eapi is None then validation is not as strict, since we want the
- same to work for multiple EAPIs that may have slightly different rules.
- @param eapi: The EAPI
- @type eapi: String or None
- @rtype: regular expression object
- @return: A regular expression object that matches valid USE flags for the
- given eapi.
- """
- if eapi is None or eapi_allows_dots_in_use_flags(eapi):
- return _useflag_re["dots_allowed_in_use_flags"]
- else:
- return _useflag_re["dots_disallowed_in_use_flags"]
-
-def isvalidatom(atom, allow_blockers=False, allow_wildcard=False, allow_repo=False):
+def isvalidatom(atom, allow_blockers=False, allow_wildcard=False,
+ allow_repo=False, eapi=None):
"""
Check to see if a depend atom is valid
@@ -1731,9 +1868,15 @@ def isvalidatom(atom, allow_blockers=False, allow_wildcard=False, allow_repo=Fal
1) False if the atom is invalid
2) True if the atom is valid
"""
+
+ if eapi is not None and isinstance(atom, Atom) and atom.eapi != eapi:
+ # We'll construct a new atom with the given eapi.
+ atom = _unicode(atom)
+
try:
if not isinstance(atom, Atom):
- atom = Atom(atom, allow_wildcard=allow_wildcard, allow_repo=allow_repo)
+ atom = Atom(atom, allow_wildcard=allow_wildcard,
+ allow_repo=allow_repo, eapi=eapi)
if not allow_blockers and atom.blocker:
return False
return True
@@ -1859,19 +2002,23 @@ def best_match_to_list(mypkg, mylist):
"""
operator_values = {'=':6, '~':5, '=*':4,
'>':2, '<':2, '>=':2, '<=':2, None:1}
- maxvalue = -2
+ maxvalue = -99
bestm = None
mypkg_cpv = None
for x in match_to_list(mypkg, mylist):
if x.extended_syntax:
- if dep_getslot(x) is not None:
+ if x.operator == '=*':
if maxvalue < 0:
maxvalue = 0
bestm = x
- else:
+ elif x.slot is not None:
if maxvalue < -1:
maxvalue = -1
bestm = x
+ else:
+ if maxvalue < -2:
+ maxvalue = -2
+ bestm = x
continue
if dep_getslot(x) is not None:
if maxvalue < 3:
@@ -1955,7 +2102,8 @@ def match_from_list(mydep, candidate_list):
mylist = []
- if operator is None:
+ if mydep.extended_syntax:
+
for x in candidate_list:
cp = getattr(x, "cp", None)
if cp is None:
@@ -1966,8 +2114,38 @@ def match_from_list(mydep, candidate_list):
if cp is None:
continue
- if cp == mycpv or (mydep.extended_syntax and \
- extended_cp_match(mydep.cp, cp)):
+ if cp == mycpv or extended_cp_match(mydep.cp, cp):
+ mylist.append(x)
+
+ if mylist and mydep.operator == "=*":
+
+ candidate_list = mylist
+ mylist = []
+ # Currently, only \*\w+\* is supported.
+ ver = mydep.version[1:-1]
+
+ for x in candidate_list:
+ x_ver = getattr(x, "version", None)
+ if x_ver is None:
+ xs = catpkgsplit(remove_slot(x))
+ if xs is None:
+ continue
+ x_ver = "-".join(xs[-2:])
+ if ver in x_ver:
+ mylist.append(x)
+
+ elif operator is None:
+ for x in candidate_list:
+ cp = getattr(x, "cp", None)
+ if cp is None:
+ mysplit = catpkgsplit(remove_slot(x))
+ if mysplit is not None:
+ cp = mysplit[0] + '/' + mysplit[1]
+
+ if cp is None:
+ continue
+
+ if cp == mydep.cp:
mylist.append(x)
elif operator == "=": # Exact match
@@ -1983,19 +2161,40 @@ def match_from_list(mydep, candidate_list):
# XXX: Nasty special casing for leading zeros
# Required as =* is a literal prefix match, so can't
# use vercmp
- mysplit = catpkgsplit(mycpv)
- myver = mysplit[2].lstrip("0")
+ myver = mycpv_cps[2].lstrip("0")
if not myver or not myver[0].isdigit():
myver = "0"+myver
- mycpv_cmp = mysplit[0]+"/"+mysplit[1]+"-"+myver
+ if myver == mycpv_cps[2]:
+ mycpv_cmp = mycpv
+ else:
+ # Use replace to preserve the revision part if it exists
+ # (mycpv_cps[3] can't be trusted because in contains r0
+ # even when the input has no revision part).
+ mycpv_cmp = mycpv.replace(
+ mydep.cp + "-" + mycpv_cps[2],
+ mydep.cp + "-" + myver, 1)
for x in candidate_list:
- xs = getattr(x, "cpv_split", None)
- if xs is None:
- xs = catpkgsplit(remove_slot(x))
+ try:
+ x.cp
+ except AttributeError:
+ try:
+ pkg = _pkg_str(remove_slot(x))
+ except InvalidData:
+ continue
+ else:
+ pkg = x
+
+ xs = pkg.cpv_split
myver = xs[2].lstrip("0")
if not myver or not myver[0].isdigit():
myver = "0"+myver
- xcpv = xs[0]+"/"+xs[1]+"-"+myver
+ if myver == xs[2]:
+ xcpv = pkg.cpv
+ else:
+ # Use replace to preserve the revision part if it exists.
+ xcpv = pkg.cpv.replace(
+ pkg.cp + "-" + xs[2],
+ pkg.cp + "-" + myver, 1)
if xcpv.startswith(mycpv_cmp):
mylist.append(x)
@@ -2048,16 +2247,33 @@ def match_from_list(mydep, candidate_list):
else:
raise KeyError(_("Unknown operator: %s") % mydep)
- if slot is not None and not mydep.extended_syntax:
+ if mydep.slot is not None:
candidate_list = mylist
mylist = []
for x in candidate_list:
- xslot = getattr(x, "slot", False)
- if xslot is False:
+ x_pkg = None
+ try:
+ x.cpv
+ except AttributeError:
xslot = dep_getslot(x)
- if xslot is not None and xslot != slot:
- continue
- mylist.append(x)
+ if xslot is not None:
+ try:
+ x_pkg = _pkg_str(remove_slot(x), slot=xslot)
+ except InvalidData:
+ continue
+ else:
+ x_pkg = x
+
+ if x_pkg is None:
+ mylist.append(x)
+ else:
+ try:
+ x_pkg.slot
+ except AttributeError:
+ mylist.append(x)
+ else:
+ if _match_slot(mydep, x_pkg):
+ mylist.append(x)
if mydep.unevaluated_atom.use:
candidate_list = mylist
@@ -2071,26 +2287,26 @@ def match_from_list(mydep, candidate_list):
continue
if mydep.use:
-
- missing_enabled = mydep.use.missing_enabled.difference(x.iuse.all)
- missing_disabled = mydep.use.missing_disabled.difference(x.iuse.all)
+ is_valid_flag = x.iuse.is_valid_flag
+ missing_enabled = frozenset(flag for flag in
+ mydep.use.missing_enabled if not is_valid_flag(flag))
+ missing_disabled = frozenset(flag for flag in
+ mydep.use.missing_disabled if not is_valid_flag(flag))
if mydep.use.enabled:
- if mydep.use.enabled.intersection(missing_disabled):
+ if any(f in mydep.use.enabled for f in missing_disabled):
continue
need_enabled = mydep.use.enabled.difference(use.enabled)
if need_enabled:
- need_enabled = need_enabled.difference(missing_enabled)
- if need_enabled:
+ if any(f not in missing_enabled for f in need_enabled):
continue
if mydep.use.disabled:
- if mydep.use.disabled.intersection(missing_enabled):
+ if any(f in mydep.use.disabled for f in missing_enabled):
continue
need_disabled = mydep.use.disabled.intersection(use.enabled)
if need_disabled:
- need_disabled = need_disabled.difference(missing_disabled)
- if need_disabled:
+ if any(f not in missing_disabled for f in need_disabled):
continue
mylist.append(x)
@@ -2110,9 +2326,9 @@ def match_from_list(mydep, candidate_list):
return mylist
def human_readable_required_use(required_use):
- return required_use.replace("^^", "exactly-one-of").replace("||", "any-of")
+ return required_use.replace("^^", "exactly-one-of").replace("||", "any-of").replace("??", "at-most-one-of")
-def get_required_use_flags(required_use):
+def get_required_use_flags(required_use, eapi=None):
"""
Returns a set of use flags that are used in the given REQUIRED_USE string
@@ -2122,6 +2338,12 @@ def get_required_use_flags(required_use):
@return: Set of use flags that are used in the given REQUIRED_USE string
"""
+ eapi_attrs = _get_eapi_attrs(eapi)
+ if eapi_attrs.required_use_at_most_one_of:
+ valid_operators = ("||", "^^", "??")
+ else:
+ valid_operators = ("||", "^^")
+
mysplit = required_use.split()
level = 0
stack = [[]]
@@ -2150,7 +2372,7 @@ def get_required_use_flags(required_use):
l = stack.pop()
ignore = False
if stack[level]:
- if stack[level][-1] in ("||", "^^") or \
+ if stack[level][-1] in valid_operators or \
(not isinstance(stack[level][-1], bool) and \
stack[level][-1][-1] == "?"):
ignore = True
@@ -2162,15 +2384,14 @@ def get_required_use_flags(required_use):
else:
raise InvalidDependString(
_("malformed syntax: '%s'") % required_use)
- elif token in ("||", "^^"):
+ elif token in valid_operators:
if need_bracket:
raise InvalidDependString(
_("malformed syntax: '%s'") % required_use)
need_bracket = True
stack[level].append(token)
else:
- if need_bracket or "(" in token or ")" in token or \
- "|" in token or "^" in token:
+ if need_bracket:
raise InvalidDependString(
_("malformed syntax: '%s'") % required_use)
@@ -2225,7 +2446,7 @@ class _RequiredUseBranch(object):
complex_nesting = False
node = self
while node != None and not complex_nesting:
- if node._operator in ("||", "^^"):
+ if node._operator in ("||", "^^", "??"):
complex_nesting = True
else:
node = node._parent
@@ -2246,7 +2467,7 @@ class _RequiredUseBranch(object):
if sys.hexversion < 0x3000000:
__nonzero__ = __bool__
-def check_required_use(required_use, use, iuse_match):
+def check_required_use(required_use, use, iuse_match, eapi=None):
"""
Checks if the use flags listed in 'use' satisfy all
constraints specified in 'constraints'.
@@ -2262,6 +2483,12 @@ def check_required_use(required_use, use, iuse_match):
@return: Indicates if REQUIRED_USE constraints are satisfied
"""
+ eapi_attrs = _get_eapi_attrs(eapi)
+ if eapi_attrs.required_use_at_most_one_of:
+ valid_operators = ("||", "^^", "??")
+ else:
+ valid_operators = ("||", "^^")
+
def is_active(token):
if token.startswith("!"):
flag = token[1:]
@@ -2271,6 +2498,11 @@ def check_required_use(required_use, use, iuse_match):
is_negated = False
if not flag or not iuse_match(flag):
+ if not eapi_attrs.required_use_at_most_one_of and flag == "?":
+ msg = _("Operator '??' is not supported with EAPI '%s'") \
+ % (eapi,)
+ e = InvalidData(msg, category='EAPI.incompatible')
+ raise InvalidDependString(msg, errors=(e,))
msg = _("USE flag '%s' is not in IUSE") \
% (flag,)
e = InvalidData(msg, category='IUSE.missing')
@@ -2288,6 +2520,8 @@ def check_required_use(required_use, use, iuse_match):
return (True in argument)
elif operator == "^^":
return (argument.count(True) == 1)
+ elif operator == "??":
+ return (argument.count(True) <= 1)
elif operator[-1] == "?":
return (False not in argument)
@@ -2317,7 +2551,7 @@ def check_required_use(required_use, use, iuse_match):
l = stack.pop()
op = None
if stack[level]:
- if stack[level][-1] in ("||", "^^"):
+ if stack[level][-1] in valid_operators:
op = stack[level].pop()
satisfied = is_satisfied(op, l)
stack[level].append(satisfied)
@@ -2346,7 +2580,7 @@ def check_required_use(required_use, use, iuse_match):
stack[level].append(satisfied)
if len(node._children) <= 1 or \
- node._parent._operator not in ("||", "^^"):
+ node._parent._operator not in valid_operators:
last_node = node._parent._children.pop()
if last_node is not node:
raise AssertionError(
@@ -2362,7 +2596,7 @@ def check_required_use(required_use, use, iuse_match):
raise AssertionError(
"node is not last child of parent")
- elif len(node._children) == 1 and op in ("||", "^^"):
+ elif len(node._children) == 1 and op in valid_operators:
last_node = node._parent._children.pop()
if last_node is not node:
raise AssertionError(
@@ -2372,7 +2606,7 @@ def check_required_use(required_use, use, iuse_match):
node._children[0]._parent = node._parent
node = node._children[0]
if node._operator is None and \
- node._parent._operator not in ("||", "^^"):
+ node._parent._operator not in valid_operators:
last_node = node._parent._children.pop()
if last_node is not node:
raise AssertionError(
@@ -2386,7 +2620,7 @@ def check_required_use(required_use, use, iuse_match):
else:
raise InvalidDependString(
_("malformed syntax: '%s'") % required_use)
- elif token in ("||", "^^"):
+ elif token in valid_operators:
if need_bracket:
raise InvalidDependString(
_("malformed syntax: '%s'") % required_use)
@@ -2396,8 +2630,7 @@ def check_required_use(required_use, use, iuse_match):
node._children.append(child)
node = child
else:
- if need_bracket or "(" in token or ")" in token or \
- "|" in token or "^" in token:
+ if need_bracket:
raise InvalidDependString(
_("malformed syntax: '%s'") % required_use)
@@ -2425,16 +2658,16 @@ def extract_affecting_use(mystr, atom, eapi=None):
that decide if the given atom is in effect.
Example usage:
- >>> extract_use_cond('sasl? ( dev-libs/cyrus-sasl ) \
+ >>> extract_affecting_use('sasl? ( dev-libs/cyrus-sasl ) \
!minimal? ( cxx? ( dev-libs/cyrus-sasl ) )', 'dev-libs/cyrus-sasl')
- (['sasl', 'minimal', 'cxx'])
+ {'cxx', 'minimal', 'sasl'}
- @param dep: The dependency string
+ @param mystr: The dependency string
@type mystr: String
@param atom: The atom to get into effect
@type atom: String
- @rtype: Tuple of two lists of strings
- @return: List of use flags that need to be enabled, List of use flag that need to be disabled
+ @rtype: Set of strings
+ @return: Set of use flags affecting given atom
"""
useflag_re = _get_useflag_re(eapi)
mysplit = mystr.split()
@@ -2540,3 +2773,48 @@ def extract_affecting_use(mystr, atom, eapi=None):
_("malformed syntax: '%s'") % mystr)
return affecting_use
+
+def extract_unpack_dependencies(src_uri, unpackers):
+ """
+ Return unpack dependencies string for given SRC_URI string.
+
+ @param src_uri: SRC_URI string
+ @type src_uri: String
+ @param unpackers: Dictionary mapping archive suffixes to dependency strings
+ @type unpackers: Dictionary
+ @rtype: String
+ @return: Dependency string specifying packages required to unpack archives.
+ """
+ src_uri = src_uri.split()
+
+ depend = []
+ for i in range(len(src_uri)):
+ if src_uri[i][-1] == "?" or src_uri[i] in ("(", ")"):
+ depend.append(src_uri[i])
+ elif (i+1 < len(src_uri) and src_uri[i+1] == "->") or src_uri[i] == "->":
+ continue
+ else:
+ for suffix in sorted(unpackers, key=lambda x: len(x), reverse=True):
+ suffix = suffix.lower()
+ if src_uri[i].lower().endswith(suffix):
+ depend.append(unpackers[suffix])
+ break
+
+ while True:
+ cleaned_depend = depend[:]
+ for i in range(len(cleaned_depend)):
+ if cleaned_depend[i] is None:
+ continue
+ elif cleaned_depend[i] == "(" and cleaned_depend[i+1] == ")":
+ cleaned_depend[i] = None
+ cleaned_depend[i+1] = None
+ elif cleaned_depend[i][-1] == "?" and cleaned_depend[i+1] == "(" and cleaned_depend[i+2] == ")":
+ cleaned_depend[i] = None
+ cleaned_depend[i+1] = None
+ cleaned_depend[i+2] = None
+ if depend == cleaned_depend:
+ break
+ else:
+ depend = [x for x in cleaned_depend if x is not None]
+
+ return " ".join(depend)