def __init__(self, fmri=None, build_release=None, publisher=None, name=None, version=None): if fmri is not None: fmri = fmri.rstrip() veridx, nameidx, pubidx = \ PkgFmri._gen_fmri_indexes(fmri) if pubidx != None: # Always use publisher information provided in # FMRI string. (It could be ""; pkg:///name is # valid.) publisher = fmri[pubidx:nameidx - 1] if veridx != None: self.pkg_name = fmri[nameidx:veridx] try: self.version = Version(fmri[veridx + 1:], build_release) except VersionError as iv: raise IllegalFmri(fmri, IllegalFmri.BAD_VERSION, nested_exc=iv) else: self.pkg_name = fmri[nameidx:] self.version = None else: # pkg_name and version must be explicitly set. self.pkg_name = name if version: self.version = Version(version, build_release) else: self.version = None # Ensure publisher is always None if one was not specified. if publisher: self.publisher = publisher else: self.publisher = None if not self.pkg_name: raise IllegalFmri(fmri, IllegalFmri.SYNTAX_ERROR, detail="Missing package name") if not self.valid_pkg_name.match(self.pkg_name): raise IllegalFmri(fmri, IllegalFmri.BAD_PACKAGENAME, detail=self.pkg_name) self._hash = None
class PkgFmri(object): """The publisher is the anchor of a package namespace. Clients can choose to take packages from multiple publishers, and specify a default search path. In general, package names may also be prefixed by a domain name, reverse domain name, or a stock symbol to avoid conflict. The unprefixed namespace is expected to be managed by architectural review. The primary equivalence relationship assumes that packages of the same package name are forwards compatible across all versions of that package, and that higher build release versions are superior publications than lower build release versions.""" # Stored in a class variable so that subclasses can override valid_pkg_name = g_valid_pkg_name __slots__ = ["version", "publisher", "pkg_name", "_hash", "__weakref__"] def __init__(self, fmri=None, build_release=None, publisher=None, name=None, version=None): if fmri is not None: fmri = fmri.rstrip() veridx, nameidx, pubidx = \ PkgFmri._gen_fmri_indexes(fmri) if pubidx != None: # Always use publisher information provided in # FMRI string. (It could be ""; pkg:///name is # valid.) publisher = fmri[pubidx:nameidx - 1] if veridx != None: self.pkg_name = fmri[nameidx:veridx] try: self.version = Version(fmri[veridx + 1:], build_release) except VersionError as iv: raise IllegalFmri(fmri, IllegalFmri.BAD_VERSION, nested_exc=iv) else: self.pkg_name = fmri[nameidx:] self.version = None else: # pkg_name and version must be explicitly set. self.pkg_name = name if version: self.version = Version(version, build_release) else: self.version = None # Ensure publisher is always None if one was not specified. if publisher: self.publisher = publisher else: self.publisher = None if not self.pkg_name: raise IllegalFmri(fmri, IllegalFmri.SYNTAX_ERROR, detail="Missing package name") if not self.valid_pkg_name.match(self.pkg_name): raise IllegalFmri(fmri, IllegalFmri.BAD_PACKAGENAME, detail=self.pkg_name) self._hash = None @staticmethod def getstate(obj, je_state=None): """Returns the serialized state of this object in a format that that can be easily stored using JSON, pickle, etc.""" return str(obj) @staticmethod def fromstate(state, jd_state=None): """Allocate a new object using previously serialized state obtained via getstate().""" return PkgFmri(state) def copy(self): return PkgFmri(str(self)) @staticmethod def _gen_fmri_indexes(fmri): """Return a tuple of offsets, used to extract different components of the FMRI.""" veridx = fmri.rfind("@") if veridx == -1: veridx = None pubidx = None if fmri.startswith("pkg://"): nameidx = fmri.find("/", 6, veridx) if nameidx == -1: raise IllegalFmri(fmri, IllegalFmri.SYNTAX_ERROR, detail=_("Missing '/' after " "publisher name")) # Publisher starts after //. pubidx = 6 # Name starts after / which terminates publisher nameidx += 1 elif fmri.startswith("pkg:/"): # Name starts after / which terminates scheme nameidx = 5 elif fmri.startswith("//"): nameidx = fmri.find("/", 2, veridx) if nameidx == -1: raise IllegalFmri(fmri, IllegalFmri.SYNTAX_ERROR, detail=_("Missing '/' after " "publisher name")) # Publisher starts after //. pubidx = 2 # Name starts after / which terminates publisher nameidx += 1 elif fmri.startswith("/"): # Name starts after / which terminates scheme nameidx = 1 else: nameidx = 0 return (veridx, nameidx, pubidx) def get_publisher(self): """Return the name of the publisher that is contained within this FMRI. This strips off extraneous data that may be attached to the publisher. The output is suitable as a key into the publisher["prefix"] table.""" # Strip off preferred publisher prefix, if it exists. if self.publisher and self.publisher.startswith(PREF_PUB_PFX): r = self.publisher.rsplit('_', 1) a = r[len(r) - 1] return a # Otherwise just return the publisher return self.publisher def set_publisher(self, publisher, preferred=False): """Set the FMRI's publisher. If this is a preferred publisher, set preferred to True.""" if preferred and not publisher.startswith(PREF_PUB_PFX): self.publisher = "{0}_{1}".format(PREF_PUB_PFX, publisher) else: self.publisher = publisher def remove_publisher(self): self.publisher = None return self def has_publisher(self): """Returns true if the FMRI has a publisher.""" if self.publisher: return True return False def has_version(self): """Returns True if the FMRI has a version""" if self.version: return True return False def preferred_publisher(self): """Returns true if this FMRI's publisher is the preferred publisher.""" if not self.publisher or \ self.publisher.startswith(PREF_PUB_PFX): return True return False def get_publisher_str(self): """Return the bare string that specifies everything about the publisher. This should only be used by code that must write out (or restore) the complete publisher information to disk.""" return self.publisher def get_name(self): return self.pkg_name def set_name(self, name): self.pkg_name = name self._hash = None def set_timestamp(self, new_ts): self.version.set_timestamp(new_ts) self._hash = None def get_timestamp(self): return self.version.get_timestamp() def get_version(self): return self.version.get_short_version() def get_pkg_stem(self, anarchy=False, include_scheme=True): """Return a string representation of the FMRI without a specific version. Anarchy returns a stem without any publisher.""" pkg_str = "" if not self.publisher or \ self.publisher.startswith(PREF_PUB_PFX) or anarchy: if include_scheme: pkg_str = "pkg:/" return "{0}{1}".format(pkg_str, self.pkg_name) if include_scheme: pkg_str = "pkg://" return "{0}{1}/{2}".format(pkg_str, self.publisher, self.pkg_name) def get_short_fmri(self, default_publisher=None, anarchy=False, include_scheme=True): """Return a string representation of the FMRI without a specific version.""" pkg_str = "" publisher = self.publisher if not publisher: publisher = default_publisher if self.version == None: version = "" else: version = "@" + self.version.get_short_version() if (not publisher or publisher.startswith(PREF_PUB_PFX) or anarchy): if include_scheme: pkg_str = "pkg:/" return "{0}{1}{2}".format(pkg_str, self.pkg_name, version) if include_scheme: pkg_str = "pkg://" return "{0}{1}/{2}{3}".format(pkg_str, publisher, self.pkg_name, version) def get_fmri(self, default_publisher=None, anarchy=False, include_scheme=True, include_build=True): """Return a string representation of the FMRI. Anarchy returns a string without any publisher.""" pkg_str = "" publisher = self.publisher if publisher == None: publisher = default_publisher if not publisher or publisher.startswith(PREF_PUB_PFX) \ or anarchy: if include_scheme: pkg_str = "pkg:/" if self.version == None: return "{0}{1}".format(pkg_str, self.pkg_name) return "{0}{1}@{2}".format( pkg_str, self.pkg_name, self.version.get_version(include_build=include_build)) if include_scheme: pkg_str = "pkg://" if self.version == None: return "{0}{1}/{2}".format(pkg_str, publisher, self.pkg_name) return "{0}{1}/{2}@{3}".format( pkg_str, publisher, self.pkg_name, self.version.get_version(include_build=include_build)) def hierarchical_names(self): """Generate the different hierarchical names that could be used to reference this fmri.""" names = self.pkg_name.split("/") res = names[-1:] for n in reversed(names[:-1]): res.append("{0}/{1}".format(n, res[-1])) return res def __str__(self): """Return as specific an FMRI representation as possible.""" return self.get_fmri() def __repr__(self): """Return as specific an FMRI representation as possible.""" if not self.publisher: if not self.version: fmristr = "pkg:/{0}".format(self.pkg_name) else: fmristr = "pkg:/{0}@{1}".format(self.pkg_name, self.version) elif not self.version: fmristr = "pkg://{0}/{1}".format(self.publisher, self.pkg_name) else: fmristr = "pkg://{0}/{1}@{2}".format(self.publisher, self.pkg_name, self.version) return "<pkg.fmri.PkgFmri '{0}' at {1:#x}".format(fmristr, id(self)) def __hash__(self): # # __hash__ need not generate a unique hash value for all # possible objects-- it must simply guarantee that two # items which are equal (i.e. a = b) always hash to # the same value. # h = self._hash if h is None: h = self._hash = hash(self.version) + hash(self.pkg_name) return h def __eq__(self, other): if not other: return False if not isinstance(other, PkgFmri): return False if self.publisher == other.publisher and \ self.pkg_name == other.pkg_name and \ self.version == other.version: return True return False def __ne__(self, other): return not self == other def __lt__(self, other): if not other: return False if not isinstance(other, PkgFmri): return True if self.publisher != other.publisher: if self.publisher is None and other.publisher: return True if self.publisher and other.publisher is None: return False if self.publisher < other.publisher: return True return False if self.pkg_name < other.pkg_name: return True if self.pkg_name != other.pkg_name: return False if self.version is None and other.version is None: return False return self.version < other.version def __gt__(self, other): if not other: return True if not isinstance(other, PkgFmri): return False if self.publisher != other.publisher: if self.publisher and other.publisher is None: return True if self.publisher is None and other.publisher: return False if self.publisher > other.publisher: return True return False if self.pkg_name > other.pkg_name: return True if self.pkg_name != other.pkg_name: return False if self.version is None and other.version is None: return False return self.version > other.version def __ge__(self, other): return not self < other def __le__(self, other): return not self > other def get_link_path(self, stemonly=False): """Return the escaped link (or file) path fragment for this FMRI.""" if stemonly: return "{0}".format(quote(self.pkg_name, "")) if self.version is None: raise MissingVersionError(self) return "{0}@{1}".format(quote(self.pkg_name, ""), quote(str(self.version), "")) def get_dir_path(self, stemonly=False): """Return the escaped directory path fragment for this FMRI.""" if stemonly: return "{0}".format(quote(self.pkg_name, "")) if self.version is None: raise MissingVersionError(self) return "{0}/{1}".format(quote(self.pkg_name, ""), quote(self.version.__str__(), "")) def get_url_path(self): """Return the escaped URL path fragment for this FMRI. Requires a version to be defined.""" if self.version is None: raise MissingVersionError(self) return "{0}@{1}".format(quote(self.pkg_name, ""), quote(self.version.__str__(), "")) def is_same_pkg(self, other): """Return true if these packages are the same (although potentially of different versions.)""" return self.pkg_name == other.pkg_name def tuple(self): return self.get_publisher_str(), self.pkg_name, self.version def is_name_match(self, fmristr): """True if the regular expression given in fmristr matches the stem of this pkg: FMRI.""" m = re.match(fmristr, self.pkg_name) return m != None def is_similar(self, other): """True if package names match exactly. Not a pattern-based query.""" return self.pkg_name == other.pkg_name def is_successor(self, other): """ returns True if self >= other """ # Fastest path for most common case. if self.pkg_name != other.pkg_name: return False if self.version is None and other.version is None: return True if self.version < other.version: return False return True