#!/usr/bin/python2
#
# Copyright 2003-2004 Karl Trygve Kalleberg
# Copyright 2003-2004 Gentoo Technologies, Inc.
# Distributed under the terms of the GNU General Public License v2
#
# $Header$
# Author: Karl Trygve Kalleberg <karltk@gentoo.org>

__author__ = "Karl Trygve Kalleberg"
__email__ = "karltk@gentoo.org"
__version__ = "0.1.4"
__productname__ = "equery"
__description__ = "Gentoo Package Query Tool"

import os
import re
import sys
import time
from glob import glob

# portage (output module) and gentoolkit need special path modifications
sys.path.insert(0, "/usr/lib/gentoolkit/pym")

import gentoolkit
try:
	import portage
except ImportError:
	sys.path.insert(0, "/usr/lib/portage/pym")
	import portage

try:
	import portage.checksum as checksum
	from portage.util import unique_array
except ImportError:
	import portage_checksum as checksum
	from portage_util import unique_array

import gentoolkit.pprinter as pp
from gentoolkit.pprinter import print_info, print_error, print_warn, die

# Auxiliary functions

def fileAsStr(name, fdesc, showType=0, showMD5=0, showTimestamp=0):
	"""
	Return file in fdesc as a filename
	@param name: 
	@param fdesc:
	@param showType:
	@param showMD5:
	@param showTimestamp:
	@rtype: string
	"""
	type = ""; fname = ""; stamp = ""; md5sum = ""

	if fdesc[0] == 'obj':
		type = "file"
		fname = name
		stamp = timestampAsStr(int(fdesc[1]))
		md5sum = fdesc[2]
	elif fdesc[0] == "dir":
		type = "dir"
		fname = pp.path(name)
	elif fdesc[0] == "sym":
		type = "symlink"
		stamp = timestampAsStr(int(fdesc[1].replace(")","")))
		tgt = fdesc[2].split()[0]
		if Config["piping"]:
			fname = name
		else:
			fname = pp.path_symlink(name + " -> " + tgt)
	elif fdesc[0] == "fif":
		type = "fifo"
		fname = name
	elif fdesc[0] == "dev":
		type = "device"
		fname = name
	else:
		raise Exception(name + " has unknown type: " + fdesc[0])

	s = ""
	if showType:
		s += "%6s " % type
	s += fname
	if showTimestamp:
		s += " " + stamp + " "
	if showMD5:
		s += " " + md5sum + " "
	return s

def timestampAsStr(timestamp):
	return time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(timestamp))

	
class Command:
	"""
	Abstract root class for all equery commands
	"""
	def __init__(self):
		pass
	def shortHelp(self):
		"""Return a help formatted to fit a single line, approx 70 characters.
		Must be overridden in the subclass."""
		return " - not implemented yet"
	def longHelp(self):
		"""Return full, multiline, color-formatted help.
		Must be overridden in the subclass."""
		return "help for syntax and options"
	def perform(self, args):
		"""Stub code for performing the command.
		Must be overridden in the subclass"""
		pass
	def parseArgs(self, args):
		"""Stub code for parsing command line arguments for this command.
		Must be overridden in the subclass."""
		pass

	
class CmdListFiles(Command):
	"""List files owned by a particular package"""
	def __init__(self):
		self.default_options = {
			"showType": 0,
			"showTimestamp": 0,
			"showMD5": 0,
			"tree": 0,
			"filter": None
			}

	def parseArgs(self,args):
		query = ""
		need_help = 0
		opts = self.default_options
		for x in args:
			if x in ["-h", "--help"]:
				need_help = 1
			elif x in ["--md5sum"]:
				opts["showMD5"] = 1
			elif x in ["--timestamp"]:
				opts["showTimestamp"] = 1
			elif x in ["--type"]:
				opts["showType"] = 1
			elif x in ["--tree"]:
				opts["tree"] = 1
			elif x[:9] == "--filter=":
				opts["filter"] = x[9:].split(',')
			elif x[0] == "/":
				die(2, "The query '" + pp.pkgquery(x) + "' does not appear to be a valid package specification")
			elif x[0] == "-":
				print_warn("unknown local option %s, ignoring" % x)
			else:
				query = x

		if need_help or query == "":
			print_info(0, self.longHelp())
			sys.exit(-1)
			
		return (query, opts)

	def filterContents(self, cnt, filter):
		if filter in [None,[]]:
			return cnt
		
		mycnt = {}
		
		for mytype in filter:
			# Filter elements by type (as recorded in CONTENTS).
			if mytype in ["dir","obj","sym","dev","fif"]:
				for mykey in cnt.keys():
					if cnt[mykey][0] == mytype:
						mycnt[mykey] = cnt[mykey]
		
		if "cmd" in filter:
			# List files that are in $PATH.
			userpath = map(os.path.normpath,os.environ["PATH"].split(os.pathsep))
			for mykey in cnt.keys():
				if cnt[mykey][0] in ['obj','sym'] \
				   and os.path.dirname(mykey) in userpath:
					mycnt[mykey] = cnt[mykey]
		
		if "path" in filter:
			# List only dirs where some files where actually installed,
			# and also skip their subdirs.
			mykeys = cnt.keys()
			mykeys.sort()
			while len(mykeys):
				mykey = mykeys.pop(0)
				if cnt[mykey][0] == 'dir':
					i = 0
					while i < len(mykeys) :
						if cnt[mykeys[i]][0] != "dir" \
						   and os.path.dirname(mykeys[i]) == mykey:
							mycnt[mykey] = cnt[mykey]
							break
						i += 1
					if i < len(mykeys):
						while len(mykeys) \
							  and len(mykey+"/") < len(mykeys[0]) \
							  and mykey+"/" == mykeys[0][:len(mykey)+1]:
							mykeys.pop(0)
		
		if "conf" in filter:
			# List configuration files.
			conf_path = gentoolkit.settings["CONFIG_PROTECT"].split()
			conf_mask_path = gentoolkit.settings["CONFIG_PROTECT_MASK"].split()
			conf_path = map(os.path.normpath, conf_path)
			conf_mask_path = map(os.path.normpath, conf_mask_path)
			for mykey in cnt.keys():
				is_conffile = False
				if cnt[mykey][0] == 'obj':
					for conf_dir in conf_path:
						if conf_dir+"/" == mykey[:len(conf_dir)+1]:
							is_conffile = True
							for conf_mask_dir in conf_mask_path:
								if conf_mask_dir+"/" == mykey[:len(conf_mask_dir)+1]:
									is_conffile = False
									break
							break
					if is_conffile:
						mycnt[mykey] = cnt[mykey]
			  
		
		for mydoctype in ["doc","man","info"]:
			# List only files from /usr/share/{doc,man,info}
			mydocpath = "/usr/share/"+mydoctype+"/"
			if mydoctype in filter:
				for mykey in cnt.keys():
					if cnt[mykey][0] == 'obj' \
					   and mykey[:len(mydocpath)] == mydocpath :
						mycnt[mykey] = cnt[mykey]
		
		return mycnt

	def perform(self, args):

		(query, opts) = self.parseArgs(args)

		# Turn off filtering for tree output
		if opts["tree"]:
			opts["filter"] = None

		if not Config["piping"] and Config["verbosityLevel"] >= 3:
			print_info(3, "[ Searching for packages matching " + pp.pkgquery(query) + "... ]")
			
		pkgs = gentoolkit.find_installed_packages(query, True)
		for x in pkgs:
			if not x.is_installed():
				continue
		
			if not Config["piping"] and Config["verbosityLevel"] >= 3:
				print_info(1, pp.section("* ") + "Contents of " + pp.cpv(x.get_cpv()) + ":")

			cnt = self.filterContents(x.get_contents(),opts["filter"])

			filenames = cnt.keys()
			filenames.sort()
			
			last=[]
			for name in filenames:
				if not opts["tree"]:
					print_info(0, fileAsStr(name,
								cnt[name],
								showType=opts["showType"],
								showTimestamp=opts["showTimestamp"],
								showMD5=opts["showMD5"]))
				else:
					c = name.split( "/" )[1:]
					if cnt[name][0] == "dir":
						if len(last) == 0:
							last = c
							print pp.path(" /" + c[0])
							continue
						numol = 0
						for d in c:
							if d in last:
								numol = last.index(d) + 1
								continue
							last = c
							if len(last) == 1:
								print pp.path(" " + last[0])
								continue
							print pp.path(" " * ( numol * 3 ) + "> " + "/" + last[-1])
					elif cnt[name][0] == "sym":
						print pp.path(" " * ( bl * 3 ) + "+ ") + pp.path_symlink(c[-1] + " -> " +  cnt[name][2])
					else:
						bl = len(last)
						print pp.path(" " * ( bl * 3 ) + "+ ") + c[-1]

	def longHelp(self):
		return "List files owned by a particular package\n" + \
			   "\n" + \
			   "Syntax:\n" + \
			   "  " + pp.command("files") + pp.localoption(" <local-opts> ") + pp.pkgquery("<cat/>packagename<-version>") + "\n" + \
			   "\n" + \
			   "Note: category and version parts are optional. \n" + \
			   "\n" + \
			   pp.localoption("<local-opts>") + " is either of: \n" + \
			   "  " + pp.localoption("--timestamp") + "      - append timestamp\n" + \
			   "  " + pp.localoption("--md5sum") + "         - append md5sum\n" + \
			   "  " + pp.localoption("--type") + "           - prepend file type\n" + \
			   "  " + pp.localoption("--tree") + "           - display results in a tree (turns off other options)\n" + \
			   "  " + pp.localoption("--filter=<rules>") + " - filter output\n" + \
			   "  " + pp.localoption("<rules>") + " is a comma separated list of elements you want to see:\n" + \
			   "  " + "  " + pp.localoption("dir") + \
					  ", " + pp.localoption("obj") + \
					  ", " + pp.localoption("sym") + \
					  ", " + pp.localoption("dev") + \
					  ", " + pp.localoption("fifo") + \
					  ", " + pp.localoption("path") + \
					  ", " + pp.localoption("conf") + \
					  ", " + pp.localoption("cmd") + \
					  ", " + pp.localoption("doc") + \
					  ", " + pp.localoption("man") + \
					  ", " + pp.localoption("info")

	def shortHelp(self):
		return pp.localoption("<local-opts> ") + pp.pkgquery("pkgspec") + " - list files owned by " + pp.pkgquery("pkgspec")

	
