Created
September 21, 2011 02:17
-
-
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/)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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