nutools/tailor.py

136 lines
4.3 KiB
Python
Executable File

#!/usr/bin/env python
# -*- coding: utf-8 mode: python -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
u"""Afficher les lignes d'un fichier en mettant en surbrillance certains patterns"""
import sys, subprocess, re
from collections import OrderedDict
def bluef(line, *ignored): return '\x1B[34m%s\x1B[0m' % line
def greenf(line, *ignored): return '\x1B[32m%s\x1B[0m' % line
def yellowf(line, *ignored): return '\x1B[33m%s\x1B[0m' % line
def redf(line, *ignored): return '\x1B[31m%s\x1B[0m' % line
def nonef(line, *ignored): return re.sub('\x1B\[.*?m', '', line)
DEFAULT_PATTERNS = OrderedDict([
(r'(?i)error', redf),
(r'(?i)warn(ing)?', yellowf),
(r'(?i)info', bluef),
(None, nonef),
])
FORMATS = OrderedDict([
('blue', bluef),
('green', greenf),
('yellow', yellowf),
('red', redf),
('none', nonef),
])
FORMAT_ALIASES = {
'b': 'blue',
'g': 'green',
'y': 'yellow',
'r': 'red',
'': 'none',
}
def strip_nl(s):
if s is None: return None
elif s.endswith("\r\n"): s = s[:-2]
elif s.endswith("\n"): s = s[:-1]
elif s.endswith("\r"): s = s[:-1]
return s
def run_tailor(inputfile=None, follow=False, patterns=None):
if inputfile is None or not follow:
if inputfile is None:
inf = sys.stdin
close = False
else:
inf = open(inputfile, 'rb')
close = True
def next_line():
try:
while True:
try: line = inf.readline()
except: break
if line == '': break
yield line
finally:
if close: inf.close()
else:
def next_line():
p = subprocess.Popen(
['tail', '-f', inputfile],
stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
while True:
try: line = p.stdout.readline()
except: break
if line == '': break
yield line
if patterns is None: patterns = DEFAULT_PATTERNS
for line in next_line():
line = nonef(strip_nl(line))
func = False
mo = None
for p, f in patterns.items():
if p is None:
func = f
mo = None
break
mo = re.search(p, line)
if mo is not None:
func = f
break
if func is not False:
line = func(line, mo)
sys.stdout.write(line)
sys.stdout.write('\n')
if __name__ == '__main__':
from argparse import ArgumentParser
AP = ArgumentParser(
usage=u"%(prog)s [-f] [INPUTFILE]",
description=__doc__,
)
AP.set_defaults(inputfile=None, follow=False, patterns=None, defaults=True)
pattern_help = u"""\
Ajouter une spécification de pattern et le format dans lequel il doit être affiché.
Le format par défaut est red.
Les formats valides sont: %(formats)s""" % {
'formats': ', '.join(FORMATS.keys()),
}
AP.add_argument('-e', '--pattern', action='append', dest='patterns', metavar='PATTERN:FORMAT',
help=pattern_help)
AP.add_argument('-z', '--no-defaults', action='store_false', dest='defaults',
help=u"Ne pas ajouter les patterns par défaut")
AP.add_argument('-f', '--follow', action='store_true', dest='follow',
help=u"Suivre le contenu du fichier spécifié")
AP.add_argument('inputfile', metavar='INPUTFILE', nargs='?',
help=u"Fichier qu'il faut afficher ou dont il faut suivre le contenu")
o = AP.parse_args()
if o.patterns is None:
patterns = DEFAULT_PATTERNS
else:
patterns = OrderedDict()
for pf in o.patterns:
mo = re.match('(.*):([a-zA-Z]*)$', pf)
if mo is not None:
p = mo.group(1)
of = mo.group(2)
else:
p = pf
of = 'red'
if p == '': p = None
f = of.lower()
f = FORMAT_ALIASES.get(f, f)
if f not in FORMATS:
raise ValueError("%s: format invalide" % of)
patterns[p] = FORMATS[f]
if o.defaults:
for p, f in DEFAULT_PATTERNS.items():
patterns.setdefault(p, f)
run_tailor(o.inputfile, o.follow, patterns)