class CmdListBelongs(Command):
	"""List all packages owning a particular file"""
	def __init__(self):
		self.default_opts = {
			"category": "*",
			"fullRegex": 0,
			"earlyOut": 0,
			"nameOnly": 0
			}

	def parseArgs(self, args):

		query = []
		need_help = 0
		opts = self.default_opts
		skip = 0
		
		for i in xrange(len(args)):

			if skip:
				skip -= 1
				continue
			x = args[i]
			
			if x in ["-h","--help"]:
				need_help = 1
				break
			elif x in ["-c", "--category"]:
				opts["category"] = args[i+1]
				skip = 1
			elif x in ["-e", "--earlyout"]:
				opts["earlyOut"] = 1
			elif x in ["-f", "--full-regex"]:
				opts["fullRegex"] = 1
			elif x in ["-n", "--name-only"]:
				opts["nameOnly"] = 1
			elif x[0] == "-":
				print_warn("unknown local option %s, ignoring" % x)
			else:
				query.append(x)

		if need_help or query == []:
			print_info(0, self.longHelp())
			sys.exit(-1)
			
		return (query, opts)
				
	def perform(self, args):
		(query, opts) = self.parseArgs(args)

		if opts["fullRegex"]:
			q = query
		else:
			# Trim trailing and multiple slashes from query 
			for i in range(0, len(query)):
				query[i] = re.compile('/+').sub('/', query[i])
				query[i] = query[i].rstrip('/')
			q = map(lambda x: ((len(x) and x[0] == "/") and "^" or "/")
			                   + re.escape(x) + "$", query)
		try:
			q = "|".join(q)
			rx = re.compile(q)
		except:
			die(2, "The query '" + pp.regexpquery(q) + "' does not appear to be a valid regular expression")

		# Pick out only selected categories
		cat = opts["category"]
		filter_fn = None
		if cat != "*":
			filter_fn = lambda x: x.find(cat+"/")==0

		if not Config["piping"] and Config["verbosityLevel"] >= 3:
			print_info(3, "[ Searching for file(s) " + pp.regexpquery(",".join(query)) + " in " + pp.cpv(cat) + "... ]")
		
		matches = portage.db["/"]["vartree"].dbapi.cpv_all()
		#matches = gentoolkit.find_all_installed_packages(filter_fn)

		found = 0

		def dumpToPipe(pkg):
			mysplit = pkg.split("/")
			cnt = portage.dblink(mysplit[0], mysplit[1], "/", gentoolkit.settings).getcontents()
			#cnt = pkg.get_contents()
			if not cnt: return
			for file in cnt.keys():
				if rx.search(file) and (opts["category"] == "*" or portage.catpkgsplit(pkg)[0] == opts["category"]):
					if opts["nameOnly"]:
						x = portage.catpkgsplit(pkg)
						print x[0]+"/"+x[1]
					else:
						print pkg
					return

		class DummyExp:
			pass
			
		def dumpToScreen(pkg):
			mysplit = pkg.split("/")
			cnt = portage.dblink(mysplit[0], mysplit[1], "/", gentoolkit.settings).getcontents()
			#cnt = pkg.get_contents()
			if not cnt: return
			for file in cnt.keys():
				if rx.search(file) and (opts["category"] == "*" or portage.catpkgsplit(pkg)[0] == opts["category"]):
					if opts["nameOnly"]:
						x = portage.catpkgsplit(pkg)
						s = x[0]+"/"+x[1]
					else:
						s = pkg
					s += " (" + pp.path(fileAsStr(file, cnt[file])) + ")"
					print_info(0, s)
					if opts["earlyOut"]:
						raise DummyExp

		try: 
			if Config["piping"]:
				map(dumpToPipe, matches)
			else:
				map(dumpToScreen, matches)
		except DummyExp:
			pass
			
	def shortHelp(self):
		return pp.localoption("<local-opts> ") + pp.path("files...") + " - list all packages owning " + pp.path("files...")
	def longHelp(self):
		return "List all packages owning a particular set of files" + \
			   "\n" + \
			   "\n" + \
			   pp.emph("Note: ") + "Normally, only one package will own a file. If multiple packages own the same file, it usually consitutes a problem, and should be reported.\n" + \
			   "\n" + \
			   "Syntax:\n" + \
			   "  " + pp.command("belongs") + pp.localoption(" <local-opts> ") + pp.path("filename") + \
			   "\n" + \
			   pp.localoption("<local-opts>") + " is either of: \n" + \
					"  " + pp.localoption("-c, --category cat") + " - only search in category " + \
						pp.pkgquery("cat") + "\n" + \
					"  " + pp.localoption("-f, --full-regex") + "   - supplied query is a regex\n" + \
					"  " + pp.localoption("-e, --earlyout") + "     - stop when first match is found\n" + \
					"  " + pp.localoption("-n, --name-only") + "    - don't print the version."

