|
|
|
|
|
|
|
#ifdef COSMOPOLITAN
|
|
|
|
#include "cosmopolitan.h"
|
|
|
|
#else
|
|
|
|
#include <getopt.h>
|
|
|
|
#include <stdbool.h>
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#include "c-colours/colours.h"
|
|
|
|
|
|
|
|
#define READ_BUFFER_SIZE 200
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Basic Settings
|
|
|
|
*/
|
|
|
|
int rainbow_len = 90; // how many characters long is one full rainbow (360deg)
|
|
|
|
int col_change = -1; // how many degrees between colours in the rainbow
|
|
|
|
int col_dist = -1; // how many characters per colour in output
|
|
|
|
int frequency = -1; // how many degrees to rotate start_hue point on new line
|
|
|
|
|
|
|
|
struct colour* colours; // the rainbow
|
|
|
|
int num_colours; // the length of the rainbow
|
|
|
|
|
|
|
|
// start_hue and end_hue hue values
|
|
|
|
int start_hue = -1;
|
|
|
|
int end_hue = -1;
|
|
|
|
bool reverse = false;
|
|
|
|
|
|
|
|
char help_text[] = "\nUsage: colcat [args] [--] [FILES] \n"
|
|
|
|
"\n"
|
|
|
|
"Concatenate files (or standard input) and print on the standard\n"
|
|
|
|
"output, with a rainbow colour. The colour range and repeat\n"
|
|
|
|
"distance can be specified.\n\n"
|
|
|
|
"Args:\n\n"
|
|
|
|
" -h Print help text and exit\n"
|
|
|
|
" -w <int> Width, in chars, of the rainbow sequence\n"
|
|
|
|
" -r <int> The amount the hue is rotated on new lines\n"
|
|
|
|
" -s <int> The hue to start the rainbow at\n"
|
|
|
|
" -e <int> The hue to end the rainbow at\n"
|
|
|
|
"\n";
|
|
|
|
|
|
|
|
|
|
|
|
/* Print the nth colour of the <rainbow_len> chars long rainbow */
|
|
|
|
void print_next_colour(int current) {
|
|
|
|
|
|
|
|
if (current % (num_colours) == 0 && current > 0) {
|
|
|
|
reverse = !reverse;
|
|
|
|
}
|
|
|
|
|
|
|
|
int index = 0;
|
|
|
|
if (!reverse) {
|
|
|
|
index = (current % num_colours) / col_dist;
|
|
|
|
} else {
|
|
|
|
index = num_colours -1- (current % num_colours) / col_dist;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (current % col_dist == 0) {
|
|
|
|
struct colour c = colours[index];
|
|
|
|
printf("\x1b[38;2;%d;%d;%dm", c.r, c.g, c.b);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Checks if a car is a possible ANSI escape code end_hueing */
|
|
|
|
bool end_hue_ansi_code(char c) {
|
|
|
|
char codes[] = "HfABCDsuJKmhlP";
|
|
|
|
for (int i = 0; i < strlen(codes); i++) {
|
|
|
|
if (c == codes[i]) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Print a file with rainbow */
|
|
|
|
int display(FILE *file) {
|
|
|
|
char c = fgetc(file);
|
|
|
|
|
|
|
|
int start = 0;
|
|
|
|
int end;
|
|
|
|
int counter = 0;
|
|
|
|
if (frequency)
|
|
|
|
end = (num_colours / frequency);
|
|
|
|
else {
|
|
|
|
end = 0;
|
|
|
|
counter = num_colours; // so that print starts at reverse=true
|
|
|
|
}
|
|
|
|
bool in_escape = false;
|
|
|
|
bool in_utf8_mbchar = false;
|
|
|
|
bool vert_reverse = false;
|
|
|
|
while (c != EOF) {
|
|
|
|
|
|
|
|
|
|
|
|
if (c == '\n') {
|
|
|
|
if (frequency && end) {
|
|
|
|
if (counter % (end) == 0) {
|
|
|
|
vert_reverse = !vert_reverse;
|
|
|
|
}
|
|
|
|
counter = start += (vert_reverse ? -1 : 1) * frequency;
|
|
|
|
if (counter < 0) {
|
|
|
|
counter = -counter;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
counter = num_colours;
|
|
|
|
}
|
|
|
|
reverse = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((c & 0b11000000) == 0b10000000) {
|
|
|
|
in_utf8_mbchar = true;
|
|
|
|
} else {
|
|
|
|
in_utf8_mbchar = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (c == '\033') {
|
|
|
|
in_escape = true;
|
|
|
|
} else if (end_hue_ansi_code(c) && in_escape) {
|
|
|
|
in_escape = false;
|
|
|
|
putc(c, stdout);
|
|
|
|
c = fgetc(file);
|
|
|
|
counter++;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!in_escape && !in_utf8_mbchar) {
|
|
|
|
print_next_colour(counter);
|
|
|
|
}
|
|
|
|
|
|
|
|
counter++;
|
|
|
|
putc(c, stdout);
|
|
|
|
c = fgetc(file);
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Attempt to open a file and display, or return 1 on error */
|
|
|
|
int open_and_cat(char *file_name) {
|
|
|
|
FILE *file = fopen(file_name, "r");
|
|
|
|
|
|
|
|
if (!file) {
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return display(file);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* - Generate an array containing all the colours to use for the
|
|
|
|
* rainbow by rotating HSV(0, 100, 100) through hue 0 to 360, using
|
|
|
|
* intervals fitting to how many characters are in the interval.
|
|
|
|
* - They are stored as RGB.
|
|
|
|
* - sets all the relevant global variables for printing the
|
|
|
|
* rainbow out again
|
|
|
|
*/
|
|
|
|
int generate_rainbow() {
|
|
|
|
|
|
|
|
if (start_hue == -1) {
|
|
|
|
start_hue = 0;
|
|
|
|
}
|
|
|
|
if (end_hue == -1) {
|
|
|
|
end_hue = 360;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (end_hue < start_hue) {
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
int arclen = end_hue - start_hue;
|
|
|
|
|
|
|
|
if (rainbow_len < arclen) {
|
|
|
|
num_colours = rainbow_len;
|
|
|
|
col_change = arclen / num_colours;
|
|
|
|
if (col_change == 0) {
|
|
|
|
col_change = 1;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
num_colours = arclen;
|
|
|
|
col_change = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
col_dist = (rainbow_len) / (num_colours);
|
|
|
|
colours = calloc(num_colours, sizeof(struct colour));
|
|
|
|
struct colour c;
|
|
|
|
c.h = start_hue;
|
|
|
|
c.s = 1;
|
|
|
|
c.v = 1;
|
|
|
|
c.sp = CS_HSV;
|
|
|
|
for (int i = 0; i < num_colours; i++) {
|
|
|
|
c.h = (int)(c.h + col_change) % arclen;
|
|
|
|
colours[i] = get_rgb(c);
|
|
|
|
}
|
|
|
|
|
|
|
|
c.h = end_hue;
|
|
|
|
colours[num_colours - 1] = get_rgb(c);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Parses args, sets globals, and chooses whether to read files or stdin */
|
|
|
|
int main(int argc, char** argv) {
|
|
|
|
int opt;
|
|
|
|
int optcount = 0;
|
|
|
|
while ((opt = getopt(argc, argv, "hw:r:s:e:")) != -1) {
|
|
|
|
optcount++;
|
|
|
|
if (opt == 'w') {
|
|
|
|
optcount++;
|
|
|
|
rainbow_len = atoi(optarg);
|
|
|
|
} else if (opt == 'r') {
|
|
|
|
optcount++;
|
|
|
|
frequency = atoi(optarg);
|
|
|
|
} else if (opt == 's') {
|
|
|
|
optcount++;
|
|
|
|
start_hue = atoi(optarg);
|
|
|
|
} else if (opt == 'e') {
|
|
|
|
optcount++;
|
|
|
|
end_hue = atoi(optarg);
|
|
|
|
} else if (opt == 'h') {
|
|
|
|
printf("%s", help_text);
|
|
|
|
exit(0);
|
|
|
|
} else {
|
|
|
|
printf("%s", help_text);
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
generate_rainbow();
|
|
|
|
|
|
|
|
if (frequency == -1) {
|
|
|
|
frequency = col_change;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (argc > optcount + 1) {
|
|
|
|
for (int i = optcount + 1; i < argc; i++) {
|
|
|
|
int err = open_and_cat(argv[i]);
|
|
|
|
if (err) {
|
|
|
|
perror("Unable to open file");
|
|
|
|
exit(err);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
display(stdin);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|