Initial commit v1.9 v1.9.36
authorMike Pagano <mpagano@gentoo.org>
Sun, 28 Mar 2010 18:44:56 +0000 (14:44 -0400)
committerMike Pagano <mpagano@gentoo.org>
Sun, 28 Mar 2010 18:44:56 +0000 (14:44 -0400)
portpeek [new file with mode: 0755]
portpeek.1 [new file with mode: 0644]

diff --git a/portpeek b/portpeek
new file mode 100755 (executable)
index 0000000..78abe3a
--- /dev/null
+++ b/portpeek
@@ -0,0 +1,897 @@
+#!/usr/bin/python
+#
+# Copyright 2006 Mike Pagano
+# Distributed under the terms of the GNU General Public License v2
+#
+# $Header$
+# Author: Mike Pagano <mpagano@gentoo.org>
+#
+# Portions written ripped from 
+# - equery, by Karl Trygve Kalleberg <karltk@gentoo.org>
+# - gentoolkit.py, by Karl Trygve Kalleberg <karltk@gentoo.org>
+# - portage.py
+# - emerge
+#
+
+__author__ = "Michael Pagano"
+__email__ = "mpagano@gentoo.org"
+__version__ = "1.9.36"
+__productname__ = "portpeek"
+__description__ = "Displays user unmasked ebuilds and installable options from the portage tree"
+
+import sys, os, portage.output, string, fileinput
+
+
+import gentoolkit, gentoolkit.helpers,gentoolkit.package,gentoolkit.versionmatch
+
+from gentoolkit.versionmatch import VersionMatch
+from portage.const import USER_CONFIG_PATH
+from portage.versions import catpkgsplit,pkgcmp,pkgsplit
+from gentoolkit.helpers import compare_package_strings
+from portage import best
+import portage.exception
+from portage.exception import InvalidAtom
+from gentoolkit.cpv import CPV
+
+porttree = portage.db[portage.root]["porttree"]
+settings = portage.config(clone=portage.settings)
+
+show_changes_only_flag = False
+checking_package_unmask = False
+checking_package_mask = False
+print_overlay_flag = False
+info = 0
+debug = 1
+logLevel = info
+show_removable_only_flag = False
+stable_list = []
+unmask_list = []
+tilde = 0
+processing_package_use = False
+
+try:
+    PORTAGE_CONFIGROOT
+except NameError:
+    PORTAGE_CONFIGROOT="/"
+
+USER_CONFIG_PATH=PORTAGE_CONFIGROOT + USER_CONFIG_PATH
+
+#parameters
+options = [
+"--keyword",
+"--unmask",
+"--mask",
+"--all",
+"--changes-only",
+"--version",
+"--version",
+"--help",
+"--removable-only",
+"--debug",
+"--fix", 
+"--tilde-check",
+"--no-color",
+"--package-use"
+]
+
+mappings = {
+"k":"--keyword",
+"u":"--unmask",
+"m":"--mask",
+"a":"--all",
+"c":"--changes-only",
+"V":"--version",
+"v":"--version",
+"h":"--help",
+"r":"--removable-only",
+"d":"--debug",
+"f":"--fix", 
+"t":"--tilde-check",
+"n":"--no-color",
+"s":"--package-use"
+}
+
+cmdline = []
+overlays = [settings["PORTDIR_OVERLAY"]]
+
+if len(overlays) > 0:
+    overlay_list = overlays[0].split(" ")
+
+def print_usage():
+    # Print full usage information for this tool to the console.
+    print "\nUsage: " + portage.output.turquoise(__productname__) +  portage.output.yellow(" command ")
+    print "       " + portage.output.turquoise(__productname__) + portage.output.green(" [ options ]") +  portage.output.yellow(" command ")
+    print "       " + portage.output.turquoise(__productname__) + portage.output.green(" [-c]") + portage.output.yellow(" [akmu]")
+    print "       " + portage.output.turquoise(__productname__) + portage.output.green(" [-r]") + portage.output.yellow(" [akmu]")
+    print "       " + portage.output.turquoise(__productname__) + portage.output.green(" [-f]") + portage.output.yellow(" [akmu]")
+    print "       " + portage.output.turquoise(__productname__) + portage.output.green(" [-F]") + portage.output.yellow(" [akmu]")
+    print portage.output.yellow(" command ") + " can be "
+    print portage.output.yellow(" -a, --all") + "       - show all matches"
+    print portage.output.yellow(" -k, --keyword") + "       - show matches from package.keyword only"
+    print portage.output.yellow(" -m, --mask") + "      - show matches from package.mask only"
+    print portage.output.yellow(" -u, --unmask") + "        - show matched from package.unmask only"
+    
+    print portage.output.yellow(" -f, --fix") + "       - will remove the stabled packages without asking for confirmation"
+    print portage.output.yellow(" -h, --help") + "      - display this message"
+    print portage.output.yellow(" -d, --debug") + "         - display more verbose output for debugging"
+    print portage.output.yellow(" -V, --version") + "       - display version info"
+    print portage.output.green("options") + " are "
+    print portage.output.green(" -c, --changes-only") + \
+        "   - show all matches that have upgrade option, use with " + \
+        "<" + portage.output.yellow(" k ") + "|" + portage.output.yellow(" u ") + \
+        "|" + portage.output.yellow(" m ") + "|" + \
+        portage.output.yellow(" a ") + ">"
+    print portage.output.green(" -n, --no-color") + \
+        "   - suppress color output"
+    print portage.output.green(" -r, --removable-only") + \
+        "   - show all matches that can be removed from package files, use with " + \
+        "<" + portage.output.yellow(" k ") + "|" + portage.output.yellow(" u ") + \
+        "|" + portage.output.yellow(" m ") + "|" + \
+        portage.output.yellow(" a ") + ">\n"
+
+def get_keywords(package, var):
+    mytree = porttree
+    filtered_keywords = ""
+    try:
+        keywords = package.environment("KEYWORDS").split()
+    except KeyError, error:
+        print "!!! Portpeek caught Exception:", error
+        print "!!! This package/version seems to be no longer available, " + \
+        "please check and update/unmerge it" 
+        return "Not Available/Deprecated"
+    
+    #filtered_keywords = filter_keywords(keywords[0])
+    filtered_keywords = filter_keywords(keywords)
+    return filtered_keywords
+
+
+
+# this is the main function for portpeek
+# TODO comment this code!
+def parse_line(line, filename):     
+    global info,debug
+    pkgs = None
+    ebuild_output = ""
+    check_pkg = ""
+    not_installed_pkgs = 0
+    pkg_length = 0
+    atom_check="<>="
+
+       # determine if we are also check ~ prefixed code
+    if  (tilde == 1): 
+        atom_check="<>=~"
+
+    diffs_found = False
+    display_done = False
+
+    fields = line.replace("\t", " ").split(" ")
+    if len(fields) > 0:
+        #if the line does not start with an atom such as (= or >), do not validate 
+        #status as this tool is for check specific versions and not entire packages 
+        # a ~cpv should be handled like an =cpv if requested bythe parameter -t
+        check_pkg = fields[0] # this should be one of <>=~
+        if check_pkg[0] not in atom_check:
+            return
+        if (tilde == 1):
+            if check_pkg[0] in "~":
+                check_tilde_masked_pkg(check_pkg, filename)
+                return
+
+               # determine if the package exists
+        try:
+            package_exists = portage.portdb.xmatch("match-all", fields[0])
+        except InvalidAtom:
+            package_exists = False
+
+        if package_exists:
+                       # return a Package List based on the cpv
+            pkgs = gentoolkit.helpers.find_packages(fields[0], True)
+            if (pkgs != None):
+                pkg_length = len(pkgs)
+                not_installed_pkgs = 0
+                display_done = False
+
+                               # go through each package version for a specific version found above
+                for current_package in pkgs:
+                    if not current_package.is_installed():
+
+                                               # we have found a package that is in our file, but not installed
+                        not_installed_pkgs = not_installed_pkgs + 1
+
+                        # check to see if specific version of pkg is not installed 
+                        # and display if true
+                        check_pkg = fields[0]
+                        if check_pkg[0] in atom_check:
+                            check_pkg = check_pkg[1:]
+
+                        if (check_pkg == str(current_package.cpv)):
+                            if (not checking_package_mask):
+                                                               # package is not instaleld
+                                print_output(info,portage.output.green("\n" + str(current_package.cpv) + ": ") + portage.output.yellow("Not Installed") , current_package, filename)
+                                stable_list.append(str(current_package.cpv))
+                                unmask_list.append(str(current_package.cpv))
+                            else:
+                                                               # package is masked, and not installed, this is normal use of package.mask
+                                print_output(info,portage.output.green("" + str(current_package.cpv) + ": ") + portage.output.yellow("Package Masked"),current_package, filename)
+                            display_done = True
+                        continue
+
+                                       # package is installed
+                                       # retrieve the keywords for a file
+                    keywords = "%s" % (get_keywords(current_package,"KEYWORDS").split())
+                    if (keywords.find("Available/Deprecated") >= 0):
+                        continue
+
+                                       #retrieve the mask status of a specific package
+                    pkgmask = _get_mask_status(current_package, False)
+
+                    #determine if installed package is unmasked, if so, display keywords as green
+                    stable = check_for_stable_release(current_package)
+
+                    # do not display if keywords don't exist
+                    if keywords == "[]":
+                        continue
+
+                    if stable:
+                        ebuild_output = portage.output.green("Installed: ") + \
+                            portage.output.turquoise(str(current_package.cpv)) + \
+                            portage.output.green("  Keywords " + keywords)
+                        if "package.unmask" in filename:
+                            unmask_list.append(str(current_package.cpv))
+                        if "package.keywords" in filename:
+                            stable_list.append(str(current_package.cpv))
+                    else:
+                        if (not show_removable_only_flag):
+                            ebuild_output = portage.output.green("Installed: ") + \
+                                portage.output.turquoise(str(current_package.cpv)) + \
+                                portage.output.yellow("  Keywords " + keywords) 
+                        else:
+                            ebuild_output = portage.output.yellow(str(current_package.cpv))
+
+                                       # check package.unmask
+                    if (checking_package_unmask):
+                        if (not is_pkg_package_masked(str(current_package.cpv))):
+
+                                                       # package is in package.unmask unnecessarily
+                            ebuild_output = ebuild_output + ": " + portage.output.yellow("Not package masked")
+                            if "package.unmask" in filename:
+                                unmask_list.append(str(current_package.cpv))
+                                print_output (info, "" +  ebuild_output,None, filename)
+                                continue
+
+                                       # print once
+                    ebuild_search_key_printed = False
+                    if stable:
+                        diffs_found = False
+                        ebuild_search_key_printed = True
+                        print_output(info,"\n" + ebuild_output,current_package, filename)
+                    elif not show_changes_only_flag and not show_removable_only_flag:
+                        diffs_found = False
+                        ebuild_search_key_printed = True
+                        print_output(info,"\n" + ebuild_output,current_package)
+
+                                       # go through all versions of a package
+                    all_pkgs = sort_package_list(gentoolkit.helpers.find_packages((current_package.category + \
+                        "/" + current_package.name), True))
+                    for a_package in all_pkgs:
+                        if not a_package.is_installed():
+                                                       # a_package is not installed
+                            pkgmask = _get_mask_status(a_package, False)
+                                                       # print status line of package we are now checking
+                            print_output(debug,portage.output.blue("Checking package: " + str(a_package.cpv) +".pkgmask is " + str(pkgmask)))
+                                                       # if package versions are different
+                            if (VersionMatch(CPV(current_package.cpv)).match(CPV(a_package.cpv))):
+                                diffs_found = True
+                                keylist = a_package.environment("KEYWORDS")
+                                keywords = "%s" % (filter_keywords(keylist)).split()
+                                #if a_package is masked
+                                if pkgmask > 0 and not show_removable_only_flag:
+                                    if show_changes_only_flag and not ebuild_search_key_printed:
+                                        print_output (info, "\n" +  ebuild_output, current_package)
+                                        ebuild_search_key_printed = True
+                                        check_for_stable_release(current_package)
+                                    if (pkgmask >= 3):
+                                        print_output (info,portage.output.red("Available: " + str(a_package.cpv) + " [M] Keywords: " + keywords),a_package)
+                                    else:
+                                        print_output (info,portage.output.brown("Available: " + str(a_package.cpv) + " Keywords: " + keywords),a_package)
+                                else:
+                                    if show_changes_only_flag and not ebuild_search_key_printed:
+                                        print_output (info,"\n" + ebuild_output,current_package)
+                                        ebuild_search_key_printed = True
+                                        print_output(info,portage.output.green("Available: " + str(a_package.cpv) + " Keywords: " + keywords),a_package)
+                # display if pkg/cat is not installed (missing version)
+                if not_installed_pkgs ==  pkg_length:
+                    if not display_done:
+                        if (not checking_package_mask):
+                            print_output(info,portage.output.green("\n" + fields[0] + ": ") + portage.output.yellow("Not Installed"),current_package)
+                            stable_list.append(str(current_package.cpv))
+                            unmask_list.append(str(current_package.cpv))
+                        else:
+                            print_output (info,portage.output.green("\n" + str(current_package.cpv) + ": ") + portage.output.yellow("Package Masked"),current_package)
+        else:
+            diffs_found = True
+            print portage.output.red ("\nPackage: " + fields[0] + " not found. Please check " + filename + " to validate entry")
+            stable_list.append(fields[0])
+            unmask_list.append(fields[0])
+            show_all_versions(fields[0])
+    current_package = ""
+
+    return diffs_found
+
+# adding support for etc/portage/package.keywords/<whatever>/package.keywords
+def get_recursive_info(filename):
+
+    # determine if filename is a directory
+    if os.path.isdir(filename):
+        # get listing of directory
+        filenames = os.listdir(filename)
+        for file_name in filenames:
+            get_recursive_info(filename+os.path.sep+file_name)
+    else:   
+        get_info(filename)
+
+def get_info(filename):
+
+    diffs_found = False
+    no_file = False
+    filedescriptor = None
+
+    try:
+        filedescriptor = open(filename)
+        for line in filedescriptor.readlines():
+            line = line.strip()
+            if len(line) <= 0:
+                continue
+            elif line.find("#") >= 0:
+                # found '#' remove comment
+                line = line[0:line.find("#")]
+                line = line.strip()
+                if len(line) <= 0:
+                    continue
+            if (processing_package_use == False):
+                diffs_found = parse_line(line, filename)
+            else:
+                # process package.use
+                diffs_found = parse_package_use(line,filename)
+    except IOError:
+        print portage.output.red("Could not find file " + filename)
+        no_file = True
+
+    if not diffs_found and no_file:
+        print portage.output.brown("No ebuild options found.")
+
+    # close file
+    if (filedescriptor != None):
+        filedescriptor.close()
+
+# parse the package.use file and look for packages
+# not installed
+def parse_package_use(line, filename):
+    global info,debug
+    pkgs = None
+    check_pkg = ""
+    pkg_length = 0
+    atom_check="<>="
+    any_version = False
+    has_atom = True
+
+    diffs_found = False
+    package_installed = False
+    fields = line.replace("\t", " ").split(" ")
+
+    if len(fields) > 0:
+       check_pkg = fields[0] # this could be one of <>=
+        if check_pkg[0] not in atom_check:
+            has_atom = False
+        else:
+            check_pkg = check_pkg[1:]
+
+        # look for any version of check_pkg installed as there is 
+        # no version specified in package.use
+        package_exists = portage.portdb.xmatch("match-all", check_pkg)
+        if package_exists:
+            # get all package versions
+
+            pkgs = gentoolkit.helpers.find_packages(check_pkg, True)
+            if (pkgs != None):
+                pkg_length = len(pkgs)
+
+                # go through each package version for a specific version found above
+                if (has_atom == False):
+                    for current_package in pkgs:
+                        if current_package.is_installed():
+                            package_installed = True
+                else: 
+                    # go through each package version for a specific version found above
+                    for current_package in pkgs:
+                        if (str(current_package.cpv) == check_pkg):
+                            if not current_package.is_installed():
+                                print_output(info,portage.output.green("\n" + check_pkg + ": ") + portage.output.yellow("Not Installed"),current_package)
+                                stable_list.append(check_pkg)
+                                unmask_list.append(check_pkg)
+                                return True
+                            else:
+                                return False
+        else:
+            print portage.output.red ("\nPackage: " + fields[0] + " not found. Please check " + filename + " to validate entry")
+            stable_list.append(check_pkg)
+            unmask_list.append(check_pkg)
+            return True
+        if (package_installed == False):
+            # package does not exists
+            print_output(info,portage.output.green("\n" + check_pkg + ": ") + portage.output.yellow("Not Installed"),current_package)
+            stable_list.append(check_pkg)
+            unmask_list.append(check_pkg)
+            return True
+
+    return False
+
+
+# parts blatantly stolen from equery
+# if pure is true, then get "true" mask status that is
+# not affected by entries in /etc/portage/package.*
+def _get_mask_status(pkg, pure):
+    pkgmask = 0
+
+    if (pkg == None):
+        return 0
+
+    if pkg.is_masked():
+        pkgmask = pkgmask + 3
+
+    if pure:
+        try:
+            keywords = portage.portdb.aux_get(str(pkg.cpv), ["KEYWORDS"])
+            keywords = keywords[0].split()
+        except KeyError:
+            # cpv does not exist
+            return 0
+    else:
+        keywords = pkg.environment("KEYWORDS").split()
+
+    # first check for stable arch, stop there if it is found
+    if settings["ARCH"] in keywords:
+        return 0
+
+    if "~" + settings["ARCH"] in keywords:
+        pkgmask = pkgmask + 1
+    elif "-*" in keywords or "-" + settings["ARCH"] in keywords:
+        pkgmask = pkgmask + 2
+    
+    return pkgmask
+
+def is_pkg_package_masked(cpv):
+
+    mysplit = catpkgsplit(cpv)
+    if not mysplit:
+        raise ValueError("invalid CPV: %s" % cpv)
+    if not portage.portdb.cpv_exists(cpv):
+        raise KeyError("CPV %s does not exist" % cpv)
+    mycp = mysplit[0] + "/" + mysplit[1]
+
+    if settings.pmaskdict.has_key(mycp):
+        for package in settings.pmaskdict[mycp]:
+            if cpv in portage.portdb.xmatch("match-all", package):
+                return True
+                
+    return False    
+
+# filter out keywords for archs other than the current one
+def filter_keywords(keywords):
+    filtered_keywords = ""
+
+    #for key in key_list:
+    for key in keywords:
+        key = string.replace(key, "[", "")
+        key = string.replace(key, "]", "")
+        key = string.replace(key, ",", "")
+        arch=settings["ARCH"]
+       if key.rfind(arch) != -1:
+            if len(filtered_keywords) != 0:
+                filtered_keywords = filtered_keywords + " "
+            filtered_keywords = filtered_keywords + key
+        elif "-*" in key:
+            if len(filtered_keywords) != 0:
+                filtered_keywords = filtered_keywords + " "
+            filtered_keywords = filtered_keywords + key
+
+
+    return filtered_keywords
+
+# check to see if we have a stable release
+# in our package.* files that we can remove
+def check_for_stable_release(pkg):
+    if not is_pkg_package_masked(str(pkg.cpv)):
+        status = _get_mask_status(pkg, True)
+        if status == 0:
+            return True
+    return False
+
+#print version info
+def print_version():
+    # Print the version of this tool to the console.
+    print __productname__ + "(" + __version__ + ") - " + \
+        __description__
+    print "Author(s): " + __author__
+
+# function to go through a ~cp without a version
+# and set for removal from file if no masked package version exists
+def check_tilde_masked_pkg(package_name, filename):
+    ebuild_output=""
+    variable_version = ""
+
+    orig_package_name = package_name
+    package_name = package_name.replace('~','')
+
+    print_output(debug,portage.output.blue("check_tilde_maskd_pkg: orig_package-name is " + orig_package_name))
+    print_output(debug,portage.output.blue("check_tilde_maskd_pkg: package_name is " + package_name))
+
+    packages = gentoolkit.helpers.find_packages(package_name+"*", True)
+    no_versions_installed = True
+    for package in packages:
+        if package.is_installed():
+            no_versions_installed = False
+
+    if (no_versions_installed == True):
+        ebuild_output = portage.output.green("\n" + package_name + ": ") + portage.output.yellow("Not Installed")
+        if "package.unmask" in filename:
+            unmask_list.append(orig_package_name)
+        if "package.keywords" in filename:
+            stable_list.append(orig_package_name)
+        print ebuild_output + portage.output.brown(" : " + filename)
+
+        return
+
+    # get all packages matching cat/package
+    pkgs = gentoolkit.helpers.find_packages(package_name+"*", True)
+    if (pkgs != None):
+        for current_package in pkgs:
+            print_output(debug,portage.output.blue("check_tilde_maskd_pkg: current_package is " + str(current_package.cpv)))
+            if (compare_package_strings(package_name,str(current_package.cpv)) <=0):
+                packageObj = gentoolkit.package.Package(str(current_package.cpv))
+                if (packageObj == None):
+                    # we could not create a package object
+                    return
+                pkgmask = _get_mask_status(packageObj, True)
+
+                print_output(debug,portage.output.blue("check_tilde_maskd_pkg: current_package is " + str(current_package.cpv) + " and pkgmask is " + str(pkgmask)))
+
+                if "package.unmask" in filename:
+                    if (pkgmask >= 3):
+                        # package was found as masked
+                        return 
+                    else:
+                        # package is not masked
+                        unmask_list.append(str(current_package.cpv))
+                        ebuild_output = portage.output.yellow(str(current_package.cpv)) + ": " + portage.output.yellow("Not package masked")
+                        print_output(info,ebuild_output, package, filename)
+                        return
+                else:
+                    if (pkgmask >= 1):
+                        # package was found as masked
+                        return 
+                    else:
+                        # at this point we have no packages >= ~cpv that are masked, present for removal
+                        # package does not have any masked versions
+                        ebuild_output = portage.output.green(package_name + " has no masked versions")
+                        
+                        if "package.unmask" in filename:
+                            unmask_list.append(orig_package_name)
+                        if "package.keywords" in filename:
+                            stable_list.append(orig_package_name)
+                        print_output(info,ebuild_output, package, filename)
+
+#helper function to print avail pks when version does not exist
+def show_all_versions(pkg):
+
+    # is package masked
+    is_package_masked = False
+    pkgArr = portage.pkgsplit(pkg)
+
+    if pkgArr is None or len(pkgArr) == 0:
+        return
+
+    # determine if category/package is masked 
+    package = pkgArr[0]
+
+    operator = portage.dep.Atom(pkg).operator
+    if operator is not None:
+        package = package[len(operator):]
+
+    # package is category/package and pkg is category/package-version
+    # is category/package-version we are checking package masked?
+    if portage.settings.pmaskdict.has_key(package):
+        pkg_list = portage.settings.pmaskdict.get(package)
+        # iterate through list array looking for pkg
+        for pkg_check in pkg_list:
+            operator = portage.get_operator(pkg_check)
+            if operator is None:
+                if pkg_check == package:
+                    is_package_masked = True
+
+    all_pkgs = sort_package_list(gentoolkit.helpers.find_packages(package, True))
+    for current_package in all_pkgs:
+        keywords = "%s" % (current_package.environment("KEYWORDS").split())
+        keywords = filter_keywords(keywords)
+        keywords = "[" + keywords + "]"
+        ebuild = current_package.ebuild_path()
+        if ebuild:
+            pkgmask = _get_mask_status(current_package, True)
+            if is_package_masked:
+                print portage.output.red("Available: " + str(current_package.cpv) + " [M] Keywords: " + keywords)
+            elif pkgmask > 4:
+                print portage.output.red("Available: " + str(current_package.cpv) + " [M] Keywords: " + keywords)
+            elif pkgmask == 4 or pkgmask == 1:
+                print portage.output.brown("Available: " + str(current_package.cpv) + " Keywords: " + keywords)
+            else:
+                print portage.output.green("Available: " + str(current_package.cpv) + " Keywords: " + keywords)
+                stable_list.append(str(current_package.cpv))
+
+def handle_if_overlay(package):
+    overlay_text = ""
+    global print_overlay_flag
+    print_overlay_flag = True
+
+    ebuild_path,overlay_path = porttree.dbapi.findname2(str(package.cpv))
+    index = -1
+    try:
+        index = overlay_list.index(overlay_path)
+    except ValueError,error:
+        overlay_list.append(overlay_path)
+        index = overlay_list.index(overlay_path)
+
+    overlay_text = " [" + str(index+1) + "]"
+
+    return overlay_text
+
+# if the overlay_text was displayed to the user
+# we need to display the string at the end 
+# this array will store the overlays to be displayed
+def print_overlay_text():
+   
+    global print_overlay_flag
+    
+    if (not print_overlay_flag):
+        return
+    
+    if (len(overlay_list) <= 0):
+        return
+
+    index = 1
+    for x in overlay_list:
+        print portage.output.turquoise("[" + str(index) + "] ") + x
+        index = index + 1
+    
+    print "\n"
+
+#helper function to print output
+def print_output(log_level,output_string, package=None, filename=None):
+    
+    global logLevel
+
+    if package != None:
+        if (package.is_overlay()):
+            output_string = output_string + portage.output.turquoise(handle_if_overlay(package))
+        else:
+            if filename != None:
+                output_string = output_string + portage.output.brown(" : " + filename)
+
+    if (log_level <= logLevel):
+        print output_string
+
+# remove stabled files that are no longer needed from package.keywords
+# or package.mask
+# includes support for etc/portage/package.keywords/<whatever>/package.keywords
+def cleanFile (filename):
+    
+    removeDups = []
+    removed_list = []
+
+    # if the file or directory does not exist
+    # exit out
+    if (os.path.exists(filename) == False):
+            return
+    
+    if "package.keywords" in filename:
+        if ( len(stable_list) == 0):
+            return
+        for i in stable_list:
+            if not removeDups.count(i):
+                removeDups.append(i)
+    else:
+        if ( len(unmask_list) == 0):
+            return
+        for i in unmask_list:
+            if not removeDups.count(i):
+                removeDups.append(i)
+
+    removedDict = {}
+
+    try:
+        # determine if filename is a directory
+        if os.path.isdir(filename):
+            # get listing of directory
+            filenames = os.listdir(filename)
+            for file_name in filenames:
+                cleanFile(filename+os.path.sep+file_name)
+        else:   
+            #go through stable array and remove line if found
+            for line in fileinput.input(filename,inplace =1):
+                itemFound = False
+                line = line.strip()
+
+                # make sure line is not empty and do not remove commented out lines
+                if len(line) <= 0:
+                    continue
+                elif line.find("#") == 0:
+                    print line
+                    continue
+
+                for item in removeDups:
+                    if item in line:
+                        dup_found = False
+                        for check_item in removed_list:
+                            if (check_item == item):
+                                dup_found = True
+                        if (dup_found == False):
+                            removed_list.append(item)
+                            removedDict[filename] = item
+                            itemFound = True
+                if (itemFound == False):
+                    print line
+            fileinput.close()
+    except OSError,error:
+        print portage.output.red("Modify/Read access to file: " + filename + " failed: ") ,error
+    
+    if (len(removed_list) > 0):
+        print "\n"
+        for package in removed_list:
+            print portage.output.red("Removing from: ") + portage.output.yellow(filename) + ": " + portage.output.green(package) + "\n"
+
+
+# thanks to Paul Varner (Fuzzyray)
+#Returns the list ordered in the same way portage 
+#would do with lowest version at the head of the list.
+def sort_package_list(pkglist):
+    from gentoolkit.package import Package
+    pkglist.sort()
+    return pkglist
+            
+# main
+if __name__ == "__main__":
+
+    if len(sys.argv) == 1:
+        print_usage()
+        sys.exit(1)
+
+    # soooooo stolen from emerge
+    tmpcmdline = sys.argv[1:]
+
+    for cmd in tmpcmdline:
+        if cmd[0:1] == "-" and cmd[1:2] != "-":
+            for cmd_item in cmd[1:]:
+                if mappings.has_key(cmd_item):
+                    if mappings[cmd_item] in cmdline:
+                        print
+                        print "*** Warning: Redundant use of ", mappings[cmd_item]
+                    else:
+                        cmdline.append(mappings[cmd_item])
+                else:
+                    print "!!! Error: -"+cmd_item+" is an invalid option."
+                    sys.exit(-1)
+        else:
+            cmdline.append(cmd)
+
+    #parse long options
+    for cmd in cmdline:
+        if len(cmd)>=2 and cmd[0:2]=="--":
+            try:
+                i = options.index(cmd)
+                continue
+            except ValueError:
+                print "!!! Error: -"+cmd+" is an invalid option."
+                sys.exit(-1)
+
+    if "--changes-only" in cmdline:
+        cmdline.remove("--changes-only")
+        show_changes_only_flag = True
+
+    if "--removable-only" in cmdline:
+        cmdline.remove("--removable-only")
+        show_removable_only_flag = True
+
+    if "--debug" in cmdline:
+        logLevel = debug
+
+    if "--no-color" in cmdline:
+        portage.output.nocolor()
+
+    if "--tilde-check" in cmdline:
+        tilde=1
+
+    if "--version" in cmdline:
+        print_version()
+        sys.exit(0)
+    
+    if "--all" in cmdline:
+        tmpcmdline = ["--all"]
+        if "--fix" in cmdline:
+            tmpcmdline.append("--fix")
+        elif "--fixwithall" in cmdline:
+            tmpcmdline.append("--fixwithall")
+        cmdline=tmpcmdline      
+
+    if "--help" in cmdline:
+        print_usage()
+        sys.exit(0)
+    
+    if (show_changes_only_flag and show_removable_only_flag):
+        print "Please select only one of --show-removable (-r) or --changes-only"
+        print "Use --help for more info."
+        sys.exit(0)
+        
+    for cmd in cmdline:
+        if cmd == "--keyword":
+            print portage.output.bold("\npackage.keywords:")
+            get_recursive_info(USER_CONFIG_PATH + "/package.keywords")
+            if "--fix" in cmdline:
+                cleanFile(USER_CONFIG_PATH + "/package.keywords")
+            print portage.output.bold("Done\n")
+        elif cmd == "--unmask": 
+            print portage.output.bold("\npackage.unmask:")
+            checking_package_unmask = True
+            get_recursive_info(USER_CONFIG_PATH + "/package.unmask")
+            checking_package_unmask = False
+            if "--fix" in cmdline:
+                cleanFile(USER_CONFIG_PATH + "/package.unmask")
+        elif cmd == "--mask":
+            checking_package_mask = True
+            print portage.output.bold("\npackage.mask:")
+            get_recursive_info(USER_CONFIG_PATH + "/package.mask")
+            print portage.output.bold("Done\n")
+            checking_package_mask = False
+            if "--fix" in cmdline:
+                cleanFile(USER_CONFIG_PATH + "/package.mask")
+        elif cmd == "--package-use":
+            print portage.output.bold("\npackage.use:")
+            processing_package_use = True
+            get_recursive_info(USER_CONFIG_PATH + "/package.use")
+            if "--fix" in cmdline:
+                cleanFile(USER_CONFIG_PATH + "/package.use")
+            print portage.output.bold("Done\n")
+        elif cmd == "--all":
+            print portage.output.bold("\npackage.keywords:")
+            get_recursive_info(USER_CONFIG_PATH + "/package.keywords")
+            print portage.output.bold("\npackage.unmask:")
+            checking_package_unmask = True
+            get_recursive_info(USER_CONFIG_PATH + "/package.unmask")
+            checking_package_unmask = False
+            checking_package_mask = True
+            print portage.output.bold("\npackage.mask:")
+            get_recursive_info(USER_CONFIG_PATH + "/package.mask")
+            print portage.output.bold("\npackage.use:")
+            processing_package_use = True
+            get_recursive_info(USER_CONFIG_PATH + "/package.use")
+            if "--fix" in cmdline:
+                cleanFile(USER_CONFIG_PATH + "/package.keywords")
+                cleanFile(USER_CONFIG_PATH + "/package.unmask")
+                cleanFile(USER_CONFIG_PATH + "/package.use")
+                cleanFile(USER_CONFIG_PATH + "/package.mask")
+            print portage.output.bold("\nDone\n")
+
+    print_overlay_text()
+
+    if len(cmdline) == 0:
+        if show_changes_only_flag or show_removable_only_flag:
+            if (show_changes_only_flag):
+                print portage.output.green("-c") + " or " + portage.output.green("--changes-only") + " must be accompanied by one of the following:"
+            else:
+                print portage.output.green("-r") + " or " + portage.output.green("--removable-only") + " must be accompanied by one of the following:"
+
+            print "     " + portage.output.yellow("-k") + " or " + portage.output.yellow("--keyword")
+            print "     " + portage.output.yellow("-u") + " or " + portage.output.yellow("--unmask")
+            print "     " + portage.output.yellow("-m") + " or " + portage.output.yellow("--mask")
+            print "     " + portage.output.yellow("-a") + " or " + portage.output.yellow("--all")
+
+            print_usage()
+
diff --git a/portpeek.1 b/portpeek.1
new file mode 100644 (file)
index 0000000..e2ed28e
--- /dev/null
@@ -0,0 +1,79 @@
+.TH "PORTPEEK" "1" "Aug 2009"
+.SH "NAME"
+portpeek \- Package (un)masking management script
+.SH "SYNOPSIS"
+.TP
+.BR portpeek
+[\fIoptions\fR] \fIcommand\fR 
+.TP
+.BR portpeek
+\fB\-\-keyword\fR | \fB\-\-unmask\fR | \fB\-\-mask\fR | \fB\-\-package\-use\fR | \fB\-\-all
+.TP
+.BR portpeek
+\fB\-\-changes-only\fR [\fB--keyword\fR | \fB\-\-unmask\fR | \fB\-\-mask\fR | \fB\-\-package\-use\fR | \fB\-\-all]
+.SH "DESCRIPTION"
+\fBportpeek\fR is a utility script that will process a system's package.*
+files and display to the user potential package upgrades. Along with displaying
+the installed packages and the versions that the package could be upgraded to,
+the keywords for each specific package will also be displayed.
+Results are colorcoded for readability.
+.SH "OPTIONS"
+The 'command' is the only mandatory option to \fBportpeek\fR.
+
+[options] may only be:
+
+.B \-c, \-\-changes-only:
+show only packages that have a possible upgrade
+.PP
+.B \-n, \-\-no-color:
+suppress color output 
+.PP
+.B \-r, \-\-removable-only:
+show all matches that can be removed from package files
+.PP
+.B \-f \-\-fix:
+remove the stabled packages without asking for confirmation
+.PP
+.B \-d \-\-debug:
+display more verbose output for debugging
+.PP
+Except for version information, commands can be combined.  The possible commands are:
+
+.B \-a, \-\-all
+show all matches
+.PP
+.B \-h, \-\-help
+displays a help summary
+.PP
+.B \-k, \-\-keyword
+show matches from package.keywords only
+.PP
+.B \-m, \-\-mask
+show matches from package.mask only
+.PP
+.B \-u, \-\-unmask
+show matches from package.unmask only
+.PP
+.B \-s, \-\-package.use
+show matches from package.use only
+.PP
+.B \-t, \-\-tilde-check
+process tilde entries ~(cat/pkg-version) 
+.PP
+.B \-V, \-\-version
+displays the equery version
+.PP
+
+.SH "EXAMPLES"
+
+portpeek \-\-changes-only \-\-keyword \- list all packages with a potential upgrade located in package.keywords
+
+portpeek \-\-unmask \- list all packages and their keywords currently listed in package.unmask
+
+portpeek \-ac \- list all packages and keywords from all package.* files
+
+portpeek \-arf \- list all packages which are stable and can be removed and then remove them automatically
+
+.SH "AUTHORS"
+.nf
+Michael Pagano <mpagano@gentoo.org>