diff --git a/nerdy/Dockerfile b/nerdy/Dockerfile new file mode 100644 index 0000000..279667b --- /dev/null +++ b/nerdy/Dockerfile @@ -0,0 +1,21 @@ +FROM debian:jessie +MAINTAINER Jessica Frazelle + +RUN apt-get update && apt-get install -y \ + ca-certificates \ + cowsay \ + curl \ + figlet \ + imagemagick \ + jp2a \ + python \ + --no-install-recommends + +ENV HOME /root +WORKDIR $HOME + +COPY ./lolcat /usr/bin/lolcat + +RUN echo 'image_me() { convert $1 jpg:- | jp2a ${*:2} -; }' >> $HOME/.bashrc + +ENTRYPOINT [ "bash" ] diff --git a/nerdy/lolcat b/nerdy/lolcat new file mode 100755 index 0000000..3d69612 --- /dev/null +++ b/nerdy/lolcat @@ -0,0 +1,181 @@ +#!/usr/bin/env python + +import atexit +import math +import os +import random +import re +import sys +import time + + +# Reset terminal colors at exit +def reset(): + sys.stdout.write('\x1b[0m') + sys.stdout.flush() + +atexit.register(reset) + + +STRIP_ANSI = re.compile(r'\x1b\[(\d+)(;\d+)?(;\d+)?[m|K]') +COLOR_ANSI = ( + (0x00, 0x00, 0x00), (0xcd, 0x00, 0x00), + (0x00, 0xcd, 0x00), (0xcd, 0xcd, 0x00), + (0x00, 0x00, 0xee), (0xcd, 0x00, 0xcd), + (0x00, 0xcd, 0xcd), (0xe5, 0xe5, 0xe5), + (0x7f, 0x7f, 0x7f), (0xff, 0x00, 0x00), + (0x00, 0xff, 0x00), (0xff, 0xff, 0x00), + (0x5c, 0x5c, 0xff), (0xff, 0x00, 0xff), + (0x00, 0xff, 0xff), (0xff, 0xff, 0xff), +) + +class LolCat(object): + def __init__(self, mode=256, output=sys.stdout): + self.mode = mode + self.output = output + + def _distance(self, rgb1, rgb2): + return sum(map(lambda c: (c[0] - c[1]) ** 2, + zip(rgb1, rgb2))) + + def ansi(self, rgb): + r, g, b = rgb + + if self.mode in (8, 16): + colors = COLOR_ANSI[:self.mode] + matches = [(self._distance(c, map(int, rgb)), i) for i, c in enumerate(colors)] + matches.sort() + color = matches[0][1] + + return '3%d' % (color,) + else: + gray_possible = True + sep = 2.5 + + while gray_possible: + if r < sep or g < sep or b < sep: + gray = r < sep and g < sep and b < sep + gray_possible = False + + sep += 42.5 + + if gray: + color = 232 + int(float(sum(rgb) / 33.0)) + else: + color = sum([16]+[int(6 * float(val)/256) * mod + for val, mod in zip(rgb, [36, 6, 1])]) + + return '38;5;%d' % (color,) + + def wrap(self, *codes): + return '\x1b[%sm' % (''.join(codes),) + + def rainbow(self, freq, i): + r = math.sin(freq * i) * 127 + 128 + g = math.sin(freq * i + 2 * math.pi / 3) * 127 + 128 + b = math.sin(freq * i + 4 * math.pi / 3) * 127 + 128 + return [r, g, b] + + def cat(self, fd, options): + if options.animate: + self.output.write('\x1b[?25l') + + for line in fd: + options.os += 1 + self.println(line, options) + + if options.animate: + self.output.write('\x1b[?25h') + + def println(self, s, options): + s = s.rstrip() + if options.force or self.output.isatty(): + s = STRIP_ANSI.sub('', s) + + if options.animate: + self.println_ani(s, options) + else: + self.println_plain(s, options) + + self.output.write('\n') + self.output.flush() + + def println_ani(self, s, options): + if not s: + return + + for i in xrange(1, options.duration): + self.output.write('\x1b[%dD' % (len(s),)) + self.output.flush() + options.os += options.spread + self.println_plain(s, options) + time.sleep(1.0 / options.speed) + + def println_plain(self, s, options): + for i, c in enumerate(s): + rgb = self.rainbow(options.freq, options.os + i / options.spread) + self.output.write(''.join([ + self.wrap(self.ansi(rgb)), + c, + ])) + + +def detect_mode(term_hint='xterm-256color'): + ''' + Poor-mans color mode detection. + ''' + if 'ANSICON' in os.environ: + return 16 + elif os.environ.get('ConEmuANSI', 'OFF') == 'ON': + return 256 + else: + term = os.environ.get('TERM', term_hint) + if term.endswith('-256color') or term in ('xterm', 'screen'): + return 256 + elif term.endswith('-color') or term in ('rxvt',): + return 16 + else: + return 256 # optimistic default + + +def run(): + import optparse + + parser = optparse.OptionParser(usage=r'%prog [] [file ...]') + parser.add_option('-p', '--spread', type='float', default=3.0, + help='Rainbow spread') + parser.add_option('-F', '--freq', type='float', default=0.1, + help='Rainbow frequency') + parser.add_option('-S', '--seed', type='int', default=0, + help='Rainbow seed') + parser.add_option('-a', '--animate', action='store_true', default=False, + help='Enable psychedelics') + parser.add_option('-d', '--duration', type='int', default=12, + help='Animation duration') + parser.add_option('-s', '--speed', type='float', default=20.0, + help='Animation speed') + parser.add_option('-f', '--force', action='store_true', default=False, + help='Force colour even when stdout is not a tty') + + parser.add_option('-3', action='store_const', dest='mode', const=8, + help='Force 3 bit colour mode') + parser.add_option('-4', action='store_const', dest='mode', const=16, + help='Force 4 bit colour mode') + parser.add_option('-8', action='store_const', dest='mode', const=256, + help='Force 8 bit colour mode') + + options, args = parser.parse_args() + options.os = random.randint(0, 256) if options.seed == 0 else options.seed + options.mode = options.mode or detect_mode() + + lolcat = LolCat(mode=options.mode) + + if not args: + args = ['-'] + + for filename in args: + fd = sys.stdin if filename == '-' else file(filename) + lolcat.cat(fd, options) + +if __name__ == '__main__': + sys.exit(run())