#!/usr/bin/env python import sys,os from datetime import datetime import re,signal import subprocess VERSION=1 W= '30' R= '31' G= '32' Y= '33' B= '34' M= '35' C= '36' S= '1' E= '0' BR= '41' CLR = '\033[2J' SAVE = '\033[s' LOAD = '\033[u' CLRLN = '\033[K' CLRBLN = '\033[1K' DOWN = '\033[1B' def setup_options(): ''' Setup the command line options ''' from argparse import ArgumentParser import argparse parser=ArgumentParser(description=''' Tool to clean up and colorize the output of Anduril. Example: anduril run yourscript.and | %(prog)s You can tap in to an existing log with: tail -f -n +0 log/_global | %(prog)s''',formatter_class=argparse.RawTextHelpFormatter) parser.add_argument("--time-stamp",'-t',action="store_true",dest="timestamp",default=False, help="Print a timestamp on each line") parser.add_argument("--no-colors",'--nc',action="store_false",dest="colors",default=True, help="Disable colored output") parser.add_argument("logfile",type=str,action="store",default="",nargs="?", help="Log file to read, uses stdin automatically") parser.add_argument("--version",action='version', version=VERSION) options=parser.parse_args() return options def c(attribs): ''' ANSI colorizer ''' return '\033['+';'.join(attribs)+'m' def pos(y,x): ''' ANSI absolute position set ''' return "\033["+str(y)+";"+str(x)+"H" color_match={#'line_ends':(re.compile('$'),c.END), 'err':(re.compile('(Failed)'),c([R,S])+'\\1'+c([E])), 'done':(re.compile('(Done)'),c([G,S])+'\\1'+c([E])), } def colorize(string): ''' colorizes a string based on color_match ''' if not options.colors: return string for c in color_match: string=color_match[c][0].sub(color_match[c][1],string) return string def count_running(string, stats): ''' Counts the running executions ''' spl=[i.strip() for i in string.split('|')] if spl[3] in stats['files']: index=stats['files'].index(spl[3]) stats['running'][index]=spl else: stats['running'].append(spl) stats['running'].sort(key=lambda x: x[3]) stats['files']=[i[3] for i in stats['running']] return stats class EndProgram( Exception ): ''' Nice way of exiting the program ''' pass def remove_running(stats): ''' Remove list item matching to string ''' if not stats['running']: return stats if len(stats['running'])>10: for e in enumerate(stats['running']): if e[1][2]=='Done': stats['running'].pop(e[0]) stats['files'].pop(e[0]) return stats def print_stats(stats): ''' Prints logged errors, and the status line ''' sys.stdout.write(SAVE) for e in range(2): sys.stdout.write(pos(e+1,0)+CLRLN) sys.stdout.write(pos(e+1,0)+"="*20+" "+human_time()+CLRLN) if (stats['running']): sys.stdout.write(pos(e+2,0)+"Last update: "+stats['running'][0][0]+CLRLN) else: return for ex in enumerate(stats['running']): sys.stdout.write(pos(e+3+ex[0],0)+'('+str(ex[0]+1)+') '+' '.join([(ex[1][1]),colorize(ex[1][2]),ex[1][3]])+CLRLN) for i in range(3): sys.stdout.write(pos(e+4+ex[0],0)+CLRLN) sys.stdout.write(DOWN+CLRBLN+CLRLN) sys.stdout.write(LOAD) def human_time(): t=datetime.now().strftime("%I:%M:%S %p") return t def readinput(lf): try: line = lf.stdout.readline() #line=lf.readline() return line except: return "CleanerTimeout" options=setup_options() stats={'time':datetime.now(), 'running':[], 'files':[]} proc = subprocess.Popen(['aerofs-sh','transfers'],stdout=subprocess.PIPE) sys.stdout.write(CLR) while 1: try: sys.stdout.flush() # set a 3 second timeout for the line read. signal.signal(signal.SIGALRM, readinput) signal.alarm(3) line=readinput(proc) if not line: raise EndProgram # timeout returns a special string, in this case we re-read if line=="CleanerTimeout": print_stats(stats) continue stats=count_running(line,stats) # store only maximum number of error lines # if line empty, read next if line.strip()=="": continue print_stats(stats) stats=remove_running(stats) except EndProgram,KeyboardInterrupt: sys.stdout.flush() sys.exit(0)