#!/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>