File:  [Ghantoos CVS Repository] / yum-parser / yum-parser.py
Revision 1.7: download - view: text, annotated - select for diffs - revision graph
Mon Sep 29 16:30:44 2008 UTC (23 months, 1 week ago) by ghantoos
Branches: MAIN
CVS tags: HEAD
Corrected minor bug (update/yumfood)

#!/usr/bin/env python
#
#    YUM output parser
#    -main-
#
#    $Id: yum-parser.py,v 1.7 2008/09/29 16:30:44 ghantoos Exp $
#
#    "Copyright 2008 Ignace Mouzannar ( http://ghantoos.org )"
#    Email: ghantoos@ghantoos.org
#
#    This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation, either version 3 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.


import sys
import re
import os
import time
import getopt
import threading
import popen2
import string

regexp_dependency = '^Transaction Summary'
regexp_overview = '^(Install|Update|Remove)(.*)Package\(s\)'
regexp_download = '^Running Transaction$'
regexp_process = 'Installing|Erasing|Updating' 
regexp_nothingtodo = '^Nothing to do'
regexp_noremoval = '^No Packages marked for (removal|Update)'
regexp_unknown = '^No package.*available.'

logfile = 'yum-parser.log'

class progressBar:
	# Source: http://code.activestate.com/recipes/168639/#c14
	# Licence: MIT (http://www.opensource.org/licenses/mit-license.php)
	""" Creates a text-based progress bar. Call the object with the `print'
		command to see the progress bar, which looks something like this:

		[=======>		22%				  ]

		You may specify the progress bar's width, min and max values on init.
	"""

	def __init__(self, minValue = 0, maxValue = 100, totalWidth=80):
		self.progBar = "[]"   # This holds the progress bar string
		self.min = minValue
		self.max = maxValue
		self.span = maxValue - minValue
		self.width = totalWidth
		self.amount = 0	   # When amount == max, we are 100% done
		self.updateAmount(0)  # Build progress bar string

	def updateAmount(self, newAmount = 0):
		""" Update the progress bar with the new amount (with min and max
			values set at initialization; if it is over or under, it takes the
			min or max value as a default. """
		if newAmount < self.min: newAmount = self.min
		if newAmount > self.max: newAmount = self.max
		self.amount = newAmount

		# Figure out the new percent done, round to an integer
		diffFromMin = float(self.amount - self.min)
		percentDone = (diffFromMin / float(self.span)) * 100.0
		percentDone = int(round(percentDone))

		# Figure out how many hash bars the percentage should be
		allFull = self.width - 2
		numHashes = (percentDone / 100.0) * allFull
		numHashes = int(round(numHashes))

		# Build a progress bar with an arrow of equal signs; special cases for
		# empty and full
		if numHashes == 0:
			self.progBar = "[>%s]" % (' '*(allFull-1))
		elif numHashes == allFull:
			self.progBar = "[%s]" % ('='*allFull)
		else:
			self.progBar = "[%s>%s]" % ('='*(numHashes-1),
										' '*(allFull-numHashes))

		# figure out where to put the percentage, roughly centered
		percentPlace = (len(self.progBar) / 2) - len(str(percentDone))
		percentString = str(percentDone) + "%"

		# slice the percentage into the bar
		self.progBar = ''.join([self.progBar[0:percentPlace], percentString,
								self.progBar[percentPlace+len(percentString):]
								])

	def __str__(self):
		return str(self.progBar)

	def printprogress(self, value):
		""" Updates the amount, and writes to stdout. Prints a carriage return
			first, so it will overwrite the current line in stdout."""
		print '\r',
		self.updateAmount(value)
		sys.stdout.write(str(self))
		sys.stdout.flush()

class downloadthread(threading.Thread):
	def __init__(self, regexp_download, line, patience, loop, phase, thepatience):
		threading.Thread.__init__(self)
		self.stop_event = threading.Event()
		self.regexp_download = regexp_download
		self.line = line
		self.patience = patience
		self.loop = loop
		self.phase = phase
		self.thepatience = thepatience
	def stop(self):
		self.stop_event.set()
		self.join()

	def run(self):
		while not self.stop_event.isSet():
			time.sleep(.1)
			self.loop, self.phase = self.thepatience(self.regexp_download, self.line, self.loop, self.phase)