class CmdDisplayUSEs(Command):
	"""Advanced report of a package's USE flags"""
	def __init__(self):
		self.default_opts = {
			"allPackages" : False
			}
	def parseArgs(self, args):

		query = ""
		need_help = 0
		opts = self.default_opts
		skip = 0
		
		for i in xrange(len(args)):

			if skip:
				skip -= 1
				continue
			x = args[i]
			
			if x in ["-h","--help"]:
				need_help = 1
				break
			elif x in ["-a", "--all"]:
				opts["allPackages"] = True
			elif x[0] == "-":
				print_warn("unknown local option %s, ignoring" % x)
			else:
				query = x

		if need_help or query == "":
			print_info(0, self.longHelp())
			sys.exit(-1)
			
		return (query, opts)

	def perform(self, args):

		(query, opts) = self.parseArgs(args)

		if not Config["piping"] and Config["verbosityLevel"] >= 3:
			print_info(3, "[ Searching for packages matching " + pp.pkgquery(query) + "... ]")
		
		if not opts["allPackages"]:
			matches = gentoolkit.find_installed_packages(query, True)
			if not matches:
				matches = gentoolkit.find_packages(query, False)
				if matches:
					matches = gentoolkit.sort_package_list(matches)
					matches = matches[-1:]
		else:
			matches = gentoolkit.find_packages(query, True)

		if not matches:
			die(3, "No matching packages found for \"" + pp.pkgquery(query) + "\"")


		useflags = gentoolkit.settings["USE"].split()	
		usedesc = {}
		uselocaldesc = {}

		# Load global USE flag descriptions
		try:
			fd = open(gentoolkit.settings["PORTDIR"]+"/profiles/use.desc")
			usedesc = {}
			for line in fd.readlines():
				if line[0] == "#":
					continue
				fields = line.split(" - ", 1)
				if len(fields) == 2:
					usedesc[fields[0].strip()] = fields[1].strip()
		except IOError:
			print_warn(5, "Could not load USE flag descriptions from " + ppath(gentoolkit.settings["PORTDIR"] + "/profiles/use.desc"))

		# TODO: Add USE_EXPANDED variables to usedesc hash -- Bug #238005
		# Pseudo-code:
		# for all files in gentoolkit.settings["PORTDIR"]+"/desc/*.desc
		# variable name = <filename>_<field1>
		# description = <field 2>
		for descfile in glob(gentoolkit.settings["PORTDIR"]+"/profiles/desc/*.desc"):
			try:
				fd = open(descfile)
				for line in fd.readlines():
					if line[0] == "#":
						continue
					fields = [field.strip() for field in line.split(" - ", 1)]
					if len(fields) == 2:
							usedesc["%s_%s" % (descfile.split("/")[-1][0:-5], fields[0],)] = fields[1]
			except IOError:
				print_warn(5, "Could not load USE flag descriptions from " + descfile)

		# Load local USE flag descriptions
		try:
			fd = open(gentoolkit.settings["PORTDIR"]+"/profiles/use.local.desc")
			for line in fd.readlines():
				if line[0] == "#":
					continue
				fields = line.split(" - ", 1)
				if len(fields) == 2:
					catpkguse = re.search("(.*):(.*)", fields[0])
					if catpkguse:
						if not uselocaldesc.has_key(catpkguse.group(1).strip()):
							uselocaldesc[catpkguse.group(1).strip()] = {catpkguse.group(2).strip() : fields[1].strip()}
						else:
							uselocaldesc[catpkguse.group(1).strip()][catpkguse.group(2).strip()] = fields[1].strip()
		except IOError:
				print_warn(5, "Could not load USE flag descriptions from " + path(gentoolkit.settings["PORTDIR"] + "/profiles/use.local.desc"))

		if not Config["piping"] and Config["verbosityLevel"] >= 3: 
			print_info(3, "[ Colour Code : " + pp.useflagon("set") + " " + pp.useflagoff("unset") + " ]")
			print_info(3, "[ Legend : Left column  (U) - USE flags from make.conf              ]")
			print_info(3, "[        : Right column (I) - USE flags packages was installed with ]")

		# Iterate through matches, printing a report for each package
		matches = gentoolkit.sort_package_list(matches)
		matches_found = 0
		for p in matches:

			matches_found += 1
			
			bestver = p.get_cpv()
			iuse = p.get_env_var("IUSE")
		  
			if iuse:
				# Fix Bug #91623 by making sure the list of USE flags is unique
				# Added sort to make output prettier
				usevar = unique_array(iuse.split())

				# Remove prefixed +/- from flags in IUSE, Bug #232019
				for i in range(len(usevar)):
					if usevar[i][0] == "+" or usevar[i][0] == "-":
						usevar[i] = usevar[i][1:]

				usevar.sort()
			else:
				usevar = []

			inuse = []
			if p.is_installed():
				used = p.get_use_flags().split()
			else:
				# cosmetic issue here as noninstalled packages don't have "used" flags
				used = useflags

			# store (inuse, inused, flag, desc)
			output = []

			for u in usevar:
				inuse = 0
				inused = 0
				try:
					desc = usedesc[u]
				except KeyError:
					try:
						desc = uselocaldesc[p.get_category() + "/" + p.get_name()][u]
					except KeyError:
						desc = ""

				if u in p.get_settings("USE").split():
					inuse = 1
				if u in used:
					inused = 1

				output.append((inuse, inused, u, desc))

			# pretty print
			if output:
				if not Config["piping"] and Config["verbosityLevel"] >= 3:
					print_info(0, "[ Found these USE variables for " + pp.cpv(bestver) + " ]")
					print_info(3, pp.emph(" U I"))
				maxflag_len = 0
				for inuse, inused, u, desc in output:
					if len(u) > maxflag_len:
						maxflag_len = len(u)

				for in_makeconf, in_installed, flag, desc in output:
					markers = ["-","+"]
					colour = [pp.useflagoff, pp.useflagon]
					if Config["piping"]:
						print_info(0, markers[in_makeconf] + flag)
					else:
						if in_makeconf != in_installed:
							print_info(0, pp.emph(" %s %s" % (markers[in_makeconf], markers[in_installed])), False)
						else:
							print_info(0, " %s %s" % (markers[in_makeconf], markers[in_installed]), False)
	
						print_info(0, " " + colour[in_makeconf](flag.ljust(maxflag_len)), False)

						# print description
						if desc:
							print_info(0, " : " + desc)
						else:
							print_info(0, " : <unknown>")
			else:
				if not Config["piping"] and Config["verbosityLevel"] >= 3:
					print_info(1, "[ No USE flags found for " + pp.cpv(p.get_cpv()) + "]")

		if Config["verbosityLevel"] >= 2:
			if matches_found == 0:
				s = ""
				die(3, "No " + s + "packages found for " + pp.pkgquery(query))
				
					
	def shortHelp(self):
		return pp.localoption("<local-opts> ") + pp.pkgquery("pkgspec") + " - display USE flags for " + pp.pkgquery("pkgspec")
	def longHelp(self):
		return "Display USE flags for a given package\n" + \
			   "\n" + \
			   "Syntax:\n" + \
			   "  " + pp.command("uses") + pp.localoption(" <local-opts> ") + pp.pkgquery("pkgspec") + \
			   "\n" + \
			   pp.localoption("<local-opts>") + " is: \n" + \
			   "  " + pp.localoption("-a, --all") + " - include all package versions\n"


