summaryrefslogtreecommitdiff
path: root/rccat.md
blob: ff0a3dd06506bec8b2fafc4aaacbb2e329f53d6d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
# 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:

``` {.c file=rccat.c}
<<includes>>

<<constants>>

<<colors-function>>

<<main-function>>
```

Now for the includes:

``` {.c #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:
``` {.c #constants}
#define BUF_SIZE 10

<<colors>>
```

Now the colors:

``` {.c #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

``` {.c #colors-function}
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.
``` {.c #colors-notty}
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.
``` {.c #colors-normal}
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.
``` {.c #colors-reset}
printf("%s", KWHT);
```

# Main

The main function that every C program must have.

``` {.c #main-function}
int main(int argc, char *argv[])
{
	<<main-body>>

	return EXIT_SUCCESS;
}
```

Initialize random and buffer, and loop through the arguments.
``` {.c #main-body}
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.
``` {.c #parse-arg}
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`.
``` {.c #read-file}
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.
``` {.c #print-from-stdin}
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`.
``` {.c #open-file}
FILE *fp;
fp = fopen(argv[i], "r");
if (!(fp)) {
	fprintf(stderr, "rccat: %s: no such file or directory.\n", argv[i]);
}
```

``` {.c #close-file}
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:

``` {.make file=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.
``` {.make #make-all}
all: build
```

Compiling the program with your C compiler.
``` {.make #make-build}
build:
	$(CC) -o rccat rccat.c
```

A simple way to delete the `rccat` file, and any other generated files (in the future).
``` {.make #make-clean}
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`. 
``` {.make #make-install}
install:
	$(INSTALL_PROGRAM) -d $(PREFIX)/bin
	$(INSTALL_PROGRAM) -m755 rccat $(PREFIX)/bin
```

Uninstall the program from `PREFIX/bin`.
``` {.make #make-uninstall}
uninstall:
	$(RM) $(PREFIX)/bin/rccat
```