Skip to content

Instantly share code, notes, and snippets.

@un33k
Created September 21, 2011 02:17
Show Gist options
  • Save un33k/1231041 to your computer and use it in GitHub Desktop.
Save un33k/1231041 to your computer and use it in GitHub Desktop.
Take some text and print each line in different colour based on regex input (http://code.google.com/p/c4lt/)
#!/usr/bin/python -u
#
# c4lt - simple log lines colorer filter
#
# Copyright (C) 2011 Yaroslav Pogrebnyak <[email protected]>
#
# 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/>.
#
#
# Version: 0.1
# URL: http://code.google.com/p/c4lt/
#
import re
import sys
import time
import json
import os.path
import argparse
from cStringIO import StringIO
# Constants
DEFAULT_CONFIG_PATH = os.path.dirname(__file__) + "/config/default.config"
ENCODING = 'utf8'
# Color codes
COLORS = {
'gray' : 0x0,
'red' : 0x1,
'green' : 0x2,
'yellow' : 0x3,
'blue' : 0x4,
'magenta' : 0x5,
'cyan' : 0x6,
'white' : 0x7,
}
# Flush any coloring
FLUSH = '\033[0;m'
class Markup:
'''
Representation of bg/fg color markup
'''
DEFAULT_CODE = COLORS['white']
DEFAULT_FG = True
DEFAULT_HI = 0
@staticmethod
def merge(a, b):
return a if not (a is None) else b
def __init__(self, code=None, color=None, fg=None, hi=None):
self.code = self.merge(code, COLORS.get(color))
self.f = fg
self.h = hi
def override_with(self, other):
code = self.merge(other.code, self.code)
f = self.merge(other.f, self.f)
h = self.merge(other.h, self.h)
return Markup(code, fg=f, hi=h)
def __int__(self):
return self.code
def __str__(self):
return '\033[%d;%d%dm' % ( int(self.merge(self.h, self.DEFAULT_HI)),
{True:3, False:4}[self.merge(self.f, self.DEFAULT_FG)],
self.merge(self.code, self.DEFAULT_CODE) )
def paint(line, rules):
'''
Paint line using rules
'''
# Fill color array
line_len = len(line)
color_array = bytearray(line_len)
for i, rule in enumerate(rules):
for m in re.finditer(rule['pattern'], line):
end, start = m.end(rule['group']), m.start(rule['group'])
color_array[start:end] = [i]*(end-start)
out = StringIO()
last = None
# Paint line with COLORS
for i in range(line_len-1):
if color_array[i] == last:
out.write(line[i].encode(ENCODING))
continue
out.write(FLUSH)
out.write(str(rules[color_array[i]]['markup']))
out.write(line[i])
last = color_array[i]
# write flush before newline to avoid coloring the wrong space
# end the end of the next line
out.write(FLUSH)
out.write('\n')
return out.getvalue()
def do_filter(config):
'''
Read line from stdin, coloring according to rules and
write to stdout until not stopeed
'''
(mapping, rules) = config
rules_cache = {}
def prepare_rules(color):
if not color in rules_cache:
new_rules = []
for ru in rules:
r = dict(ru)
r['markup'] = color.override_with(r['markup'])
new_rules += [r]
rules_cache[color] = new_rules
return rules_cache[color]
while True:
out_line = line = sys.stdin.readline().decode(ENCODING)
if line == '': break
for level, color in mapping.items():
if level in line:
try:
out_line = paint(line, prepare_rules(color))
break
except Exception, e:
sys.stdout.write(FLUSH) # flush COLORS before die
sys.stderr.write(str(e))
break
sys.stdout.write(out_line)
def load_config(filename):
'''
Loads color mapping and rules from a config file
'''
def strkeys(d):
''' Convert dict keys to strings '''
return dict([(str(k), v) for k,v in d.items()])
cfg = {}
try:
with open(filename) as f:
# Remove # comments
data = [re.sub('#.*', '', l) for l in f.readlines()]
cfg = json.loads("".join(data) )
except Exception, e:
print "Configuration load error: ", e
exit() #return {}, []
# load logging level -> color mapping
mapping = {}
for level, markup in cfg['levels'].items():
mapping[level] = Markup(**strkeys(markup))
# load regexp coloring rules
rules = []
for rule in cfg['rules']:
rules += [{
'pattern' : rule['pattern'],
'group' : rule['group'],
'markup' : Markup(**strkeys(rule.get('markup', {})))
}]
return (mapping, rules)
if __name__ == '__main__':
'''
Parse parameters and run
'''
parser = argparse.ArgumentParser(description='Colorize log stream')
parser.add_argument('--config', default=DEFAULT_CONFIG_PATH, help='configuration file')
args = parser.parse_args()
try:
config = load_config(args.config)
do_filter(config)
except IOError, e:
print e
except KeyboardInterrupt, e:
print "Bye!"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment