mirror of
https://github.com/garabik/grc.git
synced 2024-10-28 17:41:01 +01:00
313 lines
11 KiB
Python
Executable File
313 lines
11 KiB
Python
Executable File
#! /usr/bin/env python3
|
|
|
|
from __future__ import print_function
|
|
|
|
import sys, os, string, re, signal, errno
|
|
|
|
|
|
# redefine readline to pass invalidly encoded characters unchanged, if possible
|
|
if hasattr(sys.stdin, 'reconfigure') and hasattr(sys.stdout, 'reconfigure'):
|
|
# at least python3.7
|
|
sys.stdin.reconfigure(errors='surrogateescape')
|
|
sys.stdout.reconfigure(errors='surrogateescape')
|
|
myreadline = sys.stdin.readline
|
|
myprint = print
|
|
else:
|
|
if hasattr(sys.stdin, 'buffer'):
|
|
# python3
|
|
buffer_reader = sys.stdin.buffer
|
|
else:
|
|
buffer_reader = sys.stdin
|
|
def myreadline():
|
|
for line in buffer_reader:
|
|
try:
|
|
decoded = line.decode('utf-8', errors='surrogateescape')
|
|
except (UnicodeDecodeError, LookupError):
|
|
decoded = line.decode('utf-8', errors='ignore')
|
|
return decoded
|
|
return ''
|
|
def myprint(x):
|
|
try:
|
|
print(x)
|
|
except UnicodeEncodeError:
|
|
print(x.encode('utf-8', errors='replace').decode('utf-8'))
|
|
|
|
#some default definitions
|
|
colours = {
|
|
'none' : "",
|
|
'default' : "\033[0m",
|
|
'bold' : "\033[1m",
|
|
'underline' : "\033[4m",
|
|
'blink' : "\033[5m",
|
|
'reverse' : "\033[7m",
|
|
'concealed' : "\033[8m",
|
|
|
|
'black' : "\033[30m",
|
|
'red' : "\033[31m",
|
|
'green' : "\033[32m",
|
|
'yellow' : "\033[33m",
|
|
'blue' : "\033[34m",
|
|
'magenta' : "\033[35m",
|
|
'cyan' : "\033[36m",
|
|
'white' : "\033[37m",
|
|
|
|
'on_black' : "\033[40m",
|
|
'on_red' : "\033[41m",
|
|
'on_green' : "\033[42m",
|
|
'on_yellow' : "\033[43m",
|
|
'on_blue' : "\033[44m",
|
|
'on_magenta' : "\033[45m",
|
|
'on_cyan' : "\033[46m",
|
|
'on_white' : "\033[47m",
|
|
|
|
'beep' : "\007",
|
|
'previous' : "prev",
|
|
'unchanged' : "unchanged",
|
|
|
|
# non-standard attributes, supported by some terminals
|
|
'dark' : "\033[2m",
|
|
'italic' : "\033[3m",
|
|
'rapidblink' : "\033[6m",
|
|
'strikethrough': "\033[9m",
|
|
|
|
# aixterm bright color codes
|
|
# prefixed with standard ANSI codes for graceful failure
|
|
'bright_black' : "\033[30;90m",
|
|
'bright_red' : "\033[31;91m",
|
|
'bright_green' : "\033[32;92m",
|
|
'bright_yellow' : "\033[33;93m",
|
|
'bright_blue' : "\033[34;94m",
|
|
'bright_magenta' : "\033[35;95m",
|
|
'bright_cyan' : "\033[36;96m",
|
|
'bright_white' : "\033[37;97m",
|
|
|
|
'on_bright_black' : "\033[40;100m",
|
|
'on_bright_red' : "\033[41;101m",
|
|
'on_bright_green' : "\033[42;102m",
|
|
'on_bright_yellow' : "\033[43;103m",
|
|
'on_bright_blue' : "\033[44;104m",
|
|
'on_bright_magenta' : "\033[45;105m",
|
|
'on_bright_cyan' : "\033[46;106m",
|
|
'on_bright_white' : "\033[47;107m",
|
|
}
|
|
|
|
|
|
# ignore ctrl C - this is not ideal for standalone grcat, but
|
|
# enables propagating SIGINT to the other subprocess in grc
|
|
signal.signal(signal.SIGINT, signal.SIG_IGN)
|
|
|
|
def add2list(clist, m, patterncolour):
|
|
for group in range(0, len(m.groups()) +1):
|
|
if group < len(patterncolour):
|
|
clist.append((m.start(group), m.end(group), patterncolour[group]))
|
|
else:
|
|
clist.append((m.start(group), m.end(group), patterncolour[0]))
|
|
|
|
def get_colour(x):
|
|
if x in colours:
|
|
return colours[x]
|
|
elif len(x)>=2 and x[0]=='"' and x[-1]=='"':
|
|
return eval(x)
|
|
else:
|
|
raise ValueError('Bad colour specified: '+x)
|
|
|
|
|
|
home = []
|
|
conffile = None
|
|
xdg_config = os.environ.get('XDG_CONFIG_HOME')
|
|
xdg_data = os.environ.get('XDG_DATA_HOME')
|
|
home = os.environ.get('HOME')
|
|
if home and not xdg_config:
|
|
xdg_config = home + '/.config'
|
|
if home and not xdg_data:
|
|
xdg_data = home + '/.local/share'
|
|
|
|
conffilepath = [""]
|
|
if xdg_data:
|
|
conffilepath += [xdg_data + '/grc/']
|
|
if xdg_config:
|
|
conffilepath += [xdg_config + '/grc/']
|
|
if home:
|
|
conffilepath += [home + '/.grc/']
|
|
conffilepath += ['/usr/local/share/grc/', '/usr/share/grc/']
|
|
if len(sys.argv) != 2:
|
|
sys.stderr.write("You are not supposed to call grcat directly, but the usage is: grcat conffile\n")
|
|
sys.exit(1)
|
|
|
|
conffile_arg = sys.argv[1] # tentative conffile
|
|
for i in conffilepath:
|
|
# test if conffile exists, it can be also a pipe
|
|
if os.path.exists(i+conffile_arg) and not os.path.isdir(i+conffile_arg):
|
|
conffile = i+conffile_arg
|
|
break
|
|
|
|
if not conffile:
|
|
sys.stderr.write("config file [%s] not found\n" % sys.argv[1])
|
|
sys.exit(1)
|
|
|
|
regexplist = []
|
|
|
|
f = open(conffile, "r")
|
|
is_last = 0
|
|
split = str.split
|
|
lower = str.lower
|
|
letters = string.ascii_letters
|
|
while not is_last:
|
|
ll = {'count':"more"}
|
|
while 1:
|
|
l = f.readline()
|
|
if l == "":
|
|
is_last = 1
|
|
break
|
|
if l[0] == "#" or l[0] == '\012':
|
|
continue
|
|
if not l[0] in letters:
|
|
break
|
|
fields = split(l.rstrip('\r\n'), "=", 1)
|
|
if len(fields) != 2:
|
|
sys.stderr.write('Error in configuration, I expect keyword=value line\n')
|
|
sys.stderr.write('But I got instead:\n')
|
|
sys.stderr.write(repr(l))
|
|
sys.stderr.write('\n')
|
|
sys.exit(1)
|
|
keyword, value = fields
|
|
keyword = lower(keyword)
|
|
if keyword in ('colors', 'colour', 'color'):
|
|
keyword = 'colours'
|
|
if not keyword in ["regexp", "colours", "count", "command", "skip", "replace", "concat"]:
|
|
raise ValueError("Invalid keyword")
|
|
ll[keyword] = value
|
|
|
|
# Split string into one string per regex group
|
|
# e.g. split "brown bold, red" into "brown bold" and
|
|
# "red"
|
|
#colstrings = []
|
|
#for colgroup in split(ll['colours'], ','):
|
|
# colourlist = split(colgroup)
|
|
# c = ""
|
|
# for i in colourlist :
|
|
# c = c + colours[i]
|
|
# colstrings.append(c)
|
|
# do not try to understand the optimized form below :-)
|
|
if 'colours' in ll:
|
|
colstrings = list(
|
|
[''.join([get_colour(x) for x in split(colgroup)]) for colgroup in split(ll['colours'], ',')]
|
|
)
|
|
ll['colours'] = colstrings
|
|
|
|
cs = ll['count']
|
|
if 'regexp' in ll:
|
|
ll['regexp'] = re.compile(ll['regexp']).search
|
|
regexplist.append(ll)
|
|
|
|
prevcolour = colours['default']
|
|
prevcount = "more"
|
|
blockflag = 0
|
|
|
|
while 1:
|
|
line = myreadline()
|
|
if line == "" :
|
|
break
|
|
if line[-1] in '\r\n':
|
|
line = line[:-1]
|
|
clist = []
|
|
skip = 0
|
|
for pattern in regexplist:
|
|
pos = 0
|
|
currcount = pattern['count']
|
|
was_replace = 0 # watch replacements, replace regexp only one per line, to avoid infinte loops if the replacement matches the regexp again
|
|
while 1:
|
|
m = pattern['regexp'](line, pos)
|
|
if m:
|
|
if 'replace' in pattern:
|
|
if was_replace:
|
|
break
|
|
line = re.sub(m.re, pattern['replace'], line)
|
|
was_replace = 1
|
|
if 'colours' in pattern:
|
|
if currcount == "block":
|
|
blockflag = 1
|
|
blockcolour = pattern['colours'][0]
|
|
currcount = "stop"
|
|
break
|
|
elif currcount == "unblock":
|
|
blockflag = 0
|
|
blockcolour = colours['default']
|
|
currcount = "stop"
|
|
add2list(clist, m, pattern['colours'])
|
|
if currcount == "previous":
|
|
currcount = prevcount
|
|
if currcount == "stop":
|
|
break
|
|
if currcount == "more":
|
|
prevcount = "more"
|
|
newpos = m.end(0)
|
|
# special case, if the regexp matched but did not consume anything,
|
|
# advance the position by 1 to escape endless loop
|
|
if newpos == pos:
|
|
pos += 1
|
|
else:
|
|
pos = newpos
|
|
else:
|
|
prevcount = "once"
|
|
pos = len(line)
|
|
if 'concat' in pattern:
|
|
with open(pattern['concat'], 'a') as f :
|
|
f.write(line + '\n')
|
|
if 'colours' not in pattern:
|
|
break
|
|
if 'command' in pattern:
|
|
os.system(pattern['command'])
|
|
if 'colours' not in pattern:
|
|
break
|
|
if 'skip' in pattern:
|
|
skip = pattern['skip'] in ("yes", "1", "true")
|
|
if 'colours' not in pattern:
|
|
break
|
|
else:
|
|
break
|
|
if m and currcount == "stop":
|
|
prevcount = "stop"
|
|
break
|
|
if len(clist) == 0:
|
|
prevcolour = colours['default']
|
|
first_char = 0
|
|
last_char = 0
|
|
length_line = len(line)
|
|
if blockflag == 0:
|
|
cline = (length_line+1)*[colours['default']]
|
|
for i in clist:
|
|
# each position in the string has its own colour
|
|
if i[2] == "prev":
|
|
cline[i[0]:i[1]] = [colours['default']+prevcolour]*(i[1]-i[0])
|
|
elif i[2] != "unchanged":
|
|
cline[i[0]:i[1]] = [colours['default']+i[2]]*(i[1]-i[0])
|
|
if i[0] == 0:
|
|
first_char = 1
|
|
if i[2] != "prev":
|
|
prevcolour = i[2]
|
|
if i[1] == length_line:
|
|
last_char = 1
|
|
if first_char == 0 or last_char == 0:
|
|
prevcolour = colours['default']
|
|
else:
|
|
cline = (length_line+1)*[blockcolour]
|
|
nline = ""
|
|
clineprev = ""
|
|
if not skip:
|
|
for i in range(len(line)):
|
|
if cline[i] == clineprev:
|
|
nline = nline + line[i]
|
|
else:
|
|
nline = nline + cline[i] + line[i]
|
|
clineprev = cline[i]
|
|
nline = nline + colours['default']
|
|
try:
|
|
myprint(nline)
|
|
except IOError as e:
|
|
if e.errno == errno.EPIPE:
|
|
break
|
|
else:
|
|
raise
|
|
|