From bbcd72b5fe85fe9bbca1913f8aa22077d94e75d0 Mon Sep 17 00:00:00 2001 From: "Wolfgang E. Sanyer" Date: Mon, 20 Sep 2021 08:49:15 -0400 Subject: Change tabs to spaces (using autopep8). Also, format repo using black. The following command was used to change the tabs to spaces: autopep8 --in-place --select=E101,E11,E121,E122,E123,E124,E125,E126,E127,E128,E129,E131,E133,E20,E211,E22,E224,E224,E226,E227,E228,E231,E241,E242,E251,E252,E26,E265,E266,E27,E301,E302,E303,E304,E305,E306,W291,W293,W391 -r . And then black was run as `black .` on the entire tree Signed-off-by: Wolfgang E. Sanyer Signed-off-by: Matt Turner --- pym/gentoolkit/__init__.py | 10 +- pym/gentoolkit/atom.py | 626 +++++++------- pym/gentoolkit/base.py | 207 ++--- pym/gentoolkit/cpv.py | 400 ++++----- pym/gentoolkit/dbapi.py | 8 +- pym/gentoolkit/dependencies.py | 608 +++++++------- pym/gentoolkit/eclean/clean.py | 274 +++---- pym/gentoolkit/eclean/cli.py | 1099 ++++++++++++++----------- pym/gentoolkit/eclean/exclude.py | 483 +++++------ pym/gentoolkit/eclean/output.py | 354 ++++---- pym/gentoolkit/eclean/pkgindex.py | 148 ++-- pym/gentoolkit/eclean/search.py | 1122 ++++++++++++------------- pym/gentoolkit/ekeyword/ekeyword.py | 825 ++++++++++--------- pym/gentoolkit/ekeyword/test_ekeyword.py | 638 +++++++-------- pym/gentoolkit/enalyze/__init__.py | 163 ++-- pym/gentoolkit/enalyze/analyze.py | 928 +++++++++++---------- pym/gentoolkit/enalyze/lib.py | 685 ++++++++-------- pym/gentoolkit/enalyze/output.py | 537 ++++++------ pym/gentoolkit/enalyze/rebuild.py | 708 ++++++++-------- pym/gentoolkit/eprefix.py | 6 +- pym/gentoolkit/equery/__init__.py | 556 ++++++------- pym/gentoolkit/equery/belongs.py | 218 ++--- pym/gentoolkit/equery/check.py | 453 ++++++----- pym/gentoolkit/equery/depends.py | 325 ++++---- pym/gentoolkit/equery/depgraph.py | 395 ++++----- pym/gentoolkit/equery/files.py | 496 +++++------ pym/gentoolkit/equery/has.py | 326 ++++---- pym/gentoolkit/equery/hasuse.py | 272 ++++--- pym/gentoolkit/equery/keywords.py | 5 +- pym/gentoolkit/equery/list_.py | 424 +++++----- pym/gentoolkit/equery/meta.py | 955 +++++++++++----------- pym/gentoolkit/equery/size.py | 257 +++--- pym/gentoolkit/equery/uses.py | 522 ++++++------ pym/gentoolkit/equery/which.py | 156 ++-- pym/gentoolkit/errors.py | 205 ++--- pym/gentoolkit/eshowkw/__init__.py | 318 +++++--- pym/gentoolkit/eshowkw/display_pretty.py | 208 ++--- pym/gentoolkit/eshowkw/keywords_content.py | 756 +++++++++-------- pym/gentoolkit/eshowkw/keywords_header.py | 273 ++++--- pym/gentoolkit/flag.py | 276 +++---- pym/gentoolkit/formatters.py | 223 +++-- pym/gentoolkit/helpers.py | 492 +++++------ pym/gentoolkit/imlate/imlate.py | 915 ++++++++++++--------- pym/gentoolkit/keyword.py | 180 ++-- pym/gentoolkit/metadata.py | 454 ++++++----- pym/gentoolkit/module_base.py | 268 +++--- pym/gentoolkit/package.py | 1118 ++++++++++++------------- pym/gentoolkit/pprinter.py | 253 +++--- pym/gentoolkit/profile.py | 192 ++--- pym/gentoolkit/query.py | 720 ++++++++-------- pym/gentoolkit/revdep_rebuild/analyse.py | 781 +++++++++--------- pym/gentoolkit/revdep_rebuild/assign.py | 312 +++---- pym/gentoolkit/revdep_rebuild/cache.py | 281 ++++--- pym/gentoolkit/revdep_rebuild/collect.py | 437 +++++----- pym/gentoolkit/revdep_rebuild/rebuild.py | 266 +++--- pym/gentoolkit/revdep_rebuild/runner.py | 93 +-- pym/gentoolkit/revdep_rebuild/settings.py | 299 +++---- pym/gentoolkit/revdep_rebuild/stuff.py | 164 ++-- pym/gentoolkit/sets.py | 73 +- pym/gentoolkit/test/eclean/creator.py | 388 +++++---- pym/gentoolkit/test/eclean/distsupport.py | 885 ++++++++++---------- pym/gentoolkit/test/eclean/test_clean.py | 6 +- pym/gentoolkit/test/eclean/test_search.py | 1221 +++++++++++++++------------- pym/gentoolkit/test/equery/test_init.py | 74 +- pym/gentoolkit/test/test_atom.py | 250 +++--- pym/gentoolkit/test/test_cpv.py | 219 ++--- pym/gentoolkit/test/test_helpers.py | 123 +-- pym/gentoolkit/test/test_keyword.py | 91 ++- pym/gentoolkit/test/test_profile.py | 92 +-- pym/gentoolkit/test/test_query.py | 177 ++-- pym/gentoolkit/test/test_syntax.py | 38 +- pym/gentoolkit/textwrap_.py | 180 ++-- pym/gentoolkit/versionmatch.py | 209 +++-- 73 files changed, 14948 insertions(+), 13751 deletions(-) (limited to 'pym') diff --git a/pym/gentoolkit/__init__.py b/pym/gentoolkit/__init__.py index 9af78fc..ab9ce9d 100644 --- a/pym/gentoolkit/__init__.py +++ b/pym/gentoolkit/__init__.py @@ -10,14 +10,14 @@ import sys CONFIG = { # Color handling: -1: Use Portage settings, 0: Force off, 1: Force on - 'color': -1, + "color": -1, # Guess piping output: - 'piping': False if sys.stdout.isatty() else True, + "piping": False if sys.stdout.isatty() else True, # Set some defaults: - 'quiet': False, + "quiet": False, # verbose is True if not quiet and not piping - 'verbose': True, - 'debug': False + "verbose": True, + "debug": False, } # vim: set ts=8 sw=4 tw=79: diff --git a/pym/gentoolkit/atom.py b/pym/gentoolkit/atom.py index 364fe4e..dd843d7 100644 --- a/pym/gentoolkit/atom.py +++ b/pym/gentoolkit/atom.py @@ -6,7 +6,7 @@ """Subclasses portage.dep.Atom to provide methods on a Gentoo atom string.""" -__all__ = ('Atom',) +__all__ = ("Atom",) # ======= # Imports @@ -24,319 +24,319 @@ from gentoolkit import errors # Classes # ======= + class Atom(portage.dep.Atom, CPV): - """Portage's Atom class with improvements from pkgcore. + """Portage's Atom class with improvements from pkgcore. + + portage.dep.Atom provides the following instance variables: + + @type operator: str + @ivar operator: one of ('=', '=*', '<', '>', '<=', '>=', '~', None) + @type cp: str + @ivar cp: cat/pkg + @type cpv: str + @ivar cpv: cat/pkg-ver (if ver) + @type slot: str or None (modified to tuple if not None) + @ivar slot: slot passed in as cpv:# + """ + + # Necessary for Portage versions < 2.1.7 + _atoms = weakref.WeakValueDictionary() + + def __init__(self, atom): + self.atom = atom + self.operator = self.blocker = self.use = self.slot = None + + try: + portage.dep.Atom.__init__(self, atom) + except portage.exception.InvalidAtom: + raise errors.GentoolkitInvalidAtom(atom) + + # Make operator compatible with intersects + if self.operator is None: + self.operator = "" + + CPV.__init__(self, self.cpv) + + # use_conditional is USE flag condition for this Atom to be required: + # For: !build? ( >=sys-apps/sed-4.0.5 ), use_conditional = '!build' + self.use_conditional = None + + def __eq__(self, other): + if not isinstance(other, self.__class__): + err = "other isn't of %s type, is %s" + raise TypeError(err % (self.__class__, other.__class__)) + + if self.operator != other.operator: + return False + + if not CPV.__eq__(self, other): + return False + + if bool(self.blocker) != bool(other.blocker): + return False + + if self.blocker and other.blocker: + if self.blocker.overlap.forbid != other.blocker.overlap.forbid: + return False + + if self.use_conditional != other.use_conditional: + return False + + # Don't believe Portage has something like this + # c = cmp(self.negate_vers, other.negate_vers) + # if c: + # return c + + if self.slot != other.slot: + return False + + this_use = None + if self.use is not None: + this_use = sorted(self.use.tokens) + that_use = None + if other.use is not None: + that_use = sorted(other.use.tokens) + if this_use != that_use: + return False + + # Not supported by Portage Atom yet + # return cmp(self.repo_name, other.repo_name) + return True + + def __hash__(self): + return hash(self.atom) + + def __ne__(self, other): + return not self == other + + def __lt__(self, other): + if not isinstance(other, self.__class__): + err = "other isn't of %s type, is %s" + raise TypeError(err % (self.__class__, other.__class__)) + + if self.operator != other.operator: + return self.operator < other.operator + + if not CPV.__eq__(self, other): + return CPV.__lt__(self, other) + + if bool(self.blocker) != bool(other.blocker): + # We want non blockers, then blockers, so only return True + # if self.blocker is True and other.blocker is False. + return bool(self.blocker) > bool(other.blocker) + + if self.blocker and other.blocker: + if self.blocker.overlap.forbid != other.blocker.overlap.forbid: + # we want !! prior to ! + return self.blocker.overlap.forbid < other.blocker.overlap.forbid + + # Don't believe Portage has something like this + # c = cmp(self.negate_vers, other.negate_vers) + # if c: + # return c + + if self.slot != other.slot: + if self.slot is None: + return False + elif other.slot is None: + return True + return self.slot < other.slot + + this_use = [] + if self.use is not None: + this_use = sorted(self.use.tokens) + that_use = [] + if other.use is not None: + that_use = sorted(other.use.tokens) + if this_use != that_use: + return this_use < that_use + + # Not supported by Portage Atom yet + # return cmp(self.repo_name, other.repo_name) + + return False + + def __gt__(self, other): + if not isinstance(other, self.__class__): + err = "other isn't of %s type, is %s" + raise TypeError(err % (self.__class__, other.__class__)) + + return not self <= other + + def __le__(self, other): + if not isinstance(other, self.__class__): + raise TypeError( + "other isn't of %s type, is %s" % (self.__class__, other.__class__) + ) + return self < other or self == other + + def __ge__(self, other): + if not isinstance(other, self.__class__): + raise TypeError( + "other isn't of %s type, is %s" % (self.__class__, other.__class__) + ) + return self > other or self == other + + def __repr__(self): + uc = self.use_conditional + uc = "%s? " % uc if uc is not None else "" + return "<%s %r>" % (self.__class__.__name__, "%s%s" % (uc, self.atom)) + + def __setattr__(self, name, value): + object.__setattr__(self, name, value) + + def intersects(self, other): + """Check if a passed in package atom "intersects" this atom. + + Lifted from pkgcore. + + Two atoms "intersect" if a package can be constructed that + matches both: + - if you query for just "dev-lang/python" it "intersects" both + "dev-lang/python" and ">=dev-lang/python-2.4" + - if you query for "=dev-lang/python-2.4" it "intersects" + ">=dev-lang/python-2.4" and "dev-lang/python" but not + "" in self.operator and ">" in other.operator + ): + return True + + # If one of us is an exact match we intersect if the other matches it: + if self.operator == "=": + if other.operator == "=*": + return self.fullversion.startswith(other.fullversion) + return VersionMatch(other, op=other.operator).match(self) + if other.operator == "=": + if self.operator == "=*": + return other.fullversion.startswith(self.fullversion) + return VersionMatch(self, op=self.operator).match(other) + + # If we are both ~ matches we match if we are identical: + if self.operator == other.operator == "~": + return self.version == other.version and self.revision == other.revision + + # If we are both glob matches we match if one of us matches the other. + if self.operator == other.operator == "=*": + return self.fullversion.startswith( + other.fullversion + ) or other.fullversion.startswith(self.fullversion) + + # If one of us is a glob match and the other a ~ we match if the glob + # matches the ~ (ignoring a revision on the glob): + if self.operator == "=*" and other.operator == "~": + return other.fullversion.startswith(self.version) + if other.operator == "=*" and self.operator == "~": + return self.fullversion.startswith(other.version) + + # If we get here at least one of us is a <, <=, > or >=: + if self.operator in ("<", "<=", ">", ">="): + ranged, ranged.operator = self, self.operator + else: + ranged, ranged.operator = other, other.operator + other, other.operator = self, self.operator + + if "<" in other.operator or ">" in other.operator: + # We are both ranged, and in the opposite "direction" (or + # we would have matched above). We intersect if we both + # match the other's endpoint (just checking one endpoint + # is not enough, it would give a false positive on <=2 vs >2) + return VersionMatch(other, op=other.operator).match( + ranged + ) and VersionMatch(ranged, op=ranged.operator).match(other) + + if other.operator == "~": + # Other definitely matches its own version. If ranged also + # does we're done: + if VersionMatch(ranged, op=ranged.operator).match(other): + return True + # The only other case where we intersect is if ranged is a + # > or >= on other's version and a nonzero revision. In + # that case other will match ranged. Be careful not to + # give a false positive for ~2 vs <2 here: + return ranged.operator in (">", ">=") and VersionMatch( + other, op=other.operator + ).match(ranged) + + if other.operator == "=*": + # a glob match definitely matches its own version, so if + # ranged does too we're done: + if VersionMatch(ranged, op=ranged.operator).match(other): + return True + if "<" in ranged.operator: + # If other.revision is not defined then other does not + # match anything smaller than its own fullversion: + if other.revision: + return False + + # If other.revision is defined then we can always + # construct a package smaller than other.fullversion by + # tagging e.g. an _alpha1 on. + return ranged.fullversion.startswith(other.version) + else: + # Remaining cases where this intersects: there is a + # package greater than ranged.fullversion and + # other.fullversion that they both match. + return ranged.fullversion.startswith(other.version) + + # Handled all possible ops. + raise NotImplementedError( + "Someone added an operator without adding it to intersects" + ) + + def get_depstr(self): + """Returns a string representation of the original dep""" + uc = self.use_conditional + uc = "%s? " % uc if uc is not None else "" + return "%s%s" % (uc, self.atom) - portage.dep.Atom provides the following instance variables: - - @type operator: str - @ivar operator: one of ('=', '=*', '<', '>', '<=', '>=', '~', None) - @type cp: str - @ivar cp: cat/pkg - @type cpv: str - @ivar cpv: cat/pkg-ver (if ver) - @type slot: str or None (modified to tuple if not None) - @ivar slot: slot passed in as cpv:# - """ - - # Necessary for Portage versions < 2.1.7 - _atoms = weakref.WeakValueDictionary() - - def __init__(self, atom): - self.atom = atom - self.operator = self.blocker = self.use = self.slot = None - - try: - portage.dep.Atom.__init__(self, atom) - except portage.exception.InvalidAtom: - raise errors.GentoolkitInvalidAtom(atom) - - # Make operator compatible with intersects - if self.operator is None: - self.operator = '' - - CPV.__init__(self, self.cpv) - - # use_conditional is USE flag condition for this Atom to be required: - # For: !build? ( >=sys-apps/sed-4.0.5 ), use_conditional = '!build' - self.use_conditional = None - - def __eq__(self, other): - if not isinstance(other, self.__class__): - err = "other isn't of %s type, is %s" - raise TypeError(err % (self.__class__, other.__class__)) - - if self.operator != other.operator: - return False - - if not CPV.__eq__(self, other): - return False - - if bool(self.blocker) != bool(other.blocker): - return False - - if self.blocker and other.blocker: - if self.blocker.overlap.forbid != other.blocker.overlap.forbid: - return False - - if self.use_conditional != other.use_conditional: - return False - - # Don't believe Portage has something like this - #c = cmp(self.negate_vers, other.negate_vers) - #if c: - # return c - - if self.slot != other.slot: - return False - - this_use = None - if self.use is not None: - this_use = sorted(self.use.tokens) - that_use = None - if other.use is not None: - that_use = sorted(other.use.tokens) - if this_use != that_use: - return False - - # Not supported by Portage Atom yet - #return cmp(self.repo_name, other.repo_name) - return True - - def __hash__(self): - return hash(self.atom) - - def __ne__(self, other): - return not self == other - - def __lt__(self, other): - if not isinstance(other, self.__class__): - err = "other isn't of %s type, is %s" - raise TypeError(err % (self.__class__, other.__class__)) - - if self.operator != other.operator: - return self.operator < other.operator - - if not CPV.__eq__(self, other): - return CPV.__lt__(self, other) - - if bool(self.blocker) != bool(other.blocker): - # We want non blockers, then blockers, so only return True - # if self.blocker is True and other.blocker is False. - return bool(self.blocker) > bool(other.blocker) - - if self.blocker and other.blocker: - if self.blocker.overlap.forbid != other.blocker.overlap.forbid: - # we want !! prior to ! - return (self.blocker.overlap.forbid < - other.blocker.overlap.forbid) - - # Don't believe Portage has something like this - #c = cmp(self.negate_vers, other.negate_vers) - #if c: - # return c - - if self.slot != other.slot: - if self.slot is None: - return False - elif other.slot is None: - return True - return self.slot < other.slot - - this_use = [] - if self.use is not None: - this_use = sorted(self.use.tokens) - that_use = [] - if other.use is not None: - that_use = sorted(other.use.tokens) - if this_use != that_use: - return this_use < that_use - - # Not supported by Portage Atom yet - #return cmp(self.repo_name, other.repo_name) - - return False - - def __gt__(self, other): - if not isinstance(other, self.__class__): - err = "other isn't of %s type, is %s" - raise TypeError(err % (self.__class__, other.__class__)) - - return not self <= other - - def __le__(self, other): - if not isinstance(other, self.__class__): - raise TypeError("other isn't of %s type, is %s" % ( - self.__class__, other.__class__) - ) - return self < other or self == other - - def __ge__(self, other): - if not isinstance(other, self.__class__): - raise TypeError("other isn't of %s type, is %s" % ( - self.__class__, other.__class__) - ) - return self > other or self == other - - def __repr__(self): - uc = self.use_conditional - uc = "%s? " % uc if uc is not None else '' - return "<%s %r>" % (self.__class__.__name__, "%s%s" % (uc, self.atom)) - - def __setattr__(self, name, value): - object.__setattr__(self, name, value) - - def intersects(self, other): - """Check if a passed in package atom "intersects" this atom. - - Lifted from pkgcore. - - Two atoms "intersect" if a package can be constructed that - matches both: - - if you query for just "dev-lang/python" it "intersects" both - "dev-lang/python" and ">=dev-lang/python-2.4" - - if you query for "=dev-lang/python-2.4" it "intersects" - ">=dev-lang/python-2.4" and "dev-lang/python" but not - "' in self.operator and '>' in other.operator)): - return True - - # If one of us is an exact match we intersect if the other matches it: - if self.operator == '=': - if other.operator == '=*': - return self.fullversion.startswith(other.fullversion) - return VersionMatch(other, op=other.operator).match(self) - if other.operator == '=': - if self.operator == '=*': - return other.fullversion.startswith(self.fullversion) - return VersionMatch(self, op=self.operator).match(other) - - # If we are both ~ matches we match if we are identical: - if self.operator == other.operator == '~': - return (self.version == other.version and - self.revision == other.revision) - - # If we are both glob matches we match if one of us matches the other. - if self.operator == other.operator == '=*': - return (self.fullversion.startswith(other.fullversion) or - other.fullversion.startswith(self.fullversion)) - - # If one of us is a glob match and the other a ~ we match if the glob - # matches the ~ (ignoring a revision on the glob): - if self.operator == '=*' and other.operator == '~': - return other.fullversion.startswith(self.version) - if other.operator == '=*' and self.operator == '~': - return self.fullversion.startswith(other.version) - - # If we get here at least one of us is a <, <=, > or >=: - if self.operator in ('<', '<=', '>', '>='): - ranged, ranged.operator = self, self.operator - else: - ranged, ranged.operator = other, other.operator - other, other.operator = self, self.operator - - if '<' in other.operator or '>' in other.operator: - # We are both ranged, and in the opposite "direction" (or - # we would have matched above). We intersect if we both - # match the other's endpoint (just checking one endpoint - # is not enough, it would give a false positive on <=2 vs >2) - return ( - VersionMatch(other, op=other.operator).match(ranged) and - VersionMatch(ranged, op=ranged.operator).match(other) - ) - - if other.operator == '~': - # Other definitely matches its own version. If ranged also - # does we're done: - if VersionMatch(ranged, op=ranged.operator).match(other): - return True - # The only other case where we intersect is if ranged is a - # > or >= on other's version and a nonzero revision. In - # that case other will match ranged. Be careful not to - # give a false positive for ~2 vs <2 here: - return (ranged.operator in ('>', '>=') and - VersionMatch(other, op=other.operator).match(ranged)) - - if other.operator == '=*': - # a glob match definitely matches its own version, so if - # ranged does too we're done: - if VersionMatch(ranged, op=ranged.operator).match(other): - return True - if '<' in ranged.operator: - # If other.revision is not defined then other does not - # match anything smaller than its own fullversion: - if other.revision: - return False - - # If other.revision is defined then we can always - # construct a package smaller than other.fullversion by - # tagging e.g. an _alpha1 on. - return ranged.fullversion.startswith(other.version) - else: - # Remaining cases where this intersects: there is a - # package greater than ranged.fullversion and - # other.fullversion that they both match. - return ranged.fullversion.startswith(other.version) - - # Handled all possible ops. - raise NotImplementedError( - 'Someone added an operator without adding it to intersects') - - def get_depstr(self): - """Returns a string representation of the original dep - """ - uc = self.use_conditional - uc = "%s? " % uc if uc is not None else '' - return "%s%s" % (uc, self.atom) # vim: set ts=4 sw=4 tw=79: diff --git a/pym/gentoolkit/base.py b/pym/gentoolkit/base.py index 372ed74..8dfa2db 100644 --- a/pym/gentoolkit/base.py +++ b/pym/gentoolkit/base.py @@ -6,7 +6,7 @@ """Gentoolkit Base Module class to hold common module operation functions """ -__docformat__ = 'epytext' +__docformat__ = "epytext" import os @@ -18,127 +18,132 @@ from gentoolkit.formatters import format_options GLOBAL_OPTIONS = ( - (" -h, --help", "display this help message"), - (" -q, --quiet", "minimal output"), - (" -C, --no-color", "turn off colors"), - (" -N, --no-pipe", "turn off pipe detection"), - (" -V, --version", "display version info") + (" -h, --help", "display this help message"), + (" -q, --quiet", "minimal output"), + (" -C, --no-color", "turn off colors"), + (" -N, --no-pipe", "turn off pipe detection"), + (" -V, --version", "display version info"), ) def initialize_configuration(): - """Setup the standard equery config""" - - # Get terminal size - term_width = pp.output.get_term_size()[1] - if term_width < 1: - # get_term_size() failed. Set a sane default width: - term_width = 80 - # Terminal size, minus a 1-char margin for text wrapping - gentoolkit.CONFIG['termWidth'] = term_width - 1 - # Guess color output - if (gentoolkit.CONFIG['color'] == -1 and (not sys.stdout.isatty() or - os.getenv("NOCOLOR") in ("yes", "true")) or gentoolkit.CONFIG['color'] == 0): - pp.output.nocolor() - gentoolkit.CONFIG['verbose'] = not gentoolkit.CONFIG['piping'] + """Setup the standard equery config""" + + # Get terminal size + term_width = pp.output.get_term_size()[1] + if term_width < 1: + # get_term_size() failed. Set a sane default width: + term_width = 80 + # Terminal size, minus a 1-char margin for text wrapping + gentoolkit.CONFIG["termWidth"] = term_width - 1 + # Guess color output + if ( + gentoolkit.CONFIG["color"] == -1 + and (not sys.stdout.isatty() or os.getenv("NOCOLOR") in ("yes", "true")) + or gentoolkit.CONFIG["color"] == 0 + ): + pp.output.nocolor() + gentoolkit.CONFIG["verbose"] = not gentoolkit.CONFIG["piping"] def split_arguments(args): - """Separate module name from module arguments""" + """Separate module name from module arguments""" - return args.pop(0), args + return args.pop(0), args def main_usage(module_info): - """Return the main usage message for analyse""" - return "%(usage)s %(product)s [%(g_opts)s] %(mod_name)s [%(mod_opts)s]" % { - 'usage': pp.emph("Usage:"), - 'product': pp.productname(module_info["__productname__"]), - 'g_opts': pp.globaloption("global-options"), - 'mod_name': pp.command("module-name"), - 'mod_opts': pp.localoption("module-options") - } + """Return the main usage message for analyse""" + return "%(usage)s %(product)s [%(g_opts)s] %(mod_name)s [%(mod_opts)s]" % { + "usage": pp.emph("Usage:"), + "product": pp.productname(module_info["__productname__"]), + "g_opts": pp.globaloption("global-options"), + "mod_name": pp.command("module-name"), + "mod_opts": pp.localoption("module-options"), + } def print_version(module_info): - """Print the version of this tool to the console.""" + """Print the version of this tool to the console.""" - print("%(product)s (%(version)s) - %(docstring)s" % { - "product": pp.productname(module_info["__productname__"]), - "version": module_info["__version__"], - "docstring": module_info["__doc__"] - }) + print( + "%(product)s (%(version)s) - %(docstring)s" + % { + "product": pp.productname(module_info["__productname__"]), + "version": module_info["__version__"], + "docstring": module_info["__doc__"], + } + ) def print_help(module_info, formatted_options=None, with_description=True): - """Print description, usage and a detailed help message. - - @param with_description (bool): Option to print module's __doc__ or not - """ - - if with_description: - print() - print(module_info["__doc__"]) - print() - print(main_usage(module_info)) - print() - print(pp.globaloption("global options")) - print(format_options(GLOBAL_OPTIONS)) - print() - if formatted_options: - print(pp.command("modules") + " (" + pp.command("short name") + ")") - print(format_options(formatted_options)) - else: - print("Error: calling function did not supply formatted options") - print() + """Print description, usage and a detailed help message. + + @param with_description (bool): Option to print module's __doc__ or not + """ + + if with_description: + print() + print(module_info["__doc__"]) + print() + print(main_usage(module_info)) + print() + print(pp.globaloption("global options")) + print(format_options(GLOBAL_OPTIONS)) + print() + if formatted_options: + print(pp.command("modules") + " (" + pp.command("short name") + ")") + print(format_options(formatted_options)) + else: + print("Error: calling function did not supply formatted options") + print() def parse_global_options(global_opts, args, module_info, formatted_options): - """Parse global input args and return True if we should display help for - the called module, else False (or display help and exit from here). - """ - - need_help = False - do_help = False - opts = (opt[0] for opt in global_opts) - for opt in opts: - if opt in ('-h', '--help'): - do_help = True - if args: - need_help = True - else: - do_help = True - elif opt in ('-q','--quiet'): - gentoolkit.CONFIG['quiet'] = True - elif opt in ('-C', '--no-color', '--nocolor'): - gentoolkit.CONFIG['color'] = 0 - pp.output.nocolor() - elif opt in ('-N', '--no-pipe'): - gentoolkit.CONFIG['piping'] = False - elif opt in ('-V', '--version'): - print_version(module_info) - sys.exit(0) - elif opt in ('--debug'): - gentoolkit.CONFIG['debug'] = True - if do_help: - print_help( module_info, formatted_options) - sys.exit(0) - return need_help + """Parse global input args and return True if we should display help for + the called module, else False (or display help and exit from here). + """ + + need_help = False + do_help = False + opts = (opt[0] for opt in global_opts) + for opt in opts: + if opt in ("-h", "--help"): + do_help = True + if args: + need_help = True + else: + do_help = True + elif opt in ("-q", "--quiet"): + gentoolkit.CONFIG["quiet"] = True + elif opt in ("-C", "--no-color", "--nocolor"): + gentoolkit.CONFIG["color"] = 0 + pp.output.nocolor() + elif opt in ("-N", "--no-pipe"): + gentoolkit.CONFIG["piping"] = False + elif opt in ("-V", "--version"): + print_version(module_info) + sys.exit(0) + elif opt in ("--debug"): + gentoolkit.CONFIG["debug"] = True + if do_help: + print_help(module_info, formatted_options) + sys.exit(0) + return need_help def mod_usage(mod_name="module", arg="pkgspec", optional=False): - """Provide a consistant usage message to the calling module. - - @type arg: string - @param arg: what kind of argument the module takes (pkgspec, filename, etc) - @type optional: bool - @param optional: is the argument optional? - """ - - return "%(usage)s: %(mod_name)s [%(opts)s] %(arg)s" % { - 'usage': pp.emph("Usage"), - 'mod_name': pp.command(mod_name), - 'opts': pp.localoption("options"), - 'arg': ("[%s]" % pp.emph(arg)) if optional else pp.emph(arg) - } - + """Provide a consistant usage message to the calling module. + + @type arg: string + @param arg: what kind of argument the module takes (pkgspec, filename, etc) + @type optional: bool + @param optional: is the argument optional? + """ + + return "%(usage)s: %(mod_name)s [%(opts)s] %(arg)s" % { + "usage": pp.emph("Usage"), + "mod_name": pp.command(mod_name), + "opts": pp.localoption("options"), + "arg": ("[%s]" % pp.emph(arg)) if optional else pp.emph(arg), + } diff --git a/pym/gentoolkit/cpv.py b/pym/gentoolkit/cpv.py index 5238e24..6b2a533 100644 --- a/pym/gentoolkit/cpv.py +++ b/pym/gentoolkit/cpv.py @@ -6,11 +6,7 @@ """Provides attributes and methods for a category/package-version string.""" -__all__ = ( - 'CPV', - 'compare_strs', - 'split_cpv' -) +__all__ = ("CPV", "compare_strs", "split_cpv") # ======= # Imports @@ -26,228 +22,232 @@ from gentoolkit import errors # Globals # ======= -isvalid_version_re = re.compile(r"^(?:cvs\.)?(?:\d+)(?:\.\d+)*[a-z]?" - r"(?:_(p(?:re)?|beta|alpha|rc)\d*)*$") +isvalid_version_re = re.compile( + r"^(?:cvs\.)?(?:\d+)(?:\.\d+)*[a-z]?" r"(?:_(p(?:re)?|beta|alpha|rc)\d*)*$" +) isvalid_cat_re = re.compile(r"^(?:[a-zA-Z0-9][-a-zA-Z0-9+._]*(?:/(?!$))?)+$") _pkg_re = re.compile(r"^[a-zA-Z0-9+._]+$") # Prefix specific revision is of the form -r0+.+ -isvalid_rev_re = re.compile(r'(\d+|0\d+\.\d+)') +isvalid_rev_re = re.compile(r"(\d+|0\d+\.\d+)") # ======= # Classes # ======= + class CPV: - """Provides methods on a category/package-version string. - - Will also correctly split just a package or package-version string. - - Example usage: - >>> from gentoolkit.cpv import CPV - >>> cpv = CPV('sys-apps/portage-2.2-r1') - >>> cpv.category, cpv.name, cpv.fullversion - ('sys-apps', 'portage', '2.2-r1') - >>> str(cpv) - 'sys-apps/portage-2.2-r1' - >>> # An 'rc' (release candidate) version is less than non 'rc' version: - ... CPV('sys-apps/portage-2') > CPV('sys-apps/portage-2_rc10') - True - """ - - def __init__(self, cpv, validate=False): - self.cpv = cpv - self._category = None - self._name = None - self._version = None - self._revision = None - self._cp = None - self._fullversion = None - - self.validate = validate - if validate and not self.name: - raise errors.GentoolkitInvalidCPV(cpv) - - @property - def category(self): - if self._category is None: - self._set_cpv_chunks() - return self._category - - @property - def name(self): - if self._name is None: - self._set_cpv_chunks() - return self._name - - @property - def version(self): - if self._version is None: - self._set_cpv_chunks() - return self._version - - @property - def revision(self): - if self._revision is None: - self._set_cpv_chunks() - return self._revision - - @property - def cp(self): - if self._cp is None: - sep = '/' if self.category else '' - self._cp = sep.join((self.category, self.name)) - return self._cp - - @property - def fullversion(self): - if self._fullversion is None: - sep = '-' if self.revision else '' - self._fullversion = sep.join((self.version, self.revision)) - return self._fullversion - - def _set_cpv_chunks(self): - chunks = split_cpv(self.cpv, validate=self.validate) - self._category = chunks[0] - self._name = chunks[1] - self._version = chunks[2] - self._revision = chunks[3] - - def __eq__(self, other): - if not isinstance(other, self.__class__): - return False - return self.cpv == other.cpv - - def __hash__(self): - return hash(self.cpv) - - def __ne__(self, other): - return not self == other - - def __lt__(self, other): - if not isinstance(other, self.__class__): - raise TypeError("other isn't of %s type, is %s" % ( - self.__class__, other.__class__) - ) - - if self.category != other.category: - return self.category < other.category - elif self.name != other.name: - return self.name < other.name - else: - # FIXME: this cmp() hack is for vercmp not using -1,0,1 - # See bug 266493; this was fixed in portage-2.2_rc31 - #return vercmp(self.fullversion, other.fullversion) - return vercmp(self.fullversion, other.fullversion) < 0 - - def __gt__(self, other): - if not isinstance(other, self.__class__): - raise TypeError("other isn't of %s type, is %s" % ( - self.__class__, other.__class__) - ) - return not self <= other - - def __le__(self, other): - if not isinstance(other, self.__class__): - raise TypeError("other isn't of %s type, is %s" % ( - self.__class__, other.__class__) - ) - return self < other or self == other - - def __ge__(self, other): - if not isinstance(other, self.__class__): - raise TypeError("other isn't of %s type, is %s" % ( - self.__class__, other.__class__) - ) - return self > other or self == other - - def __repr__(self): - return "<%s %r>" % (self.__class__.__name__, str(self)) - - def __str__(self): - return self.cpv + """Provides methods on a category/package-version string. + + Will also correctly split just a package or package-version string. + + Example usage: + >>> from gentoolkit.cpv import CPV + >>> cpv = CPV('sys-apps/portage-2.2-r1') + >>> cpv.category, cpv.name, cpv.fullversion + ('sys-apps', 'portage', '2.2-r1') + >>> str(cpv) + 'sys-apps/portage-2.2-r1' + >>> # An 'rc' (release candidate) version is less than non 'rc' version: + ... CPV('sys-apps/portage-2') > CPV('sys-apps/portage-2_rc10') + True + """ + + def __init__(self, cpv, validate=False): + self.cpv = cpv + self._category = None + self._name = None + self._version = None + self._revision = None + self._cp = None + self._fullversion = None + + self.validate = validate + if validate and not self.name: + raise errors.GentoolkitInvalidCPV(cpv) + + @property + def category(self): + if self._category is None: + self._set_cpv_chunks() + return self._category + + @property + def name(self): + if self._name is None: + self._set_cpv_chunks() + return self._name + + @property + def version(self): + if self._version is None: + self._set_cpv_chunks() + return self._version + + @property + def revision(self): + if self._revision is None: + self._set_cpv_chunks() + return self._revision + + @property + def cp(self): + if self._cp is None: + sep = "/" if self.category else "" + self._cp = sep.join((self.category, self.name)) + return self._cp + + @property + def fullversion(self): + if self._fullversion is None: + sep = "-" if self.revision else "" + self._fullversion = sep.join((self.version, self.revision)) + return self._fullversion + + def _set_cpv_chunks(self): + chunks = split_cpv(self.cpv, validate=self.validate) + self._category = chunks[0] + self._name = chunks[1] + self._version = chunks[2] + self._revision = chunks[3] + + def __eq__(self, other): + if not isinstance(other, self.__class__): + return False + return self.cpv == other.cpv + + def __hash__(self): + return hash(self.cpv) + + def __ne__(self, other): + return not self == other + + def __lt__(self, other): + if not isinstance(other, self.__class__): + raise TypeError( + "other isn't of %s type, is %s" % (self.__class__, other.__class__) + ) + + if self.category != other.category: + return self.category < other.category + elif self.name != other.name: + return self.name < other.name + else: + # FIXME: this cmp() hack is for vercmp not using -1,0,1 + # See bug 266493; this was fixed in portage-2.2_rc31 + # return vercmp(self.fullversion, other.fullversion) + return vercmp(self.fullversion, other.fullversion) < 0 + + def __gt__(self, other): + if not isinstance(other, self.__class__): + raise TypeError( + "other isn't of %s type, is %s" % (self.__class__, other.__class__) + ) + return not self <= other + + def __le__(self, other): + if not isinstance(other, self.__class__): + raise TypeError( + "other isn't of %s type, is %s" % (self.__class__, other.__class__) + ) + return self < other or self == other + + def __ge__(self, other): + if not isinstance(other, self.__class__): + raise TypeError( + "other isn't of %s type, is %s" % (self.__class__, other.__class__) + ) + return self > other or self == other + + def __repr__(self): + return "<%s %r>" % (self.__class__.__name__, str(self)) + + def __str__(self): + return self.cpv # ========= # Functions # ========= + def compare_strs(pkg1, pkg2): - """Similar to the builtin cmp, but for package strings. Usually called - as: package_list.sort(cpv.compare_strs) + """Similar to the builtin cmp, but for package strings. Usually called + as: package_list.sort(cpv.compare_strs) - An alternative is to use the CPV descriptor from gentoolkit.cpv: - >>> package_list = ['sys-apps/portage-9999', 'media-video/ffmpeg-9999'] - >>> cpvs = sorted(CPV(x) for x in package_list) + An alternative is to use the CPV descriptor from gentoolkit.cpv: + >>> package_list = ['sys-apps/portage-9999', 'media-video/ffmpeg-9999'] + >>> cpvs = sorted(CPV(x) for x in package_list) - @see: >>> help(cmp) - """ + @see: >>> help(cmp) + """ - pkg1 = catpkgsplit(pkg1) - pkg2 = catpkgsplit(pkg2) - if pkg1[0] != pkg2[0]: - return -1 if pkg1[0] < pkg2[0] else 1 - elif pkg1[1] != pkg2[1]: - return -1 if pkg1[1] < pkg2[1] else 1 - else: - return pkgcmp(pkg1[1:], pkg2[1:]) + pkg1 = catpkgsplit(pkg1) + pkg2 = catpkgsplit(pkg2) + if pkg1[0] != pkg2[0]: + return -1 if pkg1[0] < pkg2[0] else 1 + elif pkg1[1] != pkg2[1]: + return -1 if pkg1[1] < pkg2[1] else 1 + else: + return pkgcmp(pkg1[1:], pkg2[1:]) def split_cpv(cpv, validate=True): - """Split a cpv into category, name, version and revision. - - Modified from pkgcore.ebuild.cpv - - @type cpv: str - @param cpv: pkg, cat/pkg, pkg-ver, cat/pkg-ver - @rtype: tuple - @return: (category, pkg_name, version, revision) - Each tuple element is a string or empty string (""). - """ - - category = name = version = revision = '' - - try: - category, pkgver = cpv.rsplit("/", 1) - except ValueError: - pkgver = cpv - if validate and category and not isvalid_cat_re.match(category): - raise errors.GentoolkitInvalidCPV(cpv) - pkg_chunks = pkgver.split("-") - lpkg_chunks = len(pkg_chunks) - if lpkg_chunks == 1: - return (category, pkg_chunks[0], version, revision) - if isvalid_rev(pkg_chunks[-1]): - if lpkg_chunks < 3: - # needs at least ('pkg', 'ver', 'rev') - raise errors.GentoolkitInvalidCPV(cpv) - rev = pkg_chunks.pop(-1) - if rev: - revision = rev - - if isvalid_version_re.match(pkg_chunks[-1]): - version = pkg_chunks.pop(-1) - - if not isvalid_pkg_name(pkg_chunks): - raise errors.GentoolkitInvalidCPV(cpv) - name = '-'.join(pkg_chunks) - - return (category, name, version, revision) + """Split a cpv into category, name, version and revision. + + Modified from pkgcore.ebuild.cpv + + @type cpv: str + @param cpv: pkg, cat/pkg, pkg-ver, cat/pkg-ver + @rtype: tuple + @return: (category, pkg_name, version, revision) + Each tuple element is a string or empty string (""). + """ + + category = name = version = revision = "" + + try: + category, pkgver = cpv.rsplit("/", 1) + except ValueError: + pkgver = cpv + if validate and category and not isvalid_cat_re.match(category): + raise errors.GentoolkitInvalidCPV(cpv) + pkg_chunks = pkgver.split("-") + lpkg_chunks = len(pkg_chunks) + if lpkg_chunks == 1: + return (category, pkg_chunks[0], version, revision) + if isvalid_rev(pkg_chunks[-1]): + if lpkg_chunks < 3: + # needs at least ('pkg', 'ver', 'rev') + raise errors.GentoolkitInvalidCPV(cpv) + rev = pkg_chunks.pop(-1) + if rev: + revision = rev + + if isvalid_version_re.match(pkg_chunks[-1]): + version = pkg_chunks.pop(-1) + + if not isvalid_pkg_name(pkg_chunks): + raise errors.GentoolkitInvalidCPV(cpv) + name = "-".join(pkg_chunks) + + return (category, name, version, revision) def isvalid_pkg_name(chunks): - if not chunks[0]: - # this means a leading - - return False - mf = _pkg_re.match - if not all(not s or mf(s) for s in chunks): - return False - if len(chunks) > 1 and chunks[-1].isdigit(): - # not allowed. - return False - return True + if not chunks[0]: + # this means a leading - + return False + mf = _pkg_re.match + if not all(not s or mf(s) for s in chunks): + return False + if len(chunks) > 1 and chunks[-1].isdigit(): + # not allowed. + return False + return True def isvalid_rev(s): - return s and s[0] == 'r' and isvalid_rev_re.match(s[1:]) + return s and s[0] == "r" and isvalid_rev_re.match(s[1:]) + # vim: set ts=4 sw=4 tw=79: diff --git a/pym/gentoolkit/dbapi.py b/pym/gentoolkit/dbapi.py index be37f32..9e480f8 100644 --- a/pym/gentoolkit/dbapi.py +++ b/pym/gentoolkit/dbapi.py @@ -9,14 +9,16 @@ take advantage of them being lazy-loaded. """ -print("gentoolkit.dbapi is deprecated.\n", - "Please migrate to using the assigned calls directly") +print( + "gentoolkit.dbapi is deprecated.\n", + "Please migrate to using the assigned calls directly", +) import portage BINDB = portage.db[portage.root]["bintree"].dbapi PORTDB = portage.db[portage.root]["porttree"].dbapi VARDB = portage.db[portage.root]["vartree"].dbapi -#virtuals = portage.db[portage.root]["virtuals"] +# virtuals = portage.db[portage.root]["virtuals"] # vim: set ts=8 sw=4 tw=79: diff --git a/pym/gentoolkit/dependencies.py b/pym/gentoolkit/dependencies.py index 38676a2..f94b82e 100644 --- a/pym/gentoolkit/dependencies.py +++ b/pym/gentoolkit/dependencies.py @@ -4,8 +4,8 @@ """Provides a class for easy calculating dependencies for a given CPV.""" -__docformat__ = 'epytext' -__all__ = ('Dependencies',) +__docformat__ = "epytext" +__all__ = ("Dependencies",) # ======= # Imports @@ -23,302 +23,314 @@ from gentoolkit.query import Query # Classes # ======= + class Dependencies(Query): - """Access a package's dependencies and reverse dependencies. - - Example usage: - >>> from gentoolkit.dependencies import Dependencies - >>> portage = Dependencies('sys-apps/portage-9999') - >>> portage - - >>> # All methods return gentoolkit.atom.Atom instances - ... portage.get_depend() - ... # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE - [, - =dev-lang/python-2.7'>, ...] - - """ - def __init__(self, query, parser=None): - Query.__init__(self, query) - self.use = [] - self.depatom = str() - - # Allow a custom parser function: - self.parser = parser if parser else self._parser - - def __eq__(self, other): - if self.atom != other.atom: - return False - else: - return True - - def __ne__(self, other): - return not self == other - - def __hash__(self): - return hash((self.atom, self.depatom, tuple(self.use))) - - def __repr__(self): - return "<%s %r>" % (self.__class__.__name__, self.atom) - - def environment(self, envvars): - """Returns predefined env vars DEPEND, SRC_URI, etc.""" - - # Try to use the Portage tree first, since emerge only uses the tree - # when calculating dependencies - try: - result = portage.db[portage.root]["porttree"].dbapi.aux_get(self.cpv, envvars) - except KeyError: - try: - result = portage.db[portage.root]["vartree"].dbapi.aux_get(self.cpv, envvars) - except KeyError: - return [] - return result - - def _get_depend(self, env_vars, raw=False): - raw_depend = ' '.join(self.environment(env_vars)) - if raw: - return raw_depend - try: - return self.parser(raw_depend) - except portage.exception.InvalidPackageName as err: - raise errors.GentoolkitInvalidCPV(err) - - def get_depend(self, **kwargs): - """Get the contents of DEPEND and parse it with self.parser.""" - return self._get_depend(('DEPEND', ), **kwargs) - - def get_pdepend(self, **kwargs): - """Get the contents of PDEPEND and parse it with self.parser.""" - return self._get_depend(('PDEPEND', ), **kwargs) - - def get_rdepend(self, **kwargs): - """Get the contents of RDEPEND and parse it with self.parser.""" - return self._get_depend(('RDEPEND', ), **kwargs) - - def get_all_depends(self, **kwargs): - """Get the contents of ?DEPEND and parse it with self.parser.""" - env_vars = ('DEPEND', 'PDEPEND', 'RDEPEND', 'BDEPEND') - return self._get_depend(env_vars, **kwargs) - - def graph_depends( - self, - max_depth=1, - printer_fn=None, - # The rest of these are only used internally: - depth=1, - seen=None, - depcache=None, - result=None - ): - """Graph direct dependencies for self. - - Optionally gather indirect dependencies. - - @type max_depth: int - @keyword max_depth: Maximum depth to recurse if. - <1 means no maximum depth - >0 means recurse only this depth; - @type printer_fn: callable - @keyword printer_fn: If None, no effect. If set, it will be applied to - each result. - @rtype: list - @return: [(depth, pkg), ...] - """ - if seen is None: - seen = set() - if depcache is None: - depcache = dict() - if result is None: - result = list() - - pkgdep = None - deps = self.get_all_depends() - for dep in deps: - if dep.atom in depcache: - continue - try: - pkgdep = depcache[dep.atom] - except KeyError: - pkgdep = Query(dep.atom).find_best() - depcache[dep.atom] = pkgdep - if not pkgdep: - continue - elif pkgdep.cpv in seen: - continue - if depth <= max_depth or max_depth == 0: - if printer_fn is not None: - printer_fn(depth, pkgdep, dep) - result.append((depth,pkgdep)) - - seen.add(pkgdep.cpv) - if depth < max_depth or max_depth == 0: - # result is passed in and added to directly - # so rdeps is disposable - rdeps = pkgdep.deps.graph_depends( # noqa - max_depth=max_depth, - printer_fn=printer_fn, - # The rest of these are only used internally: - depth=depth+1, - seen=seen, - depcache=depcache, - result=result - ) - return result - - def graph_reverse_depends( - self, - pkgset=None, - max_depth=-1, - only_direct=True, - printer_fn=None, - # The rest of these are only used internally: - depth=0, - depcache=None, - seen=None, - result=None - ): - """Graph direct reverse dependencies for self. - - Example usage: - >>> from gentoolkit.dependencies import Dependencies - >>> ffmpeg = Dependencies('media-video/ffmpeg-9999') - >>> # I only care about installed packages that depend on me: - ... from gentoolkit.helpers import get_installed_cpvs - >>> # I want to pass in a sorted list. We can pass strings or - ... # Package or Atom types, so I'll use Package to sort: - ... from gentoolkit.package import Package - >>> installed = sorted(get_installed_cpvs()) - >>> deptree = ffmpeg.graph_reverse_depends( - ... only_direct=False, # Include indirect revdeps - ... pkgset=installed) # from installed pkgset - >>> len(deptree) - 24 - - @type pkgset: iterable - @keyword pkgset: sorted pkg cpv strings or anything sublassing - L{gentoolkit.cpv.CPV} to use for calculate our revdep graph. - @type max_depth: int - @keyword max_depth: Maximum depth to recurse if only_direct=False. - -1 means no maximum depth; - 0 is the same as only_direct=True; - >0 means recurse only this many times; - @type only_direct: bool - @keyword only_direct: to recurse or not to recurse - @type printer_fn: callable - @keyword printer_fn: If None, no effect. If set, it will be applied to - each L{gentoolkit.atom.Atom} object as it is added to the results. - @rtype: list - @return: L{gentoolkit.dependencies.Dependencies} objects - """ - if not pkgset: - err = ("%s kwarg 'pkgset' must be set. " - "Can be list of cpv strings or any 'intersectable' object.") - raise errors.GentoolkitFatalError(err % (self.__class__.__name__,)) - - if depcache is None: - depcache = dict() - if seen is None: - seen = set() - if result is None: - result = list() - - if depth == 0: - pkgset = tuple(Dependencies(x) for x in pkgset) - - pkgdep = None - for pkgdep in pkgset: - raw_depends = pkgdep.get_all_depends(raw=True) - if self.cp not in raw_depends: - # fast path for obviously non-matching packages. This saves - # us the work of instantiating a whole Atom() for *every* - # dependency of *every* package in pkgset. - continue - try: - all_depends = depcache[pkgdep] - except KeyError: - all_depends = uniqify(pkgdep.get_all_depends()) - depcache[pkgdep] = all_depends - - dep_is_displayed = False - for dep in all_depends: - # TODO: Add ability to determine if dep is enabled by USE flag. - # Check portage.dep.use_reduce - if dep.intersects(self): - pkgdep.depth = depth - pkgdep.matching_dep = dep - if printer_fn is not None: - printer_fn(pkgdep, dep_is_displayed=dep_is_displayed) - result.append(pkgdep) - dep_is_displayed = True - - # if --indirect specified, call ourselves again with the dep - # Do not call if we have already called ourselves. - if ( - dep_is_displayed and not only_direct and - pkgdep.cpv not in seen and - (depth < max_depth or max_depth == -1) - ): - - seen.add(pkgdep.cpv) - result.append( - pkgdep.graph_reverse_depends( - pkgset=pkgset, - max_depth=max_depth, - only_direct=only_direct, - printer_fn=printer_fn, - depth=depth+1, - depcache=depcache, - seen=seen, - result=result - ) - ) - - if depth == 0: - return result - return pkgdep - - def _parser(self, deps, use_conditional=None, depth=0): - """?DEPEND file parser. - - @rtype: list - @return: L{gentoolkit.atom.Atom} objects - """ - result = [] - - if depth == 0: - deps = paren_reduce(deps) - for tok in deps: - if tok == '||': - continue - if tok[-1] == '?': - use_conditional = tok[:-1] - continue - if isinstance(tok, list): - sub_r = self._parser(tok, use_conditional, depth=depth+1) - result.extend(sub_r) - use_conditional = None - continue - # FIXME: This is a quick fix for bug #299260. - # A better fix is to not discard blockers in the parser, - # but to check for atom.blocker in whatever equery/depends - # (in this case) and ignore them there. - # TODO: Test to see how much a performance impact ignoring - # blockers here rather than checking for atom.blocker has. - if tok[0] == '!': - # We're not interested in blockers - continue - # skip it if it's empty - if tok and tok != '': - atom = Atom(tok) - if use_conditional is not None: - atom.use_conditional = use_conditional - result.append(atom) - else: - message = "dependencies.py: _parser() found an empty " +\ - "dep string token for: %s, deps= %s" - raise errors.GentoolkitInvalidAtom(message %(self.cpv, deps)) - - return result + """Access a package's dependencies and reverse dependencies. + + Example usage: + >>> from gentoolkit.dependencies import Dependencies + >>> portage = Dependencies('sys-apps/portage-9999') + >>> portage + + >>> # All methods return gentoolkit.atom.Atom instances + ... portage.get_depend() + ... # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE + [, + =dev-lang/python-2.7'>, ...] + + """ + + def __init__(self, query, parser=None): + Query.__init__(self, query) + self.use = [] + self.depatom = str() + + # Allow a custom parser function: + self.parser = parser if parser else self._parser + + def __eq__(self, other): + if self.atom != other.atom: + return False + else: + return True + + def __ne__(self, other): + return not self == other + + def __hash__(self): + return hash((self.atom, self.depatom, tuple(self.use))) + + def __repr__(self): + return "<%s %r>" % (self.__class__.__name__, self.atom) + + def environment(self, envvars): + """Returns predefined env vars DEPEND, SRC_URI, etc.""" + + # Try to use the Portage tree first, since emerge only uses the tree + # when calculating dependencies + try: + result = portage.db[portage.root]["porttree"].dbapi.aux_get( + self.cpv, envvars + ) + except KeyError: + try: + result = portage.db[portage.root]["vartree"].dbapi.aux_get( + self.cpv, envvars + ) + except KeyError: + return [] + return result + + def _get_depend(self, env_vars, raw=False): + raw_depend = " ".join(self.environment(env_vars)) + if raw: + return raw_depend + try: + return self.parser(raw_depend) + except portage.exception.InvalidPackageName as err: + raise errors.GentoolkitInvalidCPV(err) + + def get_depend(self, **kwargs): + """Get the contents of DEPEND and parse it with self.parser.""" + return self._get_depend(("DEPEND",), **kwargs) + + def get_pdepend(self, **kwargs): + """Get the contents of PDEPEND and parse it with self.parser.""" + return self._get_depend(("PDEPEND",), **kwargs) + + def get_rdepend(self, **kwargs): + """Get the contents of RDEPEND and parse it with self.parser.""" + return self._get_depend(("RDEPEND",), **kwargs) + + def get_all_depends(self, **kwargs): + """Get the contents of ?DEPEND and parse it with self.parser.""" + env_vars = ("DEPEND", "PDEPEND", "RDEPEND", "BDEPEND") + return self._get_depend(env_vars, **kwargs) + + def graph_depends( + self, + max_depth=1, + printer_fn=None, + # The rest of these are only used internally: + depth=1, + seen=None, + depcache=None, + result=None, + ): + """Graph direct dependencies for self. + + Optionally gather indirect dependencies. + + @type max_depth: int + @keyword max_depth: Maximum depth to recurse if. + <1 means no maximum depth + >0 means recurse only this depth; + @type printer_fn: callable + @keyword printer_fn: If None, no effect. If set, it will be applied to + each result. + @rtype: list + @return: [(depth, pkg), ...] + """ + if seen is None: + seen = set() + if depcache is None: + depcache = dict() + if result is None: + result = list() + + pkgdep = None + deps = self.get_all_depends() + for dep in deps: + if dep.atom in depcache: + continue + try: + pkgdep = depcache[dep.atom] + except KeyError: + pkgdep = Query(dep.atom).find_best() + depcache[dep.atom] = pkgdep + if not pkgdep: + continue + elif pkgdep.cpv in seen: + continue + if depth <= max_depth or max_depth == 0: + if printer_fn is not None: + printer_fn(depth, pkgdep, dep) + result.append((depth, pkgdep)) + + seen.add(pkgdep.cpv) + if depth < max_depth or max_depth == 0: + # result is passed in and added to directly + # so rdeps is disposable + rdeps = pkgdep.deps.graph_depends( # noqa + max_depth=max_depth, + printer_fn=printer_fn, + # The rest of these are only used internally: + depth=depth + 1, + seen=seen, + depcache=depcache, + result=result, + ) + return result + + def graph_reverse_depends( + self, + pkgset=None, + max_depth=-1, + only_direct=True, + printer_fn=None, + # The rest of these are only used internally: + depth=0, + depcache=None, + seen=None, + result=None, + ): + """Graph direct reverse dependencies for self. + + Example usage: + >>> from gentoolkit.dependencies import Dependencies + >>> ffmpeg = Dependencies('media-video/ffmpeg-9999') + >>> # I only care about installed packages that depend on me: + ... from gentoolkit.helpers import get_installed_cpvs + >>> # I want to pass in a sorted list. We can pass strings or + ... # Package or Atom types, so I'll use Package to sort: + ... from gentoolkit.package import Package + >>> installed = sorted(get_installed_cpvs()) + >>> deptree = ffmpeg.graph_reverse_depends( + ... only_direct=False, # Include indirect revdeps + ... pkgset=installed) # from installed pkgset + >>> len(deptree) + 24 + + @type pkgset: iterable + @keyword pkgset: sorted pkg cpv strings or anything sublassing + L{gentoolkit.cpv.CPV} to use for calculate our revdep graph. + @type max_depth: int + @keyword max_depth: Maximum depth to recurse if only_direct=False. + -1 means no maximum depth; + 0 is the same as only_direct=True; + >0 means recurse only this many times; + @type only_direct: bool + @keyword only_direct: to recurse or not to recurse + @type printer_fn: callable + @keyword printer_fn: If None, no effect. If set, it will be applied to + each L{gentoolkit.atom.Atom} object as it is added to the results. + @rtype: list + @return: L{gentoolkit.dependencies.Dependencies} objects + """ + if not pkgset: + err = ( + "%s kwarg 'pkgset' must be set. " + "Can be list of cpv strings or any 'intersectable' object." + ) + raise errors.GentoolkitFatalError(err % (self.__class__.__name__,)) + + if depcache is None: + depcache = dict() + if seen is None: + seen = set() + if result is None: + result = list() + + if depth == 0: + pkgset = tuple(Dependencies(x) for x in pkgset) + + pkgdep = None + for pkgdep in pkgset: + raw_depends = pkgdep.get_all_depends(raw=True) + if self.cp not in raw_depends: + # fast path for obviously non-matching packages. This saves + # us the work of instantiating a whole Atom() for *every* + # dependency of *every* package in pkgset. + continue + try: + all_depends = depcache[pkgdep] + except KeyError: + all_depends = uniqify(pkgdep.get_all_depends()) + depcache[pkgdep] = all_depends + + dep_is_displayed = False + for dep in all_depends: + # TODO: Add ability to determine if dep is enabled by USE flag. + # Check portage.dep.use_reduce + if dep.intersects(self): + pkgdep.depth = depth + pkgdep.matching_dep = dep + if printer_fn is not None: + printer_fn(pkgdep, dep_is_displayed=dep_is_displayed) + result.append(pkgdep) + dep_is_displayed = True + + # if --indirect specified, call ourselves again with the dep + # Do not call if we have already called ourselves. + if ( + dep_is_displayed + and not only_direct + and pkgdep.cpv not in seen + and (depth < max_depth or max_depth == -1) + ): + + seen.add(pkgdep.cpv) + result.append( + pkgdep.graph_reverse_depends( + pkgset=pkgset, + max_depth=max_depth, + only_direct=only_direct, + printer_fn=printer_fn, + depth=depth + 1, + depcache=depcache, + seen=seen, + result=result, + ) + ) + + if depth == 0: + return result + return pkgdep + + def _parser(self, deps, use_conditional=None, depth=0): + """?DEPEND file parser. + + @rtype: list + @return: L{gentoolkit.atom.Atom} objects + """ + result = [] + + if depth == 0: + deps = paren_reduce(deps) + for tok in deps: + if tok == "||": + continue + if tok[-1] == "?": + use_conditional = tok[:-1] + continue + if isinstance(tok, list): + sub_r = self._parser(tok, use_conditional, depth=depth + 1) + result.extend(sub_r) + use_conditional = None + continue + # FIXME: This is a quick fix for bug #299260. + # A better fix is to not discard blockers in the parser, + # but to check for atom.blocker in whatever equery/depends + # (in this case) and ignore them there. + # TODO: Test to see how much a performance impact ignoring + # blockers here rather than checking for atom.blocker has. + if tok[0] == "!": + # We're not interested in blockers + continue + # skip it if it's empty + if tok and tok != "": + atom = Atom(tok) + if use_conditional is not None: + atom.use_conditional = use_conditional + result.append(atom) + else: + message = ( + "dependencies.py: _parser() found an empty " + + "dep string token for: %s, deps= %s" + ) + raise errors.GentoolkitInvalidAtom(message % (self.cpv, deps)) + + return result + # vim: set ts=4 sw=4 tw=0: diff --git a/pym/gentoolkit/eclean/clean.py b/pym/gentoolkit/eclean/clean.py index 3f6fe45..a6358a4 100644 --- a/pym/gentoolkit/eclean/clean.py +++ b/pym/gentoolkit/eclean/clean.py @@ -12,142 +12,138 @@ from gentoolkit.eclean.pkgindex import PkgIndex class CleanUp: - """Performs all cleaning actions to distfiles or package directories. - - @param controller: a progress output/user interaction controller function - which returns a Boolean to control file deletion - or bypassing/ignoring - """ - - def __init__(self, controller): - self.controller = controller - - def clean_dist(self, clean_dict): - """Calculate size of each entry for display, prompt user if needed, - delete files if approved and return the total size of files that - have been deleted. - - @param clean_dict: dictionary of {'display name':[list of files]} - - @rtype: int - @return: total size that was cleaned - """ - file_type = 'file' - clean_size = 0 - # clean all entries one by one; sorting helps reading - for key in sorted(clean_dict): - clean_size += self._clean_files(clean_dict[key], key, file_type) - # return total size of deleted or to delete files - return clean_size - - def clean_pkgs(self, clean_dict, pkgdir): - """Calculate size of each entry for display, prompt user if needed, - delete files if approved and return the total size of files that - have been deleted. - - @param clean_dict: dictionary of {'display name':[list of files]} - @param metadata: package index of type portage.getbinpkg.PackageIndex() - @param pkgdir: path to the package directory to be cleaned - - @rtype: int - @return: total size that was cleaned - """ - file_type = 'binary package' - clean_size = 0 - # clean all entries one by one; sorting helps reading - for key in sorted(clean_dict): - clean_size += self._clean_files(clean_dict[key], key, file_type) - - # run 'emaint --fix' here - if clean_size: - index_control = PkgIndex(self.controller) - # emaint is not yet importable so call it - # print a blank line here for separation - print() - clean_size += index_control.call_emaint() - # return total size of deleted or to delete files - return clean_size - - - def pretend_clean(self, clean_dict): - """Shortcut function that calculates total space savings - for the files in clean_dict. - - @param clean_dict: dictionary of {'display name':[list of files]} - @rtype: integer - @return: total size that would be cleaned - """ - file_type = 'file' - clean_size = 0 - # tally all entries one by one; sorting helps reading - for key in sorted(clean_dict): - key_size = self._get_size(clean_dict[key]) - self.controller(key_size, key, clean_dict[key], file_type) - clean_size += key_size - return clean_size - - def _get_size(self, key): - """Determine the total size for an entry (may be several files).""" - key_size = 0 - for file_ in key: - #print file_ - # get total size for an entry (may be several files, and - # links don't count - # ...get its statinfo - try: - statinfo = os.stat(file_) - if statinfo.st_nlink == 1: - key_size += statinfo.st_size - except EnvironmentError as er: - print( pp.error( - "Could not get stat info for:" + file_), file=sys.stderr) - print( pp.error("Error: %s" %str(er)), file=sys.stderr) - return key_size - - def _clean_files(self, files, key, file_type): - """File removal function.""" - clean_size = 0 - for file_ in files: - #print file_, type(file_) - # ...get its statinfo - try: - statinfo = os.stat(file_) - except EnvironmentError as er: - if not os.path.exists(os.readlink(file_)): - try: - os.remove(file_) - print( pp.error( - "Removed broken symbolic link " + file_), file=sys.stderr) - break - except EnvironmentError as er: - print( pp.error( - "Error deleting broken symbolic link " + file_), file=sys.stderr) - print( pp.error("Error: %s" %str(er)), file=sys.stderr) - break - else: - print( pp.error( - "Could not get stat info for:" + file_), file=sys.stderr) - print( pp.error( - "Error: %s" %str(er)), file=sys.stderr) - if self.controller(statinfo.st_size, key, file_, file_type): - # ... try to delete it. - try: - os.unlink(file_) - # only count size if successfully deleted and not a link - if statinfo.st_nlink == 1: - clean_size += statinfo.st_size - try: - os.rmdir(os.path.dirname(file_)) - except OSError: - pass - except EnvironmentError as er: - print( pp.error("Could not delete "+file_), file=sys.stderr) - print( pp.error("Error: %s" %str(er)), file=sys.stderr) - return clean_size - - - - - - - + """Performs all cleaning actions to distfiles or package directories. + + @param controller: a progress output/user interaction controller function + which returns a Boolean to control file deletion + or bypassing/ignoring + """ + + def __init__(self, controller): + self.controller = controller + + def clean_dist(self, clean_dict): + """Calculate size of each entry for display, prompt user if needed, + delete files if approved and return the total size of files that + have been deleted. + + @param clean_dict: dictionary of {'display name':[list of files]} + + @rtype: int + @return: total size that was cleaned + """ + file_type = "file" + clean_size = 0 + # clean all entries one by one; sorting helps reading + for key in sorted(clean_dict): + clean_size += self._clean_files(clean_dict[key], key, file_type) + # return total size of deleted or to delete files + return clean_size + + def clean_pkgs(self, clean_dict, pkgdir): + """Calculate size of each entry for display, prompt user if needed, + delete files if approved and return the total size of files that + have been deleted. + + @param clean_dict: dictionary of {'display name':[list of files]} + @param metadata: package index of type portage.getbinpkg.PackageIndex() + @param pkgdir: path to the package directory to be cleaned + + @rtype: int + @return: total size that was cleaned + """ + file_type = "binary package" + clean_size = 0 + # clean all entries one by one; sorting helps reading + for key in sorted(clean_dict): + clean_size += self._clean_files(clean_dict[key], key, file_type) + + # run 'emaint --fix' here + if clean_size: + index_control = PkgIndex(self.controller) + # emaint is not yet importable so call it + # print a blank line here for separation + print() + clean_size += index_control.call_emaint() + # return total size of deleted or to delete files + return clean_size + + def pretend_clean(self, clean_dict): + """Shortcut function that calculates total space savings + for the files in clean_dict. + + @param clean_dict: dictionary of {'display name':[list of files]} + @rtype: integer + @return: total size that would be cleaned + """ + file_type = "file" + clean_size = 0 + # tally all entries one by one; sorting helps reading + for key in sorted(clean_dict): + key_size = self._get_size(clean_dict[key]) + self.controller(key_size, key, clean_dict[key], file_type) + clean_size += key_size + return clean_size + + def _get_size(self, key): + """Determine the total size for an entry (may be several files).""" + key_size = 0 + for file_ in key: + # print file_ + # get total size for an entry (may be several files, and + # links don't count + # ...get its statinfo + try: + statinfo = os.stat(file_) + if statinfo.st_nlink == 1: + key_size += statinfo.st_size + except EnvironmentError as er: + print(pp.error("Could not get stat info for:" + file_), file=sys.stderr) + print(pp.error("Error: %s" % str(er)), file=sys.stderr) + return key_size + + def _clean_files(self, files, key, file_type): + """File removal function.""" + clean_size = 0 + for file_ in files: + # print file_, type(file_) + # ...get its statinfo + try: + statinfo = os.stat(file_) + except EnvironmentError as er: + if not os.path.exists(os.readlink(file_)): + try: + os.remove(file_) + print( + pp.error("Removed broken symbolic link " + file_), + file=sys.stderr, + ) + break + except EnvironmentError as er: + print( + pp.error("Error deleting broken symbolic link " + file_), + file=sys.stderr, + ) + print(pp.error("Error: %s" % str(er)), file=sys.stderr) + break + else: + print( + pp.error("Could not get stat info for:" + file_), + file=sys.stderr, + ) + print(pp.error("Error: %s" % str(er)), file=sys.stderr) + if self.controller(statinfo.st_size, key, file_, file_type): + # ... try to delete it. + try: + os.unlink(file_) + # only count size if successfully deleted and not a link + if statinfo.st_nlink == 1: + clean_size += statinfo.st_size + try: + os.rmdir(os.path.dirname(file_)) + except OSError: + pass + except EnvironmentError as er: + print(pp.error("Could not delete " + file_), file=sys.stderr) + print(pp.error("Error: %s" % str(er)), file=sys.stderr) + return clean_size diff --git a/pym/gentoolkit/eclean/cli.py b/pym/gentoolkit/eclean/cli.py index e31fde9..bb6deeb 100644 --- a/pym/gentoolkit/eclean/cli.py +++ b/pym/gentoolkit/eclean/cli.py @@ -4,10 +4,11 @@ # Distributed under the terms of the GNU General Public License v2 -__author__ = "Thomas de Grenier de Latour (tgl), " + \ - "modular re-write by: Brian Dolbec (dol-sen)" -__email__ = "degrenier@easyconnect.fr, " + \ - "brian.dolbec@gmail.com" +__author__ = ( + "Thomas de Grenier de Latour (tgl), " + + "modular re-write by: Brian Dolbec (dol-sen)" +) +__email__ = "degrenier@easyconnect.fr, " + "brian.dolbec@gmail.com" __version__ = "git" __productname__ = "eclean" __description__ = "A cleaning tool for Gentoo distfiles and binaries." @@ -23,494 +24,654 @@ import portage from portage.output import white, yellow, turquoise, green import gentoolkit.pprinter as pp -from gentoolkit.eclean.search import (DistfilesSearch, - findPackages, port_settings, pkgdir) -from gentoolkit.eclean.exclude import (parseExcludeFile, - ParseExcludeFileException) +from gentoolkit.eclean.search import ( + DistfilesSearch, + findPackages, + port_settings, + pkgdir, +) +from gentoolkit.eclean.exclude import parseExcludeFile, ParseExcludeFileException from gentoolkit.eclean.clean import CleanUp from gentoolkit.eclean.output import OutputControl -#from gentoolkit.eclean.dbapi import Dbapi + +# from gentoolkit.eclean.dbapi import Dbapi from gentoolkit.eprefix import EPREFIX + def printVersion(): - """Output the version info.""" - print( "%s (%s) - %s" \ - % (__productname__, __version__, __description__)) - print() - print("Author: %s <%s>" % (__author__,__email__)) - print("Copyright 2003-2009 Gentoo Foundation") - print("Distributed under the terms of the GNU General Public License v2") + """Output the version info.""" + print("%s (%s) - %s" % (__productname__, __version__, __description__)) + print() + print("Author: %s <%s>" % (__author__, __email__)) + print("Copyright 2003-2009 Gentoo Foundation") + print("Distributed under the terms of the GNU General Public License v2") def printUsage(_error=None, help=None): - """Print help message. May also print partial help to stderr if an - error from {'options','actions'} is specified.""" - - out = sys.stdout - if _error: - out = sys.stderr - if not _error in ('actions', 'global-options', \ - 'packages-options', 'distfiles-options', \ - 'merged-packages-options', 'merged-distfiles-options', \ - 'time', 'size'): - _error = None - if not _error and not help: help = 'all' - if _error == 'time': - print( pp.error("Wrong time specification"), file=out) - print( "Time specification should be an integer followed by a"+ - " single letter unit.", file=out) - print( "Available units are: y (years), m (months), w (weeks), "+ - "d (days) and h (hours).", file=out) - print( "For instance: \"1y\" is \"one year\", \"2w\" is \"two"+ - " weeks\", etc. ", file=out) - return - if _error == 'size': - print( pp.error("Wrong size specification"), file=out) - print( "Size specification should be an integer followed by a"+ - " single letter unit.", file=out) - print( "Available units are: G, M, K and B.", file=out) - print("For instance: \"10M\" is \"ten megabytes\", \"200K\" "+ - "is \"two hundreds kilobytes\", etc.", file=out) - return - if _error in ('global-options', 'packages-options', 'distfiles-options', \ - 'merged-packages-options', 'merged-distfiles-options',): - print( pp.error("Wrong option on command line."), file=out) - print( file=out) - elif _error == 'actions': - print( pp.error("Wrong or missing action name on command line."), file=out) - print( file=out) - print( white("Usage:"), file=out) - if _error in ('actions','global-options', 'packages-options', \ - 'distfiles-options') or help == 'all': - print( " "+turquoise(__productname__), - yellow("[global-option] ..."), - green(""), - yellow("[action-option] ..."), file=out) - if _error == 'merged-distfiles-options' or help in ('all','distfiles'): - print( " "+turquoise(__productname__+'-dist'), - yellow("[global-option, distfiles-option] ..."), file=out) - if _error == 'merged-packages-options' or help in ('all','packages'): - print( " "+turquoise(__productname__+'-pkg'), - yellow("[global-option, packages-option] ..."), file=out) - if _error in ('global-options', 'actions'): - print( " "+turquoise(__productname__), - yellow("[--help, --version]"), file=out) - if help == 'all': - print( " "+turquoise(__productname__+"(-dist,-pkg)"), - yellow("[--help, --version]"), file=out) - if _error == 'merged-packages-options' or help == 'packages': - print( " "+turquoise(__productname__+'-pkg'), - yellow("[--help, --version]"), file=out) - if _error == 'merged-distfiles-options' or help == 'distfiles': - print( " "+turquoise(__productname__+'-dist'), - yellow("[--help, --version]"), file=out) - print(file=out) - if _error in ('global-options', 'merged-packages-options', \ - 'merged-distfiles-options') or help: - print( "Available global", yellow("options")+":", file=out) - print( yellow(" -C, --nocolor")+ - " - turn off colors on output", file=out) - print( yellow(" -d, --deep")+ - " - only keep the minimum for a reinstallation", file=out) - print( yellow(" -e, --exclude-file=")+ - " - path to the exclusion file", file=out) - print( yellow(" -i, --interactive")+ - " - ask confirmation before deletions", file=out) - print( yellow(" -n, --package-names")+ - " - protect all versions (when --deep)", file=out) - print( yellow(" -p, --pretend")+ - " - only display what would be cleaned", file=out) - print( yellow(" -q, --quiet")+ - " - be as quiet as possible", file=out) - print( yellow(" -t, --time-limit=