class CmdDisplayDepGraph(Command):
	"""Display tree graph of dependencies for a query"""

	def __init__(self):
		self.default_opts = {
			"displayUSEFlags": 1,
			"fancyFormatting": 1,
			"depth": 0
			}

	def parseArgs(self, args):

		query = ""
		need_help = 0
		opts = self.default_opts
		skip = 0
		
		for i in xrange(len(args)):

			if skip:
				skip -= 1
				continue
			x = args[i]
			
			if x in ["-h","--help"]:
				need_help = 1
				break
			elif x in ["-U","--no-useflags"]:
				opts["displayUSEFlags"] = 0
			elif x in ["-l","--linear"]:
				opts["fancyFormatting"] = 0
			elif x[:8] == "--depth=":
				opts["depth"] = int(x[8:])
			elif x[0] == "-":
				print_warn("unknown local option %s, ignoring" % x)
			else:
				query = x

		if need_help or query == "":
			print_info(0, self.longHelp())
			sys.exit(-1)
			
		return (query, opts)

	def perform(self, args):
		(query, opts) = self.parseArgs(args)

		if not Config["piping"] and Config["verbosityLevel"] >= 3:
			print_info(3, "[ Searching for packages matching " + pp.pkgquery(query) + "... ]")

		matches = gentoolkit.find_packages(query, True)

		for pkg in matches:
			if not Config["piping"] and Config["verbosityLevel"] >= 3:
				print_info(3, pp.section("* ") + "dependency graph for " + pp.cpv(pkg.get_cpv()))
			else:
				print_info(0, pkg.get_cpv() + ":")

			stats = { "maxdepth": 0, "packages": 0 }
			self._graph(pkg, opts, stats, 0, [], "")
			if not Config["piping"] and Config["verbosityLevel"] >= 3:
				print_info(0, "[ " + pp.cpv(pkg.get_cpv()) + " stats: packages (" + pp.number(str(stats["packages"])) + \
				"), max depth (" + pp.number(str(stats["maxdepth"])) + ") ]")
		
	def _graph(self, pkg, opts, stats, level=0, pkgtbl=[], suffix=""):
	
		stats["packages"] += 1
		stats["maxdepth"] = max(stats["maxdepth"], level)
		
		cpv = pkg.get_cpv()

		pfx = ""
		if opts["fancyFormatting"]:
			pfx = level * " " + "`-- " 
		print_info(0, pfx + cpv + suffix)
		
		pkgtbl.append(cpv)
		
		pkgdeps = pkg.get_runtime_deps() + pkg.get_compiletime_deps() + pkg.get_postmerge_deps()
		for x in pkgdeps:
			suffix = ""
			cpv = x[2]
			pkg = gentoolkit.find_best_match(x[0] + cpv)
			if not pkg:
				print pfx + x[0] + cpv + " (unable to resolve to a package / package masked or removed)"
				continue
			if pkg.get_cpv() in pkgtbl:
				continue
			if cpv.find("virtual") == 0:
				suffix += " (" + pp.cpv(cpv) + ")"
			if len(x[1]) and opts["displayUSEFlags"]:
				suffix += " [ " + pp.useflagon(' '.join(x[1])) + " ]"
			if (level < opts["depth"] or opts["depth"] <= 0):
				pkgtbl = self._graph(pkg, opts, stats, level+1, pkgtbl, suffix)
		return pkgtbl

	def shortHelp(self):
		return pp.localoption("<local-opts> ") + pp.pkgquery("pkgspec") + " - display a dependency tree for " + pp.pkgquery("pkgspec")
	def longHelp(self):
		return "Display a dependency tree for a given package\n" + \
			   "\n" + \
			   "Syntax:\n" + \
			   "  " + pp.command("depgraph") + pp.localoption(" <local-opts> ") + pp.pkgquery("pkgspec") + \
			   "\n" + \
			   pp.localoption("<local-opts>") + " is either of: \n" + \
			   "  " + pp.localoption("-U, --no-useflags") + " - do not show USE flags\n" + \
			   "  " + pp.localoption("-l, --linear") + "      - do not use fancy formatting\n" + \
			   "  " + pp.localoption("    --depth=n") + "     - limit dependency graph to specified depth"


class CmdDisplaySize(Command):
	"""Display disk size consumed by a package"""
	def __init__(self):
		self.default_opts = {
			"regex": 0,
			"exact": 0,
			"reportSizeInBytes": 0
			}

	def parseArgs(self, args):

		query = ""
		need_help = 0
		opts = self.default_opts
		skip = 0
		
		for i in xrange(len(args)):

			if skip:
				skip -= 1
				continue
			x = args[i]
			
			if x in ["-h","--help"]:
				need_help = 1
				break
			elif x in ["-b","--bytes"]:
				opts["reportSizeInBytes"] = 1
			elif x in ["-f", "--full-regex"]:
				opts["regex"] = 1
			elif x in ["-e", "--exact-name"]:
				opts["exact"] = 1
			elif x[0] == "-":
				print_warn("unknown local option %s, ignoring" % x)
			else:
				query = x

#		if need_help or query == "":
		if need_help:
			print_info(0, self.longHelp())
			sys.exit(-1)
			
		return (query, opts)
				
	def perform(self, args):
		(query, opts) = self.parseArgs(args)

		rev = ""
		name = ""
		ver = ""
		cat = ""

		if query != "":
			(cat, name, ver, rev) = gentoolkit.split_package_name(query)
			if rev == "r0": rev = ""

		# replace empty strings with .* and escape regular expression syntax
		if query != "":
			if not opts["regex"]:
				cat, name, ver, rev = [re.sub('^$', ".*", re.escape(x)) for x in cat, name, ver, rev]
			else:
				cat, name, ver, rev = [re.sub('^$', ".*", x) for x in cat, name, ver, rev]

			try:
				if opts["exact"]:
					filter_fn = lambda x: re.match(cat+"/"+name, x)
				else:
					filter_fn = lambda x: re.match(cat+"/.*"+name, x)
				matches = gentoolkit.find_all_installed_packages(filter_fn)
			except:
				die(2, "The query '" + pp.regexpquery(query) + "' does not appear to be a valid regular expression")
		else:
			cat, name, ver, rev = [re.sub('^$', ".*", x) for x in cat, name, ver, rev]
			matches = gentoolkit.find_all_installed_packages()

		matches = gentoolkit.sort_package_list(matches)

		if not Config["piping"] and Config["verbosityLevel"] >= 3:
			print_info(3, "[ Searching for packages matching " + pp.pkgquery(query) + "... ]")

		# If no version supplied, fix regular expression
		if ver == ".*": ver = "[0-9]+[^-]*"

		if rev != ".*":	# revision supplied
			ver = ver + "-" + rev

		if opts["exact"]:
			rx = re.compile(cat + "/" + name + "-" + ver)
		else:
			rx = re.compile(cat + "/.*" + name + ".*-" + ver)

		for pkg in matches:
			if rx.search(pkg.get_cpv()):
				(size, files, uncounted) = pkg.size()

				if Config["piping"]:
					print_info(0, pkg.get_cpv() + ": total(" + str(files) + "), inaccessible(" + str(uncounted) + \
						"), size(" + str(size) + ")")
				else:
					print_info(0, pp.section("* ") + "size of " + pp.cpv(pkg.get_cpv()))
					print_info(0, " Total files : ".rjust(25) + pp.number(str(files)))

					if uncounted:
						print_info(0, " Inaccessible files : ".rjust(25) + pp.number(str(uncounted)))

					sz = "%.2f KiB" % (size/1024.0)				
					if opts["reportSizeInBytes"]:
						sz = pp.number(str(size)) + " bytes"

					print_info(0, "Total size  : ".rjust(25) + pp.number(sz))

					
	def shortHelp(self):
		return pp.localoption("<local-opts> ") + pp.pkgquery("pkgspec") + " - print size of files contained in package " + pp.pkgquery("pkgspec")
	def longHelp(self):
		return "Print size total size of files contained in a given package" + \
			   "\n" + \
			   "Syntax:\n" + \
			   "  " + pp.command("size") + pp.localoption(" <local-opts> ") + pp.pkgquery("pkgspec") + \
			   "\n" + \
			   pp.localoption("<local-opts>") + " is: \n" + \
			   "  " + pp.localoption("-b, --bytes") + "      - report size in bytes\n" \
			   "  " + pp.localoption("-f, --full-regex") + " - query is a regular expression\n" + \
			   "  " + pp.localoption("-e, --exact-name") + " - list only those packages that exactly match\n"

class CmdDisplayChanges(Command):
	"""Display changes for pkgQuery"""
	pass

class CheckException:
	def __init__(self, s):
		self.s = s
	
