lolcat.py: make rainbows over text
#!/usr/bin/env python3 | |
# inspired by https://github.com/busyloop/lolcat | |
import sys | |
import re | |
import os | |
from math import ceil | |
from colorsys import hsv_to_rgb | |
from unicodedata import east_asian_width | |
try: | |
# in https://github.com/lilydjwg/winterpy | |
from colorfinder import hex2term_accurate as hex2term | |
except ImportError: | |
def hex2term(c): | |
red, green, blue = (int(x, 16) for x in (c[1:3], c[3:5], c[5:7])) | |
# from ruby-paint | |
gray_possible = True | |
sep = 42.5 | |
gray = False | |
while gray_possible: | |
if red < sep or green < sep or blue < sep: | |
gray = red < sep and green < sep and blue < sep | |
gray_possible = False | |
sep += 42.5 | |
if gray: | |
return 232 + (red + green + blue) // 33 | |
else: | |
return 16 + sum(6 * x // 256 * 6 ** i | |
for i, x in enumerate((blue, green, red))) | |
def get_terminal_size(fd=1): | |
""" | |
Returns height and width of current terminal. First tries to get | |
size via termios.TIOCGWINSZ, then from environment. Defaults to 25 | |
lines x 80 columns if both methods fail. | |
:param fd: file descriptor (default: 1=stdout) | |
from: http://blog.taz.net.au/2012/04/09/getting-the-terminal-size-in-python/ | |
""" | |
try: | |
import fcntl, termios, struct | |
hw = struct.unpack('hh', fcntl.ioctl(fd, termios.TIOCGWINSZ, '1234')) | |
except Exception: | |
try: | |
hw = (os.environ['LINES'], os.environ['COLUMNS']) | |
except Exception: | |
hw = (25, 80) | |
return hw | |
COLOR_CODE_RE = re.compile(r'\x1b\[(?:\d*)(?:;\d+)*[mK]') | |
init_width = 0 | |
def rainbow(freq, i): | |
h = i * freq | |
r, g, b = (int(round(x * 255)) for x in hsv_to_rgb(h, 1, 1)) | |
return "#%02X%02X%02X" % (r, g, b) | |
def colorline(line, lineno=0, *, width=0): | |
global init_width | |
term_w = get_terminal_size(2)[1] | |
if not width: | |
if not init_width: | |
init_width = term_w - 1 | |
width = init_width / 2 | |
freq = 1 / width | |
col = 0 | |
line = COLOR_CODE_RE.sub('', line) | |
for c in line: | |
sys.stdout.write(colored(c, rainbow(freq, col+lineno))) | |
if east_asian_width(c) in 'WF': | |
col += 2 | |
else: | |
col += 1 | |
sys.stdout.write('\x1b[0m') # reset | |
return ceil(col / term_w) | |
def colored(ch, color): | |
c = hex2term(color) | |
return '\x1b[01;0;38;5;{c}m{ch}'.format(c=c, ch=ch) | |
if __name__ == '__main__': | |
import argparse | |
import signal | |
parser = argparse.ArgumentParser(description='Okay, no unicorns. But rainbows! In Python.') | |
parser.add_argument('-w', '--width', type=int, metavar='N', | |
help='rainbow width. default: half of terminal width-1') | |
parser.add_argument('-i', '--ignore-interrupts', action='store_true', | |
help='ignore interrupt signals') | |
parser.add_argument('files', nargs='*', metavar='FILE', | |
type=argparse.FileType('r'), | |
help='files to cat. default: stdin') | |
args = parser.parse_args() | |
if args.ignore_interrupts: | |
signal.signal(signal.SIGINT, signal.SIG_IGN) | |
files = args.files or [sys.stdin] | |
try: | |
i = 0 | |
for f in files: | |
for line in f: | |
inc = colorline(line, i, width=args.width) | |
i += inc | |
except KeyboardInterrupt: | |
print() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This comment has been minimized.