class yumparser:
	def __init__(self, arguments):
		self.patiencepattern = ['\\','|','/','-']
		#self.patiencepattern = ['=   ',' =  ','  = ','   =','  = ',' =  ']
		self.patiencetoprint = 'This may take a few minutes. [' + self.patiencepattern[0] + ']'
		self.barwidth = self.term_width()

		yumfood, args = self.getoptions(arguments)
		self.checksu()
		yumcmd = 'yum -y ' + yumfood + ' 2>&1'
		self.log_line(yumcmd+'\n',logfile)
		outt, inn = popen2.popen2(yumcmd)
		return self.parsermain(outt,yumfood.split()[0].capitalize(),args)

	def usage(self):
		sys.stdout.write("""yum-parser is used to parse yum's output.
Usage: 
	$ yum-parser.py (-u | --update)
	$ yum-parser.py (-i | --install) package_list
	$ yum-parser.py (-r | --remove) package_list
	$ yum-parser.py (-s | --search) 'regexp'
	$ yum-parser.py (-h | --help) package_list
""")
		sys.exit(0)

	def getoptions(self, arguments):
		try:
			optlist, args = getopt.getopt(arguments, 'uirhs',['update','install','remove','help','search'])
		except getopt.GetoptError:
			self.usage()

		if len(optlist) is not 1:
			self.usage()
		for option, value in optlist:
			if option in ['-u', '--update']:
				yumfood = 'update' + string.join(args, ' ' )
			elif option in ['-i', '--install']:
				yumfood = 'install ' + string.join(args, ' ' )
			elif option in ['-r', '--remove']:
				yumfood = 'remove ' + string.join(args, ' ' )
			elif option in ['-s', '--search']:
				yumfood = 'list'
			elif option in ['-h', '--help']:
				self.usage()
		return yumfood, args

	def log_line(self,line,logfile):
		if logfile:
			if os.path.isfile(logfile) == False:
				open(logfile, 'w').close() 
			log = open(logfile,'a')
			log.write(line)
			log.close()

	def parsermain(self,stream,actiontype,arguments):
		actions = {}
		processed = 0
		phase = 0
		loop = 0
		found = False

		if not arguments:
			if actiontype in ['Update']: 
				arguments.append('all')
			else: 
				usage()

		if actiontype == 'List':
			if len(arguments) == 1:
				sys.stdout.write('* Searching for packages in pattern %s\n' % arguments)
			else: self.usage()
		else:
			sys.stdout.write('* Performing ' + actiontype.lower() + ' of following package(s) and dependencies: %s\n\n' % arguments)
			sys.stdout.write('* Calculationg dependencies. ' + self.patiencetoprint )

		while 1:
			line = stream.readline()

			self.log_line(line,logfile)

			if not line:
				if phase > 2:
					sys.stdout.write('\n\n' + actiontype + ' Done. \n')
				elif actiontype == 'List':
					if found is False:
						sys.stdout.write('No packages found matching pattern %s\n' %arguments)
				break

			if actiontype == 'List':
				if re.search(arguments[0],line):
					found = True
					sys.stdout.write(line)

			elif re.search(regexp_nothingtodo,line) and phase < 1:
				loop, phase = self.thepatience(regexp_nothingtodo, line, loop, phase)
				sys.stdout.write('Nothing to do.\n')
				phase =+ 1

			elif re.search(regexp_unknown,line):
				loop, phase = self.thepatience(regexp_unknown, line, loop, phase)
				sys.stdout.write('Unknown package request.\n')
				phase += 1

			elif re.search(regexp_noremoval,line):
				loop, phase = self.thepatience(regexp_noremoval, line, loop, phase)
				if actiontype == 'Update':
					sys.stdout.write('No updates available.\n')
				else:
					sys.stdout.write('Requested package(s) is not installed.\n')
				phase =+ 1

			elif phase == 0:
				loop, phase = self.thepatience(regexp_dependency, line, loop, phase)

		
			elif re.match(regexp_overview, line):
				(what, number) = re.match(regexp_overview, line).groups()
				actions[what]=int(number)
				phase += 1

			elif phase == 4:
				phase += 1
				sys.stdout.write('* This upgrade will install %d, update %d and remove %d packages\n\n' %(actions['Install'],actions['Update'],actions['Remove']))
				if actiontype in ['Install', 'Update']:
					sys.stdout.write('* Downloading packages. ' + self.patiencetoprint )
				total_transaction = actions['Install'] + actions['Update'] + actions['Remove']
				bar = progressBar(processed, total_transaction, self.barwidth)
				oldbar = str(bar)

			elif phase == 6:
				if re.search(regexp_download,line):
					if actiontype in ['Install', 'Update']:
						downpatience.stop()
						loop, phase = self.thepatience(regexp_download, line, loop, phase)
					elif actiontype in ['Remove']: 
						phase += 1

			elif phase == 5:
				phase += 1
				if actiontype in ['Install', 'Update']:
					downpatience = downloadthread(regexp_download, line, self.patiencepattern, loop, phase, self.thepatience)
					downpatience.start()
				
			elif phase == 7:
				if re.search(regexp_process, line):
					processed += 1
					if processed == 1:
						sys.stdout.write('* Starting operations:\n\n')
					bar.printprogress(processed)

	def thepatience(self,regexp, line, loop, phase):
		if not re.search(regexp, line):
			loop += 1
			sys.stdout.write('\b' * (len(self.patiencepattern[0])+1))
			sys.stdout.write(self.patiencepattern[loop%len(self.patiencepattern)]+']')
			sys.stdout.flush()
		else: 
			phase += 1
			sys.stdout.write('\b' * len(self.patiencetoprint))
			sys.stdout.write('Done.' + ' ' * (len(self.patiencetoprint)-5))
			sys.stdout.write('\n' * 2)
		return loop, phase

	def term_width(self):
		""" Calculates the width of the running terminal """
		fp = os.popen('stty size', 'r')
		termwidth = eval(fp.readline().split()[1])
		fp.close()
		return termwidth

	def checksu(self):
		if os.getuid():
			print "You need to be root to use this command."
			sys.exit(0)

def main(arguments):
	pass

if __name__ == "__main__":

	try:
		parser = yumparser(sys.argv[1:])
	except KeyboardInterrupt:
		sys.stdout.write('\nExited on user request\n')
		






FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>