class CmdCheckIntegrity(Command):
	"""Check timestamps and md5sums for files owned by pkgspec"""
	def __init__(self):
		self.default_opts = {
			"showSummary" : 1,
			"showGoodFiles" : 0,
			"showBadFiles" : 1,
			"checkTimestamp" : 1,
			"checkMD5sum": 1
			}

	def parseArgs(self, args):

		query = ""
		need_help = 0
		opts = self.default_opts
		skip = 0
		
		for i in xrange(len(args)):
			if skip:
				skip -= 1
				continue
			x = args[i]
			
			if x in ["-h","--help"]:
				need_help = 1
				break
			elif x[0] == "-":
				print_warn("unknown local option %s, ignoring" % x)
			else:
				query = x

		if need_help:
			print_info(0, self.longHelp())
			sys.exit(-1)
			
		return (query, opts)

	def getMD5sum(self, file):
		return checksum.perform_md5(file, calc_prelink=1)
	
	def perform(self, args):
		(query, opts) = self.parseArgs(args)

		if query == "":
			matches=gentoolkit.find_all_installed_packages()
		else:
			matches = gentoolkit.find_packages(query, True)

		matches = gentoolkit.sort_package_list(matches)

		for pkg in matches:
			if not pkg.is_installed():
				continue
			if not Config["piping"] and Config["verbosityLevel"] >= 3:
				print_info(1, "[ Checking " + pp.cpv(pkg.get_cpv()) + " ]")
			else:
				print_info(0, pkg.get_cpv() + ":")
				
			files = pkg.get_contents()
			checked_files = 0
			good_files = 0
			for file in files.keys():
				type = files[file][0]
				try:
					st = os.lstat(file)
					if type == "dir":
						if not os.path.isdir(file):
							raise CheckException(file + " exists, but is not a directory")
					elif type == "obj":
						mtime = files[file][1]
						md5sum = files[file][2]
						if opts["checkMD5sum"]:
							try: 
								actual_checksum = self.getMD5sum(file)
							except:
								raise CheckException("Failed to calculate MD5 sum for " + file)
								
							if self.getMD5sum(file) != md5sum:
								raise CheckException(file + " has incorrect md5sum")
						if opts["checkTimestamp"]:
							if int(st.st_mtime) != int(mtime):
								raise CheckException(file + (" has wrong mtime (is %d, should be %s)" % (st.st_mtime, mtime)))
					elif type == "sym":
						# FIXME: nastry strippery; portage should have this fixed!
						t = files[file][2]
						# target = os.path.normpath(t.strip())
						target = t.strip()
						if not os.path.islink(file):
							raise CheckException(file + " exists, but is not a symlink")
						tgt = os.readlink(file)
						if tgt != target:
							raise CheckException(file + " does not point to " + target)
					elif type == "fif":
						pass
					else:
						pp.print_error(file)
						pp.print_error(files[file])
						pp.print_error(type)
						raise CheckException(file + " has unknown type " + type)
					good_files += 1
				except CheckException, (e):
					print_error(e.s)
				except OSError:
					print_error(file + " does not exist")
				checked_files += 1
			print_info(0, pp.section(" * ") + pp.number(str(good_files)) + " out of " +  pp.number(str(checked_files)) + " files good")
					
	def shortHelp(self):
		return pp.pkgquery("pkgspec") + " - check MD5sums and timestamps of " + pp.pkgquery("pkgspec") + "'s files"
	def longHelp(self):
		return "Check package's files against recorded MD5 sums and timestamps" + \
				"\n" + \
				"Syntax:\n" + \
				"  " + pp.command("check") + pp.pkgquery(" pkgspec")

class CmdDisplayStatistics(Command):
	"""Display statistics about installed and uninstalled packages"""
	pass

class CmdWhich(Command):
	"""Display the filename of the ebuild for a given package
	   that would be used by Portage with the current configuration."""
	def __init__(self):
		self.default_opts = {
			"includeMasked": False
			}

	def parseArgs(self, args):

		query = ""
		need_help = 0
		opts = self.default_opts
		skip = 0
		
		for i in xrange(len(args)):

			if skip:
				skip -= 1
				continue
			x = args[i]
			
			if x in ["-h","--help"]:
				need_help = 1
				break
			elif x in ["-m", "--include-masked"]:
				opts["includeMasked"] = True
			elif x[0] == "-":
				print_warn("unknown local option %s, ignoring" % x)
			else:
				query = x

		if need_help or query == "":
			print_info(0, self.longHelp())
			sys.exit(-1)
			
		return (query, opts)
				
	def perform(self, args):
		(query, opts) = self.parseArgs(args)

		matches = gentoolkit.find_packages(query, opts["includeMasked"])
		matches = gentoolkit.sort_package_list(matches)

		if matches:
			pkg = matches[-1]
			ebuild_path = pkg.get_ebuild_path()
			if ebuild_path:
				print_info(0, os.path.normpath(ebuild_path))
			else:
				print_warn("There are no ebuilds to satisfy %s" % pkg.get_name())
		else:
			print_error("No masked or unmasked packages found for " + pp.pkgquery(query))
					
	def shortHelp(self):
		return pp.pkgquery("pkgspec") + " - print full path to ebuild for package " + pp.pkgquery("pkgspec")
	def longHelp(self):
		# Not documenting --include-masked at this time, since I'm not sure that it is needed. - FuzzyRay
		return "Print full path to ebuild for a given package" + \
			"\n" + \
			"Syntax:\n" + \
			"  " + pp.command("which ") + pp.pkgquery("pkgspec")

class CmdListGLSAs(Command):
	"""List outstanding GLSAs."""
	pass

