Skip to content

Instantly share code, notes, and snippets.

@lilydjwg
Last active February 4, 2020 12:24
Show Gist options
  • Save lilydjwg/9891346 to your computer and use it in GitHub Desktop.
Save lilydjwg/9891346 to your computer and use it in GitHub Desktop.
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()
@lilydjwg
Copy link
Author

lilydjwg commented Mar 31, 2014

screenshot

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment