A literate programming tangler written in Go
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

4.6 KiB

Randomly Colored Cat

Josias Allestad

This program will provide a cat-like utility for printing text from a file to stdout. Except that output will be randomly colorized.

Thus the name: Randomly Colored Cat.

Here is the basic structure of the program:

<<includes>>

<<constants>>

<<colors-function>>

<<main-function>>

Now for the includes:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>

unistd.h, stdlib.h, and time.h are required for randomness, stdio.h for printf, and string.h for strcmp and strlen.

Some constants that will be the same throughout the program:

#define BUF_SIZE 10

<<colors>>

Now the colors:

#define KNRM "\x1B[0m"
#define KRED "\x1B[31m"
#define KGRN "\x1B[32m"
#define KYEL "\x1B[33m"
#define KMAG "\x1B[35m"
#define KCYN "\x1B[36m"
#define KWHT "\x1B[37m"

const char *colors[] = {KNRM, KRED, KGRN, KYEL, KMAG, KCYN};

These are all the codes for the colors we want, and an array to order them nicely to randomly index them.

Print Colors

int lastcolor = 0;
void printColors(char *text)
{
	<<colors=notty>>
	if (text) {
		<<colors-normal>>
	}
	<<colors-reset>>
}

If the output is piped (and thus isn't a tty), the colors don't make much sense.

if (!(isatty(STDOUT_FILENO))) {
	printf("%s", text);
	return;
}

Go through the text character by character and print it with a random color between 0 and 6 (the index of colors). It also makes sure that the color is not the same as the last one.

for (int i = 0; i < strlen(text); ++i) {
	int color = 0;
	do {
		color = rand() % 6;
	} while (color == lastcolor);
	lastcolor = color;
	printf("%s%c", colors[color], text[i]);
}

Now we have to reset the terminal to the normal colors.

printf("%s", KWHT);

Main

The main function that every C program must have.

int main(int argc, char *argv[])
{
	<<main-body>>

	return EXIT_SUCCESS;
}

Initialize random and buffer, and loop through the arguments.

srand(time(NULL));
char buffer[BUF_SIZE] = {""};

for (int i = 0; i < argc; ++i) {
	<<parse-arg>>
}

Determine whether or not the argument is -. If so, get the input from stdin instead of a file. In addition, it should not try to read the file from argv[0], since that is itself.

if (strcmp(argv[i], "-") == 0 || argc == 1) {
	<<print-from-stdin>>
} else if (i >= 1) {
	<<open-file>>
	<<read-file>>
	<<close-file>>
}

This reads the file section by section in chucks the size of BUF_SIZE and prints them with printColors. If the section is unreadable, it prints an error to stdout.

size_t got;
while ((got = fread(buffer, 1, BUF_SIZE -1, fp))) {
	buffer[got] = '\0';
		printColors(buffer);
	}
	if (ferror(fp))
		fprintf(stderr, "Error: can't read %s\n", argv[1]);

Basically the same as above, but gets text from stdin instead of a file pointer. In addition, it needs to call clearerr to be able to reopen the input stream for multiple - arguments.

while (fgets(buffer, BUF_SIZE, stdin) != NULL) {
	printColors(buffer);
}
clearerr(stdin);

Prepare a file pointer and make sure the file exists. If not, write an error to stderr.

FILE *fp;
fp = fopen(argv[i], "r");
if (!(fp)) {
	fprintf(stderr, "rccat: %s: no such file or directory.\n", argv[i]);
}
fclose(fp);

Building

To compile with your C compiler directly, you can use gcc -o rccat rccat.c, but that isn't as useful as a Makefile.

The complete Makefile:

PREFIX ?= /usr/local
INSTALL ?= install
INSTALL_PROGRAM ?= $(INSTALL)

<<make-all>>
<<make-build>>
<<make-clean>>
<<make-install>>
<<make-uninstall>>

PREFIX determines where you want the program to be installed when make install is run.

If you just want to run make, it automatically calls build.

all: build

Compiling the program with your C compiler.

build:
	$(CC) -o rccat rccat.c

A simple way to delete the rccat file, and any other generated files (in the future).

clean:
	$(RM) rccat

Install the program to PREFIX (as mentioned above). It first prepares the install and then copies over the file to PREFIX/bin.

install:
	$(INSTALL_PROGRAM) -d $(PREFIX)/bin
	$(INSTALL_PROGRAM) -m755 rccat $(PREFIX)/bin

Uninstall the program from PREFIX/bin.

uninstall:
	$(RM) $(PREFIX)/bin/rccat