class CmdListDepends(Command):
	"""List all packages directly or indirectly depending on pkgQuery"""
	def __init__(self):
		self.default_opts = {
			"onlyDirect": 1,
			"onlyInstalled": 1,
			"spacing": 0,
			"depth": -1
			}
		# Used to cache and detect looping
		self.pkgseen = []
		self.pkglist = []
		self.pkgdeps = {}
		self.deppkgs = {}

	def parseArgs(self, args):

		query = ""
		need_help = 0
		opts = self.default_opts
		skip = 0
		 
		for i in xrange(len(args)):
			if skip:
				skip -= 1
				continue
			x = args[i]
			 
			if x in ["-h","--help"]:
				need_help = 1
				break
			elif x in ["-d", "--direct"]:
				opts["onlyDirect"] = 1
			elif x in ["-D", "--indirect"]:
				opts["onlyDirect"] = 0
			elif x in ["-a", "--all-packages"]:
				opts["onlyInstalled"] = 0
			elif x[:10] == "--spacing=":
				opts["spacing"] = int(x[10:])
			elif x[:8] == "--depth=":
				opts["depth"] = int(x[8:])
			elif x[0] == "-":
				print_warn("unknown local option %s, ignoring" % x)
			else:
				query = x
	 
		if need_help or query == "":
			print self.longHelp()
			sys.exit(-1)
		return (query, opts)
 
	def perform(self, args):

		(query, opts) = self.parseArgs(args)

		# We call ourself recursively if --indirect specified. spacing is used to control printing the tree.
		spacing = opts["spacing"]

		if not Config["piping"] and Config["verbosityLevel"] >= 3 and not spacing:
			print_info(3, "[ Searching for packages depending on " + pp.pkgquery(query) + "... ]")

		isdepend = gentoolkit.split_package_name(query)
		isdepends = map((lambda x: x.get_cpv()), gentoolkit.find_packages(query))
		if not isdepends:
			print_warn("Warning: No packages found matching %s" % query)
		
		# Cache the list of packages
		if not self.pkglist:
			if opts["onlyInstalled"]:
				packages = gentoolkit.find_all_installed_packages()
			else:
				packages = gentoolkit.find_all_packages()

			packages = gentoolkit.sort_package_list(packages)
			self.pkglist = packages
		else:
			packages = self.pkglist

		for pkg in packages:
			pkgcpv = pkg.get_cpv()
			if not pkgcpv in self.pkgdeps:
				try:
					deps = pkg.get_runtime_deps() + pkg.get_compiletime_deps() + pkg.get_postmerge_deps()
				except KeyError, e:
					# If the ebuild is not found... 
					continue
				# Remove duplicate deps
				deps = unique_array(deps)
				self.pkgdeps[pkgcpv] = deps
			else:
				deps = self.pkgdeps[pkgcpv]
			isdep = 0
			for dependency in deps:
				# TODO determine if dependency is enabled by USE flag
				# Find all packages matching the dependency
				depstr = dependency[0]+dependency[2]
				if not depstr in self.deppkgs:
					try:
						depcpvs = map((lambda x: x.get_cpv()), gentoolkit.find_packages(depstr))
						self.deppkgs[depstr] = depcpvs
					except KeyError, e:
						print_warn("")
						print_warn("Package: " + pkgcpv + " contains invalid dependency specification.")
						print_warn("Portage error: " + str(e))
						print_warn("")
						continue
				else:
					depcpvs = self.deppkgs[depstr]
				for x in depcpvs:
					cpvs=gentoolkit.split_package_name(x)
					if x in isdepends:
						cat_match=1
						name_match=1
						ver_match=1
					else:
						cat_match=0
						name_match=0
						ver_match=0
						# Match Category
						if not isdepend[0] or cpvs[0] == isdepend[0]:
							cat_match=1
						# Match Name
						if cpvs[1] == isdepend[1]:
							name_match=1
						# Match Version
						if not isdepend[2] or ( cpvs[2] == isdepend[2] and (isdepend[3] \
						   or isdepend[3] == "r0" or cpvs[3] == isdepend[3])):
							ver_match=1

					if cat_match and ver_match and name_match:
						if not isdep:
							if dependency[1]:
								if not Config["piping"] and Config["verbosityLevel"] >= 3:
									print " " * (spacing * 2) + pp.cpv(pkg.get_cpv()),
								   	print "(" + \
									pp.useflag(" & ".join(dependency[1]) + "? ") + \
									pp.pkgquery(dependency[0]+dependency[2]) + ")"
								else:
									print " " * (spacing * 2) + pkg.get_cpv()
							else:
								if not Config["piping"] and Config["verbosityLevel"] >= 3:
									print " " * (spacing * 2) + pp.cpv(pkg.get_cpv()),
									print "(" + pp.pkgquery(dependency[0]+dependency[2]) + ")"
								else:
									print " " * (spacing * 2) + pkg.get_cpv()
							isdep = 1
						elif not Config["piping"] and Config["verbosityLevel"] >= 3:
							if dependency[1]:
								print " "*len(pkg.get_cpv()) + " " * (spacing * 2) + \
								      " (" + pp.useflag("&".join(dependency[1]) + "? ") + \
								      pp.pkgquery(dependency[0]+dependency[2]) + ")"
							else:
								print " "*len(pkg.get_cpv()) + " " * (spacing * 2) + " (" + \
								      pp.pkgquery(dependency[0]+dependency[2]) + ")"	

						break

			# if --indirect specified, call ourselves again with the dependency
			# Do not call, if we have already called ourselves.
			if isdep and not opts["onlyDirect"] and pkg.get_cpv() not in self.pkgseen \
			   and (spacing < opts["depth"] or opts["depth"] == -1):
				self.pkgseen.append(pkg.get_cpv())
				self.perform(['=' + pkg.get_cpv(), '--indirect', '--spacing=' + str(int(opts["spacing"]+1))])
				opts["spacing"] = spacing;
 
 
	def shortHelp(self):
		return pp.localoption("<local-opts> ") + pp.pkgquery("pkgspec") + " - list all direct dependencies matching " + \
		       pp.pkgquery("pkgspec")
	 
	def longHelp(self):
		 return "List all direct dependencies matching a query pattern" + \
				"\n" + \
				"Syntax:\n" + \
				"  " + pp.command("depends") + pp.localoption(" <local-opts> ") + pp.pkgquery("pkgspec") + \
				"\n" + \
				pp.localoption("<local-opts>") + " is either of: \n" + \
				"  " + pp.localoption("-a, --all-packages") + " - search in all available packages (slow)\n" + \
				"  " + pp.localoption("-d, --direct") + "       - search direct dependencies only (default)\n" + \
				"  " + pp.localoption("-D, --indirect") + "     - search indirect dependencies (VERY slow)\n" + \
				"  " + pp.localoption("    --depth=n") + "      - limit indirect dependency tree to specified depth"


class CmdListPackages(Command):
	"""List packages satisfying pkgQuery"""
	def __init__(self):
		self.default_opts = {
			"category": "*",
			"includeInstalled": 1,
			"includePortTree": 0,
			"includeOverlayTree": 0,
			"includeMasked": 1,
			"regex": 0,
			"exact": 0,
			"duplicates": 0
			}

	def parseArgs(self, args):

		query = ""
		need_help = 0
		opts = self.default_opts
		skip = 0
		
		for i in xrange(len(args)):

			if skip:
				skip -= 1
				continue
			x = args[i]
			
			if x in ["-h","--help"]:
				need_help = 1
				break
			elif x in ["-i", "--installed"]:
				opts["includeInstalled"] = 1
			elif x in ["-I", "--exclude-installed"]:
				# If -I is the only option, warn
				# (warning located in perform())
				opts["includeInstalled"] = 0
			elif x in ["-p", "--portage-tree"]:
				opts["includePortTree"] = 1
			elif x in ["-o", "--overlay-tree"]:
				opts["includeOverlayTree"] = 1
			elif x in ["-m", "--exclude-masked"]:
				opts["includeMasked"] = 0
			elif x in ["-f", "--full-regex"]:
				opts["regex"] = 1
			elif x in ["-e", "--exact-name"]:
				opts["exact"] = 1
			elif x in ["-d", "--duplicates"]:
				opts["duplicates"] = 1
			elif x[0] == "-":
				print_warn("unknown local option %s, ignoring" % x)
			else:
				query = x

		# Only search installed packages when listing duplicated packages
		if opts["duplicates"]:
			opts["includeInstalled"] = 1
			opts["includePortTree"] = 0
			opts["includeOverlayTree"] = 0

		if need_help:
			print_info(0, self.longHelp())
			sys.exit(-1)
			
		return (query, opts)
				
	def perform(self, args):
		(query, opts) = self.parseArgs(args)

		rev = ""
		name = ""
		ver = ""
		cat = ""

		if query != "":
			try: (cat, name, ver, rev) = gentoolkit.split_package_name(query)
			except ValueError, e:
				if str(e) == 'too many values to unpack':
					print_error("A pattern to match against package names was expected, ")
					warn_msg = "but %s has too many slashes ('/') to match any package."
					die (1, warn_msg % query)
				else: raise ValueError(e)
			if rev == "r0": rev = ""

		package_finder = None

		if opts["includeInstalled"]:
			if opts["includePortTree"] or opts["includeOverlayTree"]:
				package_finder = gentoolkit.find_all_packages
			else:
				package_finder = gentoolkit.find_all_installed_packages
		elif opts["includePortTree"] or opts["includeOverlayTree"]:
			package_finder = gentoolkit.find_all_uninstalled_packages
		else:
			# -I was specified, and no selection of what packages to list was made
			print_warn("With -I you must specify one of -i, -p or -o. Assuming -p")
			opts["includePortTree"] = 1
			package_finder = gentoolkit.find_all_uninstalled_packages

		filter_fn = None

		if Config["verbosityLevel"] >= 3:
			scat = "'" + cat + "'"
			if not cat:
				scat = "all categories"
			sname = "package '" + name + "'"
			if not name:
				sname = "all packages"
			if not Config["piping"] and Config["verbosityLevel"] >= 3:
				print_info(1, "[ Searching for " + pp.cpv(sname) + " in " + pp.cpv(scat) + " among: ]")

		# replace empty strings with .* and escape regular expression syntax
		if query != "":
			if not opts["regex"]:
				cat, name, ver, rev = [re.sub('^$', ".*", re.escape(x)) for x in cat, name, ver, rev]
			else:
				cat, name, ver, rev = [re.sub('^$', ".*", x) for x in cat, name, ver, rev]

			try:
				if opts["exact"]:
					filter_fn = lambda x: re.match(cat+"/"+name, x)
				else:
					filter_fn = lambda x: re.match(cat+"/.*"+name, x)
				matches = package_finder(filter_fn)
			except:
				die(2, "The query '" + pp.regexpquery(query) + "' does not appear to be a valid regular expression")
		else:
			cat, name, ver, rev = [re.sub('^$', ".*", x) for x in cat, name, ver, rev]
			filter_fn = lambda x: True
			matches = package_finder(filter_fn)

		# Find duplicate packages
		if opts["duplicates"]:
			dups = {}
			newmatches = []
			for pkg in matches:
				mykey = pkg.get_category() + "/" + pkg.get_name() 
				if dups.has_key(mykey):
					dups[mykey].append(pkg)
				else:
					dups[mykey] = [pkg]

			for mykey in dups.keys():
				if len(dups[mykey]) > 1:
					newmatches += dups[mykey]

			matches = newmatches

		matches = gentoolkit.sort_package_list(matches)

		# If no version supplied, fix regular expression
		if ver == ".*": ver = "[0-9]+[^-]*"

		if rev != ".*":	# revision supplied
			ver = ver + "-" + rev

		if opts["exact"]:
			rx = re.compile(cat + "/" + name + "-" + ver)
		else:
			rx = re.compile(cat + "/.*" + name + ".*-" + ver)

		if opts["includeInstalled"]:
			self._print_installed(matches, rx)
		
		if opts["includePortTree"]:
			self._print_porttree(matches, rx)
		
		if opts["includeOverlayTree"]:
			self._print_overlay(matches, rx)

	def _get_mask_status(self, pkg):
		pkgmask = 0
		if pkg.is_masked():
		# Uncomment to only have package masked files show an 'M'
		# maskreasons = portage.getmaskingstatus(pkg.get_cpv())
		# if "package.mask" in maskreasons:
			pkgmask = pkgmask + 3
		keywords = pkg.get_env_var("KEYWORDS").split()
		if gentoolkit.settings["ARCH"] not in keywords:
			if "~" + gentoolkit.settings["ARCH"] in keywords:
				pkgmask = pkgmask + 1
			elif "-" + gentoolkit.settings["ARCH"] in keywords or "-*" in keywords:
				pkgmask = pkgmask + 2
		return pkgmask

	def _generic_print(self, header, exclude, matches, rx, status):
		if Config["verbosityLevel"] >= 3:
			print_info(1, header)

		pfxmodes = [ "---", "I--", "-P-", "--O" ]
		maskmodes = [ "  ", " ~", " -", "M ", "M~", "M-" ]

		for pkg in matches:
			if exclude(pkg):
				continue

			pkgmask = self._get_mask_status(pkg)
			
			slot = pkg.get_env_var("SLOT")

			if rx.search(pkg.get_cpv()):
				if Config["piping"]:
					print_info(0, pkg.get_cpv())
				else:
					print_info(0, "[" + pp.installedflag(pfxmodes[status]) + "] [" + pp.maskflag(maskmodes[pkgmask]) + "] " + pp.cpv(pkg.get_cpv()) + " (" + pp.slot(slot) + ")")

	def _print_overlay(self, matches, rx):
		self._generic_print(
			pp.section(" *") + " overlay tree (" + pp.path(gentoolkit.settings["PORTDIR_OVERLAY"]) + ")",
			lambda x: not x.is_overlay(),
			matches, rx, 3)
			
	def _print_porttree(self, matches, rx):
		self._generic_print(
			pp.section(" *") + " Portage tree (" + pp.path(gentoolkit.settings["PORTDIR"]) + ")",
			lambda x: x.is_overlay() or x.is_installed(),
			matches, rx, 2)
				
	def _print_installed(self, matches, rx):
		self._generic_print(
			pp.section(" *") + " installed packages",
			lambda x: not x.is_installed(),
			matches, rx, 1)

	def shortHelp(self):
		return pp.localoption("<local-opts> ") + pp.pkgquery("pkgspec") + " - list all packages matching " + pp.pkgquery("pkgspec")
	def longHelp(self):
		return "List all packages matching a query pattern" + \
			   "\n" + \
			   "Syntax:\n" + \
			   "  " + pp.command("list") + pp.localoption(" <local-opts> ") + pp.pkgquery("pkgspec") + \
			   "\n" + \
			   pp.localoption("<local-opts>") + " is either of: \n" + \
			   "  " + pp.localoption("-i, --installed") + "         - search installed packages (default)\n" + \
			   "  " + pp.localoption("-I, --exclude-installed") + " - do not search installed packages\n" + \
			   "  " + pp.localoption("-p, --portage-tree") + "      - also search in portage tree (" + gentoolkit.settings["PORTDIR"] + ")\n" + \
			   "  " + pp.localoption("-o, --overlay-tree") + "      - also search in overlay tree (" + gentoolkit.settings["PORTDIR_OVERLAY"] + ")\n" + \
			   "  " + pp.localoption("-f, --full-regex") + "        - query is a regular expression\n" + \
			   "  " + pp.localoption("-e, --exact-name") + "        - list only those packages that exactly match\n" + \
			   "  " + pp.localoption("-d, --duplicates") + "        - list only installed duplicate packages\n"

