diff options
Diffstat (limited to 'pym/gentoolkit/package.py')
-rw-r--r-- | pym/gentoolkit/package.py | 1118 |
1 files changed, 565 insertions, 553 deletions
diff --git a/pym/gentoolkit/package.py b/pym/gentoolkit/package.py index 4c851bc..92bc3a3 100644 --- a/pym/gentoolkit/package.py +++ b/pym/gentoolkit/package.py @@ -20,19 +20,26 @@ Example usage: False """ -__all__ = ( - 'Package', - 'PackageFormatter', - 'FORMAT_TMPL_VARS' -) +__all__ = ("Package", "PackageFormatter", "FORMAT_TMPL_VARS") # ======= # Globals # ======= FORMAT_TMPL_VARS = ( - '$location', '$mask', '$mask2', '$cp', '$cpv', '$category', '$name', - '$version', '$revision', '$fullversion', '$slot', '$repo', '$keywords' + "$location", + "$mask", + "$mask2", + "$cp", + "$cpv", + "$category", + "$name", + "$version", + "$revision", + "$fullversion", + "$slot", + "$repo", + "$keywords", ) # ======= @@ -57,13 +64,18 @@ from gentoolkit.eprefix import EPREFIX # Settings # ======= + def _NewPortageConfig(local_config): - ret = portage.config(local_config=local_config, - eprefix=EPREFIX if EPREFIX else None, - config_root=os.environ.get('PORTAGE_CONFIGROOT', None), - target_root=os.environ.get('ROOT', None)) - ret.lock() - return ret + ret = portage.config( + local_config=local_config, + eprefix=EPREFIX if EPREFIX else None, + config_root=os.environ.get("PORTAGE_CONFIGROOT", None), + target_root=os.environ.get("ROOT", None), + ) + ret.lock() + return ret + + default_settings = _NewPortageConfig(local_config=True) nolocal_settings = _NewPortageConfig(local_config=False) @@ -71,555 +83,555 @@ nolocal_settings = _NewPortageConfig(local_config=False) # Classes # ======= -class Package(CPV): - """Exposes the state of a given CPV.""" - - def __init__(self, cpv, validate=False, local_config=True): - if isinstance(cpv, CPV): - self.__dict__.update(cpv.__dict__) - else: - CPV.__init__(self, cpv, validate=validate) - - if validate and not all( - hasattr(self, x) for x in ('category', 'version') - ): - # CPV allows some things that Package must not - raise errors.GentoolkitInvalidPackage(self.cpv) - - if local_config: - self._settings = default_settings - else: - self._settings = nolocal_settings - - # Set dynamically - self._package_path = None - self._dblink = None - self._metadata = None - self._deps = None - self._portdir_path = None - - def __repr__(self): - return "<%s %r>" % (self.__class__.__name__, self.cpv) - - def __hash__(self): - return hash(self.cpv) - - def __contains__(self, key): - return key in self.cpv - - def __str__(self): - return str(self.cpv) - - @property - def metadata(self): - """Instantiate a L{gentoolkit.metadata.MetaData} object here.""" - - from gentoolkit.metadata import MetaData - - if self._metadata is None: - metadata_path = os.path.join( - self.package_path(), 'metadata.xml' - ) - try: - self._metadata = MetaData(metadata_path) - except IOError as error: - import errno - if error.errno != errno.ENOENT: - raise - return None - - return self._metadata - - @property - def dblink(self): - """Instantiate a L{portage.dbapi.vartree.dblink} object here.""" - - if self._dblink is None: - self._dblink = portage.dblink( - self.category, - "%s-%s" % (self.name, self.fullversion), - self._settings["ROOT"], - self._settings - ) - - return self._dblink - - @property - def deps(self): - """Instantiate a L{gentoolkit.dependencies.Dependencies} object here.""" - - from gentoolkit.dependencies import Dependencies - - if self._deps is None: - self._deps = Dependencies(self.cpv) - - return self._deps - - def environment(self, envvars, prefer_vdb=True, fallback=True): - """Returns one or more of the predefined environment variables. - - Some available envvars are: - ---------------------- - BINPKGMD5 COUNTER FEATURES LICENSE SRC_URI - CATEGORY CXXFLAGS HOMEPAGE PDEPEND USE - CBUILD DEFINED_PHASES INHERITED PF - CFLAGS DEPEND IUSE PROVIDE - CHOST DESCRIPTION KEYWORDS RDEPEND - CONTENTS EAPI LDFLAGS SLOT - - Example usage: - >>> pkg = Package('sys-apps/portage-9999') - >>> pkg.environment('USE') - '' - >>> pkg.environment(('USE', 'IUSE')) - ... # doctest: +NORMALIZE_WHITESPACE - ['', 'build doc epydoc +ipc pypy1_9 python2 python3 - selinux xattr'] - - @type envvars: str or array - @param envvars: one or more of (DEPEND, SRC_URI, etc.) - @type prefer_vdb: bool - @keyword prefer_vdb: if True, look in the vardb before portdb, else - reverse order. Specifically KEYWORDS will get more recent - information by preferring portdb. - @type fallback: bool - @keyword fallback: query only the preferred db if False - @rtype: str or list - @return: str if envvars is str, list if envvars is array - @raise KeyError: if key is not found in requested db(s) - """ - - got_string = False - if isinstance(envvars, str): - got_string = True - envvars = (envvars,) - if prefer_vdb: - try: - result = portage.db[portage.root][ - 'vartree'].dbapi.aux_get( - self.cpv, envvars) - except KeyError: - try: - if not fallback: - raise KeyError - result = portage.db[portage.root][ - 'porttree'].dbapi.aux_get( - self.cpv, envvars) - except KeyError: - raise errors.GentoolkitFatalError( - 'aux_get returned unexpected ' - 'results') - else: - try: - result = portage.db[portage.root][ - 'porttree'].dbapi.aux_get( - self.cpv, envvars) - except KeyError: - try: - if not fallback: - raise KeyError - result = portage.db[portage.root][ - 'vartree'].dbapi.aux_get( - self.cpv, envvars) - except KeyError: - raise errors.GentoolkitFatalError( - 'aux_get returned unexpected ' - 'results') - - if got_string: - return result[0] - return result - - def exists(self): - """Return True if package exists in the Portage tree, else False""" - - return bool(portage.db[portage.root]["porttree"].dbapi.cpv_exists(self.cpv)) - - def settings(self, key): - """Returns the value of the given key for this package (useful - for package.* files.""" - - if self._settings.locked: - self._settings.unlock() - try: - result = self._settings[key] - finally: - self._settings.lock() - return result - - def mask_status(self): - """Shortcut to L{portage.getmaskingstatus}. - - @rtype: None or list - @return: a list containing none or some of: - 'profile' - 'package.mask' - license(s) - "kmask" keyword - 'missing keyword' - """ - - if self._settings.locked: - self._settings.unlock() - try: - result = portage.getmaskingstatus(self.cpv, - settings=self._settings, - portdb=portage.db[portage.root]["porttree"].dbapi) - except KeyError: - # getmaskingstatus doesn't support packages without ebuilds in the - # Portage tree. - result = None - - return result - - def mask_reason(self): - """Shortcut to L{portage.getmaskingreason}. - - @rtype: None or tuple - @return: empty tuple if pkg not masked OR - ('mask reason', 'mask location') - """ - - try: - result = portage.getmaskingreason(self.cpv, - settings=self._settings, - portdb=portage.db[portage.root]["porttree"].dbapi, - return_location=True) - if result is None: - result = tuple() - except KeyError: - # getmaskingstatus doesn't support packages without ebuilds in the - # Portage tree. - result = None - - return result - - def ebuild_path(self, in_vartree=False): - """Returns the complete path to the .ebuild file. - - Example usage: - >>> pkg = Package('sys-apps/portage-9999') - >>> pkg.ebuild_path() - '/usr/portage/sys-apps/portage/portage-9999.ebuild' - >>> pkg.ebuild_path(in_vartree=True) - '/var/db/pkg/sys-apps/portage-9999/portage-9999.ebuild' - """ - - if in_vartree: - return portage.db[portage.root]["vartree"].dbapi.findname(self.cpv) - return portage.db[portage.root]["porttree"].dbapi.findname(self.cpv) - - def package_path(self, in_vartree=False): - """Return the path to where the ebuilds and other files reside.""" - - if in_vartree: - return self.dblink.getpath() - return os.sep.join(self.ebuild_path().split(os.sep)[:-1]) - - def repo_name(self, fallback=True): - """Determine the repository name. - - @type fallback: bool - @param fallback: if the repo_name file does not exist, return the - repository name from the path - @rtype: str - @return: output of the repository metadata file, which stores the - repo_name variable, or try to get the name of the repo from - the path. - @raise GentoolkitFatalError: if fallback is False and repo_name is - not specified by the repository. - """ - - try: - return self.environment('repository') - except errors.GentoolkitFatalError: - if fallback: - return self.package_path().split(os.sep)[-3] - raise - - def use(self): - """Returns the USE flags active at time of installation.""" - - return self.dblink.getstring("USE") - - def use_status(self): - """Returns the USE flags active for installation.""" - - iuse, final_flags = get_flags(self.cpv, final_setting=True) - return final_flags - - def parsed_contents(self, prefix_root=False): - """Returns the parsed CONTENTS file. - - @rtype: dict - @return: {'/full/path/to/obj': ['type', 'timestamp', 'md5sum'], ...} - """ - - contents = self.dblink.getcontents() - - # Portage will automatically prepend ROOT. Undo that. - if not prefix_root: - myroot = self._settings["ROOT"] - if myroot != '/': - ret = {} - for key, val in self.dblink.getcontents().items(): - ret['/' + os.path.relpath(key, myroot)] = val - contents = ret - - return contents - - def size(self): - """Estimates the installed size of the contents of this package. - - @rtype: tuple - @return: (size, number of files in total, number of uncounted files) - """ - - seen = set() - size = n_files = n_uncounted = 0 - for path in self.parsed_contents(prefix_root=True): - try: - st = os.lstat(_unicode_encode(path, encoding=_encodings['fs'])) - except OSError: - continue - - # Remove hardlinks by checking for duplicate inodes. Bug #301026. - file_inode = st.st_ino - if file_inode in seen: - continue - seen.add(file_inode) - - try: - size += st.st_size - n_files += 1 - except OSError: - n_uncounted += 1 - - return (size, n_files, n_uncounted) - def is_installed(self): - """Returns True if this package is installed (merged).""" - - return self.dblink.exists() +class Package(CPV): + """Exposes the state of a given CPV.""" + + def __init__(self, cpv, validate=False, local_config=True): + if isinstance(cpv, CPV): + self.__dict__.update(cpv.__dict__) + else: + CPV.__init__(self, cpv, validate=validate) + + if validate and not all(hasattr(self, x) for x in ("category", "version")): + # CPV allows some things that Package must not + raise errors.GentoolkitInvalidPackage(self.cpv) + + if local_config: + self._settings = default_settings + else: + self._settings = nolocal_settings + + # Set dynamically + self._package_path = None + self._dblink = None + self._metadata = None + self._deps = None + self._portdir_path = None + + def __repr__(self): + return "<%s %r>" % (self.__class__.__name__, self.cpv) + + def __hash__(self): + return hash(self.cpv) + + def __contains__(self, key): + return key in self.cpv + + def __str__(self): + return str(self.cpv) + + @property + def metadata(self): + """Instantiate a L{gentoolkit.metadata.MetaData} object here.""" + + from gentoolkit.metadata import MetaData + + if self._metadata is None: + metadata_path = os.path.join(self.package_path(), "metadata.xml") + try: + self._metadata = MetaData(metadata_path) + except IOError as error: + import errno + + if error.errno != errno.ENOENT: + raise + return None + + return self._metadata + + @property + def dblink(self): + """Instantiate a L{portage.dbapi.vartree.dblink} object here.""" + + if self._dblink is None: + self._dblink = portage.dblink( + self.category, + "%s-%s" % (self.name, self.fullversion), + self._settings["ROOT"], + self._settings, + ) + + return self._dblink + + @property + def deps(self): + """Instantiate a L{gentoolkit.dependencies.Dependencies} object here.""" + + from gentoolkit.dependencies import Dependencies + + if self._deps is None: + self._deps = Dependencies(self.cpv) + + return self._deps + + def environment(self, envvars, prefer_vdb=True, fallback=True): + """Returns one or more of the predefined environment variables. + + Some available envvars are: + ---------------------- + BINPKGMD5 COUNTER FEATURES LICENSE SRC_URI + CATEGORY CXXFLAGS HOMEPAGE PDEPEND USE + CBUILD DEFINED_PHASES INHERITED PF + CFLAGS DEPEND IUSE PROVIDE + CHOST DESCRIPTION KEYWORDS RDEPEND + CONTENTS EAPI LDFLAGS SLOT + + Example usage: + >>> pkg = Package('sys-apps/portage-9999') + >>> pkg.environment('USE') + '' + >>> pkg.environment(('USE', 'IUSE')) + ... # doctest: +NORMALIZE_WHITESPACE + ['', 'build doc epydoc +ipc pypy1_9 python2 python3 + selinux xattr'] + + @type envvars: str or array + @param envvars: one or more of (DEPEND, SRC_URI, etc.) + @type prefer_vdb: bool + @keyword prefer_vdb: if True, look in the vardb before portdb, else + reverse order. Specifically KEYWORDS will get more recent + information by preferring portdb. + @type fallback: bool + @keyword fallback: query only the preferred db if False + @rtype: str or list + @return: str if envvars is str, list if envvars is array + @raise KeyError: if key is not found in requested db(s) + """ + + got_string = False + if isinstance(envvars, str): + got_string = True + envvars = (envvars,) + if prefer_vdb: + try: + result = portage.db[portage.root]["vartree"].dbapi.aux_get( + self.cpv, envvars + ) + except KeyError: + try: + if not fallback: + raise KeyError + result = portage.db[portage.root]["porttree"].dbapi.aux_get( + self.cpv, envvars + ) + except KeyError: + raise errors.GentoolkitFatalError( + "aux_get returned unexpected " "results" + ) + else: + try: + result = portage.db[portage.root]["porttree"].dbapi.aux_get( + self.cpv, envvars + ) + except KeyError: + try: + if not fallback: + raise KeyError + result = portage.db[portage.root]["vartree"].dbapi.aux_get( + self.cpv, envvars + ) + except KeyError: + raise errors.GentoolkitFatalError( + "aux_get returned unexpected " "results" + ) + + if got_string: + return result[0] + return result + + def exists(self): + """Return True if package exists in the Portage tree, else False""" + + return bool(portage.db[portage.root]["porttree"].dbapi.cpv_exists(self.cpv)) + + def settings(self, key): + """Returns the value of the given key for this package (useful + for package.* files.""" + + if self._settings.locked: + self._settings.unlock() + try: + result = self._settings[key] + finally: + self._settings.lock() + return result + + def mask_status(self): + """Shortcut to L{portage.getmaskingstatus}. + + @rtype: None or list + @return: a list containing none or some of: + 'profile' + 'package.mask' + license(s) + "kmask" keyword + 'missing keyword' + """ + + if self._settings.locked: + self._settings.unlock() + try: + result = portage.getmaskingstatus( + self.cpv, + settings=self._settings, + portdb=portage.db[portage.root]["porttree"].dbapi, + ) + except KeyError: + # getmaskingstatus doesn't support packages without ebuilds in the + # Portage tree. + result = None + + return result + + def mask_reason(self): + """Shortcut to L{portage.getmaskingreason}. + + @rtype: None or tuple + @return: empty tuple if pkg not masked OR + ('mask reason', 'mask location') + """ + + try: + result = portage.getmaskingreason( + self.cpv, + settings=self._settings, + portdb=portage.db[portage.root]["porttree"].dbapi, + return_location=True, + ) + if result is None: + result = tuple() + except KeyError: + # getmaskingstatus doesn't support packages without ebuilds in the + # Portage tree. + result = None + + return result + + def ebuild_path(self, in_vartree=False): + """Returns the complete path to the .ebuild file. + + Example usage: + >>> pkg = Package('sys-apps/portage-9999') + >>> pkg.ebuild_path() + '/usr/portage/sys-apps/portage/portage-9999.ebuild' + >>> pkg.ebuild_path(in_vartree=True) + '/var/db/pkg/sys-apps/portage-9999/portage-9999.ebuild' + """ + + if in_vartree: + return portage.db[portage.root]["vartree"].dbapi.findname(self.cpv) + return portage.db[portage.root]["porttree"].dbapi.findname(self.cpv) + + def package_path(self, in_vartree=False): + """Return the path to where the ebuilds and other files reside.""" + + if in_vartree: + return self.dblink.getpath() + return os.sep.join(self.ebuild_path().split(os.sep)[:-1]) + + def repo_name(self, fallback=True): + """Determine the repository name. + + @type fallback: bool + @param fallback: if the repo_name file does not exist, return the + repository name from the path + @rtype: str + @return: output of the repository metadata file, which stores the + repo_name variable, or try to get the name of the repo from + the path. + @raise GentoolkitFatalError: if fallback is False and repo_name is + not specified by the repository. + """ + + try: + return self.environment("repository") + except errors.GentoolkitFatalError: + if fallback: + return self.package_path().split(os.sep)[-3] + raise + + def use(self): + """Returns the USE flags active at time of installation.""" + + return self.dblink.getstring("USE") + + def use_status(self): + """Returns the USE flags active for installation.""" + + iuse, final_flags = get_flags(self.cpv, final_setting=True) + return final_flags + + def parsed_contents(self, prefix_root=False): + """Returns the parsed CONTENTS file. + + @rtype: dict + @return: {'/full/path/to/obj': ['type', 'timestamp', 'md5sum'], ...} + """ + + contents = self.dblink.getcontents() + + # Portage will automatically prepend ROOT. Undo that. + if not prefix_root: + myroot = self._settings["ROOT"] + if myroot != "/": + ret = {} + for key, val in self.dblink.getcontents().items(): + ret["/" + os.path.relpath(key, myroot)] = val + contents = ret + + return contents + + def size(self): + """Estimates the installed size of the contents of this package. + + @rtype: tuple + @return: (size, number of files in total, number of uncounted files) + """ + + seen = set() + size = n_files = n_uncounted = 0 + for path in self.parsed_contents(prefix_root=True): + try: + st = os.lstat(_unicode_encode(path, encoding=_encodings["fs"])) + except OSError: + continue + + # Remove hardlinks by checking for duplicate inodes. Bug #301026. + file_inode = st.st_ino + if file_inode in seen: + continue + seen.add(file_inode) + + try: + size += st.st_size + n_files += 1 + except OSError: + n_uncounted += 1 + + return (size, n_files, n_uncounted) + + def is_installed(self): + """Returns True if this package is installed (merged).""" + + return self.dblink.exists() - def is_overlay(self): - """Returns True if the package is in an overlay.""" + def is_overlay(self): + """Returns True if the package is in an overlay.""" - ebuild, tree = portage.db[portage.root]["porttree"].dbapi.findname2(self.cpv) - if not ebuild: - return None - if self._portdir_path is None: - self._portdir_path = os.path.realpath(self._settings["PORTDIR"]) - return (tree and tree != self._portdir_path) + ebuild, tree = portage.db[portage.root]["porttree"].dbapi.findname2(self.cpv) + if not ebuild: + return None + if self._portdir_path is None: + self._portdir_path = os.path.realpath(self._settings["PORTDIR"]) + return tree and tree != self._portdir_path - def is_masked(self): - """Returns True if this package is masked against installation. + def is_masked(self): + """Returns True if this package is masked against installation. - @note: We blindly assume that the package actually exists on disk. - """ + @note: We blindly assume that the package actually exists on disk. + """ - unmasked = portage.db[portage.root]['porttree'].dbapi.xmatch( - 'match-visible', self.cpv) - return self.cpv not in unmasked + unmasked = portage.db[portage.root]["porttree"].dbapi.xmatch( + "match-visible", self.cpv + ) + return self.cpv not in unmasked class PackageFormatter: - """When applied to a L{gentoolkit.package.Package} object, determine the - location (Portage Tree vs. overlay), install status and masked status. That - information can then be easily formatted and displayed. - - Example usage: - >>> from gentoolkit.query import Query - >>> from gentoolkit.package import PackageFormatter - >>> import portage.output - >>> q = Query('gcc') - >>> pkgs = [PackageFormatter(x) for x in q.find()] - >>> havecolor = portage.output.havecolor - >>> portage.output.havecolor = False - >>> for pkg in pkgs: - ... # Only print packages that are installed and from the Portage - ... # tree - ... if set('IP').issubset(pkg.location): - ... print(pkg) - ... - [IP-] [ ] sys-devel/gcc-4.5.4:4.5 - >>> portage.output.havecolor = havecolor - - @type pkg: L{gentoolkit.package.Package} - @param pkg: package to format - @type do_format: bool - @param do_format: Whether to format the package name or not. - Essentially C{do_format} should be set to False when piping or when - quiet output is desired. If C{do_format} is False, only the location - attribute will be created to save time. - """ - - _tmpl_verbose = "[$location] [$mask] $cpv:$slot" - _tmpl_quiet = "$cpv" - - def __init__(self, pkg, do_format=True, custom_format=None): - self._pkg = None - self._do_format = do_format - self._str = None - self._location = None - if not custom_format: - if do_format: - custom_format = self._tmpl_verbose - else: - custom_format = self._tmpl_quiet - self.tmpl = Template(custom_format) - self.format_vars = LazyItemsDict() - self.pkg = pkg - - def __repr__(self): - return "<%s %s @%#8x>" % (self.__class__.__name__, self.pkg, id(self)) - - def __str__(self): - if self._str is None: - self._str = self.tmpl.safe_substitute(self.format_vars) - return self._str - - @property - def location(self): - if self._location is None: - self._location = self.format_package_location() - return self._location - - @property - def pkg(self): - """Package to format""" - return self._pkg - - @pkg.setter - def pkg(self, value): - if self._pkg == value: - return - self._pkg = value - self._location = None - - fmt_vars = self.format_vars - self.format_vars.clear() - fmt_vars.addLazySingleton("location", - lambda: getattr(self, "location")) - fmt_vars.addLazySingleton("mask", self.format_mask) - fmt_vars.addLazySingleton("mask2", self.format_mask_status2) - fmt_vars.addLazySingleton("cpv", self.format_cpv) - fmt_vars.addLazySingleton("cp", self.format_cpv, "cp") - fmt_vars.addLazySingleton("category", self.format_cpv, "category") - fmt_vars.addLazySingleton("name", self.format_cpv, "name") - fmt_vars.addLazySingleton("version", self.format_cpv, "version") - fmt_vars.addLazySingleton("revision", self.format_cpv, "revision") - fmt_vars.addLazySingleton("fullversion", self.format_cpv, - "fullversion") - fmt_vars.addLazySingleton("slot", self.format_slot) - fmt_vars.addLazySingleton("repo", self.pkg.repo_name) - fmt_vars.addLazySingleton("keywords", self.format_keywords) - - def format_package_location(self): - """Get the install status (in /var/db/?) and origin (from an overlay - and the Portage tree?). - - @rtype: str - @return: one of: - 'I--' : Installed but ebuild doesn't exist on system anymore - '-P-' : Not installed and from the Portage tree - '--O' : Not installed and from an overlay - 'IP-' : Installed and from the Portage tree - 'I-O' : Installed and from an overlay - """ - - result = ['-', '-', '-'] - - if self.pkg.is_installed(): - result[0] = 'I' - - overlay = self.pkg.is_overlay() - if overlay is None: - pass - elif overlay: - result[2] = 'O' - else: - result[1] = 'P' - - return ''.join(result) - - def format_mask_status(self): - """Get the mask status of a given package. - - @rtype: tuple: (int, list) - @return: int = an index for this list: - [" ", " ~", " -", "M ", "M~", "M-", "??"] - 0 = not masked - 1 = keyword masked - 2 = arch masked - 3 = hard masked - 4 = hard and keyword masked, - 5 = hard and arch masked - 6 = ebuild doesn't exist on system anymore - - list = original output of portage.getmaskingstatus - """ - - result = 0 - masking_status = self.pkg.mask_status() - if masking_status is None: - return (6, []) - - if ("~%s keyword" % self.pkg.settings("ARCH")) in masking_status: - result += 1 - if "missing keyword" in masking_status: - result += 2 - if set(('profile', 'package.mask')).intersection(masking_status): - result += 3 - - return (result, masking_status) - - def format_mask_status2(self): - """Get the mask status of a given package. - """ - mask = self.pkg.mask_status() - if mask: - return pp.masking(mask) - else: - arch = self.pkg.settings("ARCH") - keywords = self.pkg.environment('KEYWORDS') - mask = [determine_keyword(arch, - portage.settings["ACCEPT_KEYWORDS"], - keywords)] - return pp.masking(mask) - - def format_mask(self): - maskmodes = [' ', ' ~', ' -', 'M ', 'M~', 'M-', '??'] - maskmode = maskmodes[self.format_mask_status()[0]] - return pp.keyword( - maskmode, - stable=not maskmode.strip(), - hard_masked=set(('M', '?', '-')).intersection(maskmode) - ) - - def format_cpv(self, attr=None): - if attr is None: - value = self.pkg.cpv - else: - value = getattr(self.pkg, attr) - if self._do_format: - return pp.cpv(value) - else: - return value - - def format_slot(self): - value = self.pkg.environment("SLOT") - if self._do_format: - return pp.slot(value) - else: - return value - - def format_keywords(self): - value = self.pkg.environment("KEYWORDS") - if self._do_format: - return pp.keyword(value) - else: - return value + """When applied to a L{gentoolkit.package.Package} object, determine the + location (Portage Tree vs. overlay), install status and masked status. That + information can then be easily formatted and displayed. + + Example usage: + >>> from gentoolkit.query import Query + >>> from gentoolkit.package import PackageFormatter + >>> import portage.output + >>> q = Query('gcc') + >>> pkgs = [PackageFormatter(x) for x in q.find()] + >>> havecolor = portage.output.havecolor + >>> portage.output.havecolor = False + >>> for pkg in pkgs: + ... # Only print packages that are installed and from the Portage + ... # tree + ... if set('IP').issubset(pkg.location): + ... print(pkg) + ... + [IP-] [ ] sys-devel/gcc-4.5.4:4.5 + >>> portage.output.havecolor = havecolor + + @type pkg: L{gentoolkit.package.Package} + @param pkg: package to format + @type do_format: bool + @param do_format: Whether to format the package name or not. + Essentially C{do_format} should be set to False when piping or when + quiet output is desired. If C{do_format} is False, only the location + attribute will be created to save time. + """ + + _tmpl_verbose = "[$location] [$mask] $cpv:$slot" + _tmpl_quiet = "$cpv" + + def __init__(self, pkg, do_format=True, custom_format=None): + self._pkg = None + self._do_format = do_format + self._str = None + self._location = None + if not custom_format: + if do_format: + custom_format = self._tmpl_verbose + else: + custom_format = self._tmpl_quiet + self.tmpl = Template(custom_format) + self.format_vars = LazyItemsDict() + self.pkg = pkg + + def __repr__(self): + return "<%s %s @%#8x>" % (self.__class__.__name__, self.pkg, id(self)) + + def __str__(self): + if self._str is None: + self._str = self.tmpl.safe_substitute(self.format_vars) + return self._str + + @property + def location(self): + if self._location is None: + self._location = self.format_package_location() + return self._location + + @property + def pkg(self): + """Package to format""" + return self._pkg + + @pkg.setter + def pkg(self, value): + if self._pkg == value: + return + self._pkg = value + self._location = None + + fmt_vars = self.format_vars + self.format_vars.clear() + fmt_vars.addLazySingleton("location", lambda: getattr(self, "location")) + fmt_vars.addLazySingleton("mask", self.format_mask) + fmt_vars.addLazySingleton("mask2", self.format_mask_status2) + fmt_vars.addLazySingleton("cpv", self.format_cpv) + fmt_vars.addLazySingleton("cp", self.format_cpv, "cp") + fmt_vars.addLazySingleton("category", self.format_cpv, "category") + fmt_vars.addLazySingleton("name", self.format_cpv, "name") + fmt_vars.addLazySingleton("version", self.format_cpv, "version") + fmt_vars.addLazySingleton("revision", self.format_cpv, "revision") + fmt_vars.addLazySingleton("fullversion", self.format_cpv, "fullversion") + fmt_vars.addLazySingleton("slot", self.format_slot) + fmt_vars.addLazySingleton("repo", self.pkg.repo_name) + fmt_vars.addLazySingleton("keywords", self.format_keywords) + + def format_package_location(self): + """Get the install status (in /var/db/?) and origin (from an overlay + and the Portage tree?). + + @rtype: str + @return: one of: + 'I--' : Installed but ebuild doesn't exist on system anymore + '-P-' : Not installed and from the Portage tree + '--O' : Not installed and from an overlay + 'IP-' : Installed and from the Portage tree + 'I-O' : Installed and from an overlay + """ + + result = ["-", "-", "-"] + + if self.pkg.is_installed(): + result[0] = "I" + + overlay = self.pkg.is_overlay() + if overlay is None: + pass + elif overlay: + result[2] = "O" + else: + result[1] = "P" + + return "".join(result) + + def format_mask_status(self): + """Get the mask status of a given package. + + @rtype: tuple: (int, list) + @return: int = an index for this list: + [" ", " ~", " -", "M ", "M~", "M-", "??"] + 0 = not masked + 1 = keyword masked + 2 = arch masked + 3 = hard masked + 4 = hard and keyword masked, + 5 = hard and arch masked + 6 = ebuild doesn't exist on system anymore + + list = original output of portage.getmaskingstatus + """ + + result = 0 + masking_status = self.pkg.mask_status() + if masking_status is None: + return (6, []) + + if ("~%s keyword" % self.pkg.settings("ARCH")) in masking_status: + result += 1 + if "missing keyword" in masking_status: + result += 2 + if set(("profile", "package.mask")).intersection(masking_status): + result += 3 + + return (result, masking_status) + + def format_mask_status2(self): + """Get the mask status of a given package.""" + mask = self.pkg.mask_status() + if mask: + return pp.masking(mask) + else: + arch = self.pkg.settings("ARCH") + keywords = self.pkg.environment("KEYWORDS") + mask = [ + determine_keyword(arch, portage.settings["ACCEPT_KEYWORDS"], keywords) + ] + return pp.masking(mask) + + def format_mask(self): + maskmodes = [" ", " ~", " -", "M ", "M~", "M-", "??"] + maskmode = maskmodes[self.format_mask_status()[0]] + return pp.keyword( + maskmode, + stable=not maskmode.strip(), + hard_masked=set(("M", "?", "-")).intersection(maskmode), + ) + + def format_cpv(self, attr=None): + if attr is None: + value = self.pkg.cpv + else: + value = getattr(self.pkg, attr) + if self._do_format: + return pp.cpv(value) + else: + return value + + def format_slot(self): + value = self.pkg.environment("SLOT") + if self._do_format: + return pp.slot(value) + else: + return value + + def format_keywords(self): + value = self.pkg.environment("KEYWORDS") + if self._do_format: + return pp.keyword(value) + else: + return value # vim: set ts=4 sw=4 tw=79: |