-
Notifications
You must be signed in to change notification settings - Fork 0
/
gi.c
213 lines (173 loc) · 4.53 KB
/
gi.c
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
212
#define _BSD_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <sys/ioctl.h>
#include <sys/wait.h>
#include <unistd.h>
typedef struct {
int width;
int col;
} LineState;
static bool cowsay(char **argv);
static void exec_cowsay(int *pipefds);
static bool consume_cowsay(int *pipefds, pid_t pid, char **argv);
static void animate(char **argv);
static int termwidth(void);
static void concat_msg(char *buf, size_t bufsize, char **words);
// All printing functions assume that msg does not contain escape sequences.
static void marquee(LineState *state, int start_col, int end_col, unsigned long delay, const char *msg);
static void bounce(LineState *state, const char *msg);
static void spacegit(LineState *state, const char *msg);
static void move_to_col(LineState *state, int col);
static void move_right(LineState *state, int cols);
static void move_left(LineState *state, int cols);
static void print(LineState *state, const char *msg);
int main(int argc, char **argv) {
if (argc < 2 || argv[1][0] != 't') {
fprintf(stderr, "Usage: %s t<git args>\n", argv[0]);
return EXIT_FAILURE;
}
if (!getenv("STOP_REMINDING_ME_THAT_I_SUCK_AT_TYPING")) {
if (!cowsay(argv)) {
animate(argv);
}
}
// abuse argv
argv[0] = "git";
argv[1]++;
execvp("git", argv);
perror("Couldn't exec git");
return EXIT_FAILURE;
}
static bool cowsay(char **argv) {
int pipefds[2];
if (pipe(pipefds) < 0) {
perror("pipe");
return false;
}
pid_t pid = fork();
if (pid < 0) {
perror("fork");
return false;
} else if (pid == 0) {
exec_cowsay(pipefds);
exit(EXIT_FAILURE); // unreached unless exec fails
} else {
return consume_cowsay(pipefds, pid, argv);
}
}
static void exec_cowsay(int *pipefds) {
close(pipefds[1]);
if (dup2(pipefds[0], 0) < 0) {
exit(EXIT_FAILURE);
}
char *cowfile = getenv("GI_COWFILE");
if (cowfile && cowfile[0] != '\0') {
execlp("cowsay", "cowsay", "-f", cowfile, NULL);
} else {
execlp("cowsay", "cowsay", NULL);
}
}
static bool consume_cowsay(int *pipefds, pid_t pid, char **argv) {
close(pipefds[0]);
FILE *pp = fdopen(pipefds[1], "w");
if (!pp) {
perror("fdopen");
return false;
}
fputs("I think you meant git ", pp);
fputs(argv[1] + 1, pp); // "tfoo" -> "foo"
for (char **words = argv + 2; *words; words++) {
fprintf(pp, " %s", *words);
}
if (fclose(pp) != 0) {
perror("Error writing to cowsay");
}
int status;
waitpid(pid, &status, 0);
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
return false;
}
sleep(1);
return true;
}
static void animate(char **argv) {
LineState state = { termwidth(), 0 };
size_t msgsize = state.width + 1;
char msg[msgsize];
concat_msg(msg, msgsize, argv + 1);
move_to_col(&state, 0);
print(&state, "gi");
marquee(&state, state.width - 1, 1, 10000, msg);
usleep(50000);
bounce(&state, msg);
usleep(50000);
spacegit(&state, msg);
printf("\n");
}
static int termwidth(void) {
struct winsize ws;
ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws);
return ws.ws_col;
}
static void concat_msg(char *buf, size_t bufsize, char **words) {
size_t pos = 0;
while (*words && pos < bufsize) {
pos += snprintf(buf + pos, bufsize - pos, "%s ", *words);
words++;
}
}
static void marquee(LineState *state, int start_col, int end_col, unsigned long delay, const char *msg) {
int delta = start_col < end_col ? 1 : -1;
for (int i = start_col; i != end_col; i += delta) {
if (delta > 0 && i != start_col) {
// Blank the space we just vacated
move_to_col(state, i - 1);
print(state, " ");
} else {
move_to_col(state, i);
}
print(state, msg);
fflush(stdout);
usleep(delay);
}
}
static void bounce(LineState *state, const char *msg) {
for (int i = 0; i < 3; i++) {
// gi t<command>
marquee(state, 2, 10, 20000, msg);
usleep(500000);
// git<command>
marquee(state, 10, 1, 10000, msg);
}
}
static void spacegit(LineState *state, const char *msg) {
// git <command>
move_to_col(state, 3);
print(state, " ");
print(state, msg + 1);
}
static void move_to_col(LineState *state, int col) {
if (state->col < col) {
move_right(state, col - state->col);
} else if (state->col > col) {
move_left(state, state->col - col);
}
}
static void move_right(LineState *state, int cols) {
printf("\033[%dC", cols);
state->col += cols;
}
static void move_left(LineState *state, int cols) {
printf("\033[%dD", cols);
state->col -= cols;
}
static void print(LineState *state, const char *msg) {
int max = state->width - state->col;
for (int i = 0; i < max && msg[i] != '\0'; i++) {
putchar(msg[i]);
state->col++;
}
}