class CmdFindUSEs(Command):
	"""Find all packages with a particular USE flag."""
	def __init__(self):
		self.default_opts = {
			"category": "*",
			"includeInstalled": 1,
			"includePortTree": 0,
			"includeOverlayTree": 0,
			"includeMasked": 1,
			"regex": 0
			}

	def parseArgs(self, args):

		query = ""
		need_help = 0
		opts = self.default_opts
		skip = 0
		
		for i in xrange(len(args)):

			if skip:
				skip -= 1
				continue
			x = args[i]
			
			if x in ["-h","--help"]:
				need_help = 1
				break
			elif x in ["-i", "--installed"]:
				opts["includeInstalled"] = 1
			elif x in ["-I", "--exclude-installed"]:
				opts["includeInstalled"] = 0
			elif x in ["-p", "--portage-tree"]:
				opts["includePortTree"] = 1
			elif x in ["-o", "--overlay-tree"]:
				opts["includeOverlayTree"] = 1
			elif x in ["-m", "--exclude-masked"]:
				opts["includeMasked"] = 0
			elif x[0] == "-":
				print_warn("unknown local option %s, ignoring" % x)
			else:
				query = x

		if need_help:
			print_info(0, self.longHelp())
			sys.exit(-1)
			
		return (query, opts)
				
	def perform(self, args):
		(query, opts) = self.parseArgs(args)

		rev = ".*"
		name = ".*"
		ver = ".*"
		cat = ".*"
		
		package_finder = None

		if opts["includeInstalled"] and (opts["includePortTree"] or opts["includeOverlayTree"]):
			package_finder = gentoolkit.find_all_packages
		elif opts["includeInstalled"]:
			package_finder = gentoolkit.find_all_installed_packages
		elif opts["includePortTree"] or opts["includeOverlayTree"]:
			package_finder = gentoolkit.find_all_uninstalled_packages

		if not package_finder:
			die(2,"You must specify one of -i, -p or -o")

		filter_fn = lambda x: True
			
		if Config["verbosityLevel"] >= 3:
			scat = "'" + cat + "'"
			if cat == ".*":
				scat = "all categories"
			if not Config["piping"]:
				print_info(2, "[ Searching for USE flag " + pp.useflag(query)  + " in " + pp.cpv(scat) + " among: ]")
				if opts["includeInstalled"]:
					print_info(1, pp.section(" *") + " installed packages")
				if opts["includePortTree"]:
					print_info(1, pp.section(" *") + " Portage tree (" + pp.path(gentoolkit.settings["PORTDIR"]) + ")")
				if opts["includeOverlayTree"]:
					print_info(1, pp.section(" *") + " overlay tree (" + pp.path(gentoolkit.settings["PORTDIR_OVERLAY"]) + ")")
		
		matches = package_finder(filter_fn)

		rx = re.compile(cat + "/" + name + "-" + ver + "(-" + rev + ")?")
		pfxmodes = [ "---", "I--", "-P-", "--O" ]
		maskmodes = [ "  ", " ~", " -", "M ", "M~", "M-" ]
		for pkg in matches:
			status = 0
			if pkg.is_installed():
				status = 1
			elif pkg.is_overlay():
				status = 3
			else:
				status = 2

			useflags = pkg.get_env_var("IUSE").split()
			useflags = [f.lstrip("+-") for f in useflags]

			if query not in useflags:
				continue 
				
			# Determining mask status
			pkgmask = 0
			if pkg.is_masked():
				pkgmask = pkgmask + 3
			keywords = pkg.get_env_var("KEYWORDS").split()
			if "~"+gentoolkit.settings["ARCH"] in keywords:
				pkgmask = pkgmask + 1
			elif "-*" in keywords or "-"+gentoolkit.settings["ARCH"] in keywords:
				pkgmask = pkgmask + 2

			# Determining SLOT value
			slot = pkg.get_env_var("SLOT")

			if (status == 1 and opts["includeInstalled"]) or \
			   (status == 2 and opts["includePortTree"]) or \
			   (status == 3 and opts["includeOverlayTree"]):
				if Config["piping"]:
					print_info(0, pkg.get_cpv())
				else:
					print_info(0, "[" + pp.installedflag(pfxmodes[status]) + "] [" + pp.maskflag(maskmodes[pkgmask]) + "] " + pp.cpv(pkg.get_cpv()) + " (" + pp.slot(slot) + ")")
					
	def shortHelp(self):
		return pp.localoption("<local-opts> ") + pp.pkgquery("useflag") + " - list all packages with " + pp.pkgquery("useflag")
	def longHelp(self):
		return "List all packages with a particular USE flag" + \
			   "\n" + \
			   "Syntax:\n" + \
			   "  " + pp.command("list") + pp.localoption(" <local-opts> ") + pp.pkgquery("useflag") + \
			   "\n" + \
			   pp.localoption("<local-opts>") + " is either of: \n" + \
			   "  " + pp.localoption("-i, --installed") + "		 - search installed packages (default)\n" + \
			   "  " + pp.localoption("-I, --exclude-installed") + " - do not search installed packages\n" + \
			   "  " + pp.localoption("-p, --portage-tree") + "	  - also search in portage tree (" + gentoolkit.settings["PORTDIR"] + ")\n" + \
			   "  " + pp.localoption("-o, --overlay-tree") + "	  - also search in overlay tree (" + gentoolkit.settings["PORTDIR_OVERLAY"] + ")\n"

