commit 6ed6bfad7b89d4fa4e83dbf7cf9cf4565788b7f4
Author: Santtu Lakkala <inz@inz.fi>
Date:   Wed, 15 Jul 2020 14:54:51 +0300
Initial import
Diffstat:
4 files changed, 575 insertions(+), 0 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -0,0 +1,3 @@
+*~
+*.o
+nyancat
diff --git a/Makefile b/Makefile
@@ -0,0 +1,6 @@
+CFLAGS := -W -Wall -std=c99 $(shell pkg-config icu-uc --cflags)
+LIBS := $(shell pkg-config icu-uc --libs) -lm
+nyancat: nyancat.o
+	$(CC) -o $@ $^ $(LIBS)
+%.o: %.c
+	$(CC) $(CFLAGS) -c $< -o $@
diff --git a/README.md b/README.md
@@ -0,0 +1 @@
+# nyancat
diff --git a/nyancat.c b/nyancat.c
@@ -0,0 +1,565 @@
+/*
+ * Copyright (c) 2020 Santtu Lakkala
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
+ * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#include <stdarg.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <math.h>
+
+#include <fcntl.h>
+#include <limits.h>
+#include <unistd.h>
+
+#include <termios.h>
+#include <sys/ioctl.h>
+
+#include <unicode/uchar.h>
+#include <unicode/utf8.h>
+
+#define PI 3.1415926535898
+
+struct lolcat {
+	int x;
+	int y;
+	int w;
+	int h;
+	int sx;
+	int sy;
+	int fg;
+	int bg;
+	char bold;
+	char reverse;
+	char underline;
+	char was_bold;
+	char was_reverse;
+	char was_underline;
+
+	void (*write)(const char *buffer, size_t buflen, void *data);
+	void *write_data;
+};
+
+int nyan(int x, int y)
+{
+	return sin(x * PI / 18) * 18 + y * 18;
+}
+
+int rainbow(float freq, int i)
+{
+	return ((int)(sin(freq * i + 0) * 127 + 128)) << 16 |
+		((int)(sin(freq * i + 2 * PI / 3) * 127 + 128)) << 8 |
+		((int)(sin(freq * i + 4 * PI / 3) * 127 + 128));
+}
+
+static size_t strnspn_printable(const char *str, size_t len)
+{
+	size_t rv = 0;
+	while (rv < len && (str[rv] & ~0x9f))
+		rv++;
+	return rv;
+}
+
+static size_t strnspn(const char *str, size_t len, const char *allowed)
+{
+	uint32_t allowed_mask[(1U << CHAR_BIT) / 32] = { 0 };
+	size_t i;
+	for (; *allowed; allowed++)
+		allowed_mask[*(unsigned char *)allowed / 32] |= 1 <<
+			(*(unsigned char *)allowed & 31);
+	for (i = 0; i < len; i++)
+		if (!(allowed_mask[((unsigned char *)str)[i] / 32] & 1 <<
+		      (((unsigned char *)str)[i] & 31)))
+			break;
+	return i;
+}
+
+static void strtok_foreach(const char *str, int len, char delim,
+			   int (*cb)(const char *tok, int toklen, void *data),
+			   void *data)
+{
+	const char *i = str;
+	const char *sep;
+
+	for (sep = memchr(str, delim, len);
+	     sep;
+	     i = sep + 1, sep = memchr(i, delim, len - (i - str)))
+		if (cb(i, sep - i, data))
+			return;
+	cb(i, len - (i - str), data);
+}
+
+struct strtok_int_data {
+	int (*cb)(int val, void *data);
+	void *data;
+};
+
+static int _strtok_fi_cb(const char *tok, int toklen, void *data)
+{
+	struct strtok_int_data *fidata = data;
+	int val = 0;
+
+	while (toklen-- && *tok >= '0' && *tok <= '9')
+		val = val * 10 + *tok++ - '0';
+	return fidata->cb(val, fidata->data);
+}
+
+static void strtok_foreach_int(const char *str, int len, char delim,
+			       int (*cb)(int val, void *data), void *data)
+{
+	struct strtok_int_data fidata;
+
+	fidata.cb = cb;
+	fidata.data = data;
+
+	strtok_foreach(str, len, delim, _strtok_fi_cb, &fidata);
+}
+
+struct lolcat *lolcat_init(void (*write_cb)(const char *buffer, size_t buflen,
+					    void *data), void *cbdata)
+{
+	struct lolcat *rv = calloc(1, sizeof(struct lolcat));
+
+	rv->write = write_cb;
+	rv->write_data = cbdata;
+	rv->fg = 0xc0c0c0;
+	rv->bg = 0x000000;
+
+	return rv;
+}
+
+static int _lc_arg_m(int arg, void *data)
+{
+	struct lolcat *lc = data;
+
+	if (arg == 38 || arg == 48)
+		return 1;
+
+	switch (arg) {
+	case 0:
+		lc->bold = 0;
+		lc->reverse = 0;
+		lc->fg = 0xc0c0c0;
+		lc->bg = 0x000000;
+		break;
+	case 1:
+		lc->bold = 1;
+		break;
+	case 4:
+		lc->underline = 1;
+		break;
+	case 7:
+		lc->reverse = 1;
+		break;
+	case 24:
+		lc->underline = 0;
+		break;
+	case 30:
+	case 31:
+	case 32:
+	case 33:
+	case 34:
+	case 35:
+	case 36:
+		lc->fg = ((arg - 30) & 1) << 23 |
+			 ((arg - 30) & 2) << 14 |
+			 ((arg - 30) & 4) << 5;
+		break;
+	case 37:
+		lc->fg = 0xc0c0c0;
+		break;
+	case 40:
+	case 41:
+	case 42:
+	case 43:
+	case 44:
+	case 45:
+	case 46:
+		lc->fg = ((arg - 40) & 1) << 23 |
+			 ((arg - 40) & 2) << 14 |
+			 ((arg - 40) & 4) << 5;
+		break;
+
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+static inline int _bold(int color)
+{
+	if (!color)
+		return 0x808080;
+	if (color == 0xc0c0c0)
+		return 0xffffff;
+	return (color << 1) - (color >> 7);
+}
+
+#define MIN(a, b) ((a) < (b) ? (a) : (b))
+#define MAX(a, b) ((a) < (b) ? (b) : (a))
+#define CLAMP(a, b, c) MIN(MAX(a, b), c)
+static int _add(int a, int b, double amount)
+{
+	int r = (a >> 16) + ((b >> 16) - 0xc0) * amount;
+	int g = ((a >> 8) & 0xff) + (((b >> 8) & 0xff) - 0xc0) * amount;
+	int bl = (a & 0xff) + ((b & 0xff) - 0xc0) * amount;
+	return CLAMP(r, 0, 255) << 16 |
+		CLAMP(g, 0, 255) << 8 |
+		CLAMP(bl, 0, 255);
+}
+
+static void _lc_colorize(struct lolcat *lc, const char *u8_char, size_t len)
+{
+	char buffer[128] = "\x1b[";
+	int col;
+	char *bw = buffer + 2;
+
+	if (len == 1 && u8_char[0] == ' ' &&
+			!lc->reverse && !lc->was_reverse) {
+		lc->write(" ", 1, lc->write_data);
+		return;
+	}
+
+	if ((lc->was_bold && !lc->bold) ||
+	    (lc->was_reverse && !lc->reverse)) {
+		lc->was_underline = 0;
+		lc->was_bold = 0;
+		lc->was_reverse = 0;
+		*bw++ = '0';
+		*bw++ = ';';
+	}
+	if (lc->was_underline != lc->underline) {
+		if (lc->was_underline)
+			*bw++ = '2';
+		*bw++ = '4';
+		*bw++ = ';';
+		lc->was_underline = lc->underline;
+	}
+	if (lc->bold && !lc->was_bold) {
+		*bw++ = '1';
+		*bw++ = ';';
+		lc->was_bold = 1;
+	}
+	if (lc->reverse && !lc->was_reverse) {
+		*bw++ = '7';
+		*bw++ = ';';
+		lc->was_reverse = 1;
+	}
+
+	if (lc->bold)
+		col = _bold(lc->fg);
+	else
+		col = lc->fg;
+	col = _add(rainbow(0.03, nyan(lc->x, lc->y)), col, 0.5);
+
+	bw += sprintf(bw, "38;2;%d;%d;%dm%.*s",
+		      col >> 16,
+		      (col >> 8) & 255,
+		      col & 255, (int)len, u8_char);
+
+	lc->write(buffer, bw - buffer, lc->write_data);
+}
+
+struct strnsplit_int_data {
+	va_list args;
+	int n;
+};
+
+static int _strnsplit_int_cb(int val, void *data)
+{
+	struct strnsplit_int_data *ssidata = data;
+	int *a = va_arg(ssidata->args, int *);
+	*a = val;
+	return !--ssidata->n;
+}
+
+static int strnsplit_int(const char *str, size_t len, char delim, int n, ...)
+{
+	struct strnsplit_int_data data;
+	va_start(data.args, n);
+	data.n = n;
+	strtok_foreach_int(str, len, delim, _strnsplit_int_cb, &data);
+	va_end(data.args);
+
+	return n - data.n;
+}
+
+ssize_t lc_process(struct lolcat *lc, const char *buffer, int32_t len)
+{
+	int32_t i = 0;
+	int32_t ip = 0;
+	while (i < len) {
+		UChar32 c;
+		int eaw;
+
+		ip = i;
+		U8_NEXT(buffer, i, len, c);
+
+		if (c < 0)
+			return ip;
+
+		if (c == '\x1b') {
+			if (!buffer[i])
+				return ip;
+			if (buffer[i] == '[') {
+				size_t n_args;
+				char cmd;
+				if (buffer[i + 1] == '?') {
+					n_args = strnspn(buffer + i + 2,
+							 len - i - 2,
+							 "0123456789;");
+					if (!buffer[i + 2 + n_args])
+						return ip;
+					lc->write(buffer + ip, n_args + 4,
+						  lc->write_data);
+					i += n_args + 3;
+					continue;
+				}
+				n_args = strnspn(buffer + i + 1, len - i - 1,
+						 "0123456789;");
+				cmd = buffer[i + 1 + n_args];
+
+				if (!cmd)
+					return ip;
+
+				if (cmd == 'H') {
+					int x, y;
+					strnsplit_int(buffer + i + 1,
+						      len - i - 1, ';',
+						      2, &y, &x);
+					lc->x = x - 1;
+					lc->y = y - 1;
+				}
+				if (cmd >= 'A' && cmd <= 'D') {
+					int n = 1;
+					int dirs[][2] = {
+						{ 0, -1 },
+						{ 0, 1 },
+						{ 1, 0 },
+						{ -1, 0 }
+					};
+					strnsplit_int(buffer + i + 1,
+						      len - i - 1, ';', 1,
+						      &n);
+					lc->x += dirs[cmd - 'A'][0] * n;
+					lc->y += dirs[cmd - 'A'][1] * n;
+				}
+				if (cmd == 'G') {
+					int x;
+					strnsplit_int(buffer + i + 1,
+						      len - i - 1, ';', 1,
+						      &x);
+					lc->x = x;
+				}
+				if (cmd == 'd') {
+					int y;
+					strnsplit_int(buffer + i + 1,
+						      len - i - 1, ';', 1,
+						      &y);
+					lc->y = y;
+				}
+				if (cmd == 'J') {
+					if (atoi(buffer + i + 1) == 2) {
+						lc->x = 0;
+						lc->y = 0;
+					}
+				}
+				if (cmd == 'm') {
+					if (n_args == 0)
+						_lc_arg_m(0, lc);
+					else
+						strtok_foreach_int(
+							buffer + i + 1,
+							n_args, ';',
+							_lc_arg_m, lc);
+					i += n_args + 2;
+					continue;
+				}
+				if (cmd == 's') {
+					lc->sx = lc->x;
+					lc->sy = lc->y;
+				}
+				if (cmd == 'u') {
+					lc->x = lc->sx;
+					lc->y = lc->sy;
+				}
+				lc->write(buffer + ip, n_args + 3,
+					  lc->write_data);
+				i += n_args + 2;
+				continue;
+			}
+			if (buffer[i] == '(') {
+				size_t n_args;
+				char cmd;
+				n_args = strnspn(buffer + i + 1, len - i - 1,
+						 "0123456789;");
+				cmd = buffer[i + 1 + n_args];
+
+				if (!cmd)
+					return ip;
+
+				lc->write(buffer + ip, n_args + 3,
+					  lc->write_data);
+				i += n_args + 2;
+				continue;
+			}
+			if (buffer[i] == ']') {
+				ssize_t dlen = strnspn_printable(
+					buffer + i + 1,
+					len - i - 1);
+
+				if (dlen + i + 1 == len)
+					return ip;
+				if (buffer[i + dlen + 1] == '\007' ||
+				    buffer[i + dlen + 1] == '\x9c') {
+					lc->write(buffer + ip, dlen + 3,
+						  lc->write_data);
+					i += dlen + 2;
+					continue;
+				}
+			}
+			if (buffer[i] == '>') {
+				lc->write(buffer + ip, 2, lc->write_data);
+				i += 1;
+				continue;
+			}
+		} else if (c == '\t') {
+			lc->x = (lc->x + 8) & ~(int)0x7;
+			lc->write("\t", 1, lc->write_data);
+			continue;
+		} else if (c == '\r') {
+			lc->x = 0;
+			lc->y++;
+			lc->write("\r", 1, lc->write_data);
+			continue;
+		} else if (c == '') {
+			if (lc->x)
+				lc->x--;
+			lc->write("", 1, lc->write_data);
+			continue;
+		} else if (c == '\n') {
+			lc->x = 0;
+			lc->y++;
+			lc->write("\n", 1, lc->write_data);
+			continue;
+		}
+
+		eaw = u_getIntPropertyValue(c, UCHAR_EAST_ASIAN_WIDTH);
+
+		if (lc->w && lc->x + (eaw == U_EA_WIDE ||
+				      eaw == U_EA_FULLWIDTH) >= lc->w) {
+			lc->x = 0;
+			lc->y++;
+		}
+
+		_lc_colorize(lc, buffer + ip, i - ip);
+
+		if (eaw == U_EA_WIDE ||
+		    eaw == U_EA_FULLWIDTH)
+			lc->x++;
+		lc->x++;
+	}
+
+	return i;
+}
+
+void _write(const char *data, size_t len, void *user_data)
+{
+	fwrite(data, 1, len, user_data);
+}
+
+void lolcat_set_size(struct lolcat *lc, int w, int h)
+{
+	lc->w = w;
+	lc->h = h;
+}
+
+int main(int argc, char **argv)
+{
+	struct lolcat *lc = lolcat_init(_write, stdout);
+	char buffer[2048];
+	size_t used = 0;
+	struct termios old_tio = { 0 }, new_tio;
+	int i;
+
+	struct winsize ws = { 0 };
+
+	if (!ioctl(STDERR_FILENO, TIOCGWINSZ, &ws) ||
+	    !ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws))
+		lolcat_set_size(lc, ws.ws_col, ws.ws_row);
+
+	if (argc < 2) {
+		tcgetattr(STDIN_FILENO, &old_tio);
+
+		new_tio = old_tio;
+
+		new_tio.c_lflag &= (~ICANON & ~ECHO);
+		tcsetattr(STDIN_FILENO, TCSANOW, &new_tio);
+
+		while (TRUE) {
+			ssize_t bytes = read(STDIN_FILENO, buffer + used,
+					     sizeof(buffer) - used);
+			size_t processed;
+
+			if (bytes <= 0)
+				break;
+
+			used += bytes;
+			processed = lc_process(lc, buffer, used);
+
+			if (processed != used)
+				memmove(buffer, buffer + processed,
+					used - processed);
+			used -= processed;
+
+			fflush(stdout);
+		}
+
+		fflush(stdout);
+		tcsetattr(STDIN_FILENO, TCSANOW, &old_tio);
+	} else {
+		for (i = 1; i < argc; i++) {
+			int fd;
+			if (!strcmp(argv[i], "-"))
+				fd = STDIN_FILENO;
+			else
+				fd = open(argv[i], O_RDONLY);
+			while (TRUE) {
+				ssize_t bytes = read(fd, buffer + used,
+						     sizeof(buffer) - used);
+				size_t processed;
+
+				if (bytes <= 0)
+					break;
+
+				used += bytes;
+				processed = lc_process(lc, buffer, used);
+
+				if (processed != used)
+					memmove(buffer, buffer + processed,
+						used - processed);
+				used -= processed;
+
+				fflush(stdout);
+			}
+			if (fd != STDIN_FILENO)
+				close(fd);
+		}
+	}
+
+	fprintf(stdout, "\x1b[0m");
+
+	return 0;
+}