#
# Command line tokens to commands mapping
#

Known_commands = {
	"list" : CmdListPackages(),
	"files" : CmdListFiles(),
	"belongs" : CmdListBelongs(),
	"depends" : CmdListDepends(),
	"hasuse" : CmdFindUSEs(),
	"uses" : CmdDisplayUSEs(),
	"depgraph" : CmdDisplayDepGraph(),
	"changes" : CmdDisplayChanges(),
	"size" : CmdDisplaySize(),
	"check" : CmdCheckIntegrity(),
	"stats" : CmdDisplayStatistics(),
	"glsa" : CmdListGLSAs(),
	"which": CmdWhich()
	}

# Short command line tokens

Short_commands = {
	"a" : "glsa",
	"b" : "belongs",
	"c" : "changes",
	"d" : "depends",
	"f" : "files",
	"g" : "depgraph",
	"h" : "hasuse",
	"k" : "check",
	"l" : "list",
	"s" : "size",
	"t" : "stats",
	"u" : "uses",
	"w" : "which"
}

from gentoolkit import Config

Config = {
	# Query will include packages installed on the system
	"installedPackages":  1,
	# Query will include packages available for installation
	"uninstalledPackages": 0,
	# Query will include overlay packages (iff uninstalledPackages==1)
	"overlayPackages": 1,
	# Query will include masked packages (iff uninstalledPackages==1)
	"maskedPackages": 0,
	# Query will only consider packages in the following categories, empty means all.
	"categoryFilter": [],
	# Enable color output (-1 = use Portage setting, 0 = force off, 1 = force on)
	"color": -1,
	# Level of detail on the output
	"verbosityLevel": 3,
	# Query will display info for multiple SLOTed versions
	"considerDuplicates": 1,
	# Are we writing to a pipe?
	"piping": 0
}
	
def printVersion():
	"""Print the version of this tool to the console."""
	print_info(0, __productname__ + "(" + __version__ + ") - " + \
		  __description__)
	print_info(0, "Author(s): " + __author__)

def buildReverseMap(m):
	r = {}
	for x in m.keys():
		r[m[x]] = x
	return r
	
def printUsage():
	"""Print full usage information for this tool to the console."""
	
	short_cmds = buildReverseMap(Short_commands);
	
	print_info(0, pp.emph("Usage: ") + pp.productname(__productname__) + pp.globaloption(" <global-opts> ") + pp.command("command") + pp.localoption(" <local-opts>"))
	print_info(0, "where " + pp.globaloption("<global-opts>") + " is one of")
	print_info(0, pp.globaloption(" -q, --quiet") + "   - minimal output")
	print_info(0, pp.globaloption(" -C, --nocolor") + " - turn off colours")
	print_info(0, pp.globaloption(" -h, --help") + "    - this help screen")
	print_info(0, pp.globaloption(" -V, --version") + " - display version info")
	print_info(0, pp.globaloption(" -N, --no-pipe") + " - turn off pipe detection")
	
	print_info(0, "where " + pp.command("command") + "(" + pp.command("short") + ") is one of")
	keys = Known_commands.keys()
	keys.sort()
	for x in keys:
		print_info(0, " " + pp.command(x) + "(" + pp.command(short_cmds[x]) + ") " + \
			Known_commands[x].shortHelp())
	print
	
def configure():
	"""Set up default configuration.
	"""

	# Guess colour output
	if (Config["color"] == -1 and \
		((not sys.stdout.isatty()) or (gentoolkit.settings["NOCOLOR"] in ["yes","true"]))):
			pp.output.nocolor()

	# Guess piping output
	if not sys.stdout.isatty():
		Config["piping"] = True
	else:
		Config["piping"] = False
		
	
def parseArgs(args):
	"""Parse tool-specific arguments. 
	
	Arguments are on the form equery <tool-specific> [command] <command-specific>
	
	This function will only parse the <tool-specific> bit.
	"""
	command = None
	local_opts = []
	showhelp = 0

	def expand(x):
		if x in Short_commands.keys():
			return Short_commands[x]
		return x
		
	for i in xrange(len(args)):
		x = args[i]
		if 0:
			pass
		elif x in ["-h", "--help"]:
			showhelp = True
		elif x in ["-V", "--version"]:
			printVersion()
			sys.exit(0)
		elif x in ["-C", "--nocolor"]:
			Config["color"] = 0
			pp.output.nocolor()
		elif x in ["-N", "--no-pipe"]:
			Config["piping"] = False
		elif x in ["-q","--quiet"]:
			Config["verbosityLevel"] = 0
		elif expand(x) in Known_commands.keys():
			command = Known_commands[expand(x)]
			local_opts.extend(args[i+1:])
			if showhelp:
				local_opts.append("--help")
			break
		else:
			print_warn("unknown global option %s, reusing as local option" % x)
			local_opts.append(x)
		
	if not command and showhelp:
		printUsage()
		sys.exit(0)

	return (command, local_opts)
   
if __name__ == "__main__":
	configure()
	(cmd, local_opts) = parseArgs(sys.argv[1:])
	if cmd:
		try:
			cmd.perform(local_opts)
		except KeyError, e:
			if e and e[0].find("Specific key requires an operator") >= 0:
				print_error("Invalid syntax: missing operator")
				print_error("If you want only specific versions please use one of")
				print_error("the following operators as prefix for the package name:")
				print_error("   >  >=  =  <=  <")
				print_error("Example to only match gcc versions greater or equal 3.2:")
				print_error("   >=sys-devel/gcc-3.2")
				print_error("")
				print_error("Note: The symbols > and < are used for redirection in the shell")
				print_error("and must be quoted if either one is used.")

			else:
				print_error("Internal portage error, terminating")
				if len(e[0]):
					print_error(str(e))
			sys.exit(2)
		except ValueError, e:
			if isinstance(e[0], list):
				print_error("Ambiguous package name " + pp.emph("\"" + local_opts[0] + "\""))
				print_error("Please use one of the following long names:")
				for p in e[0]:
					print_error("	" + str(p))
			else:
				print_error("Internal portage error, terminating")
				if len(e[0]):
					print_error(str(e[0]))
			sys.exit(2)
		except KeyboardInterrupt:
			print_info(0, "Interrupted by user, aborting.")
	else:
		print_error("No command or unknown command given")
		printUsage()
  
