diff --git a/examples/rote.c b/examples/rote.c new file mode 100644 index 000000000..3819ce331 --- /dev/null +++ b/examples/rote.c @@ -0,0 +1,322 @@ +#/*────────────────────────────────────────────────────────────────╗ +┌┘ To the extent possible under law, Justine Tunney has waived │ +│ all copyright and related or neighboring rights to this file, │ +│ as it is written in the following disclaimers: │ +│ • http://unlicense.org/ │ +│ • http://creativecommons.org/publicdomain/zero/1.0/ │ +╚─────────────────────────────────────────────────────────────────*/ +#include +#include +#include +#include +#include +#include +#include + +/** + * @fileoverview cosmopolitan flash cards viewer + */ + +struct Card { + char* qa[2]; +}; + +atomic_int g_done; + +void onsig(int sig) { + g_done = 1; +} + +void* xmalloc(int n) { + void* p; + if ((p = malloc(n))) + return p; + perror("malloc"); + exit(1); +} + +void* xrealloc(void* p, int n) { + if ((p = realloc(p, n))) + return p; + perror("realloc"); + exit(1); +} + +char* xstrcat(const char* a, const char* b) { + char* p; + size_t n, m; + n = strlen(a); + m = strlen(b); + p = xmalloc(n + m + 1); + memcpy(p, a, n); + memcpy(p + n, b, m + 1); + return p; +} + +void shuffle(struct Card* a, int n) { + while (n > 1) { + int i = rand() % n--; + struct Card t = a[i]; + a[i] = a[n]; + a[n] = t; + } +} + +char* trim(char* s) { + int i; + if (s) { + while (isspace(*s)) + ++s; + for (i = strlen(s); i--;) { + if (isspace(s[i])) { + s[i] = 0; + } else { + break; + } + } + } + return s; +} + +char* readline(FILE* f) { + for (;;) { + char* line = trim(fgetln(f, 0)); + if (!line) + return 0; + if (*line != '#') + if (*line) + return line; + } +} + +char* fill(const char* text, int max_line_width, int* out_line_count) { + int text_len = strlen(text); + char* result = xmalloc(text_len * 2 + 1); + int result_pos = 0; + int line_start = 0; + int line_count = 1; + int i = 0; + while (i < text_len && isspace(text[i])) + i++; + while (i < text_len) { + int word_end = i; + while (word_end < text_len && !isspace(text[word_end])) + word_end++; + int word_length = word_end - i; + if ((result_pos - line_start) + (result_pos > line_start ? 1 : 0) + + word_length > + max_line_width) { + if (result_pos > line_start) { + ++line_count; + result[result_pos++] = '\n'; + line_start = result_pos; + } + } else if (result_pos > line_start) { + result[result_pos++] = ' '; + } + memcpy(result + result_pos, text + i, word_length); + result_pos += word_length; + i = word_end; + while (i < text_len && isspace(text[i])) + i++; + } + result[result_pos] = '\0'; + result = xrealloc(result, result_pos + 1); + if (out_line_count) + *out_line_count = line_count; + return result; +} + +void show(const char* text, int i, int n) { + + // get pseudoteletypewriter dimensions + struct winsize ws = {80, 25}; + tcgetwinsize(1, &ws); + int width = ws.ws_col; + if (width > (int)(ws.ws_col * .9)) + width = ws.ws_col * .9; + if (width > 80) + width = 80; + width &= -2; + + // clear display + printf("\033[H\033[J"); + + // display flash card text in middle of display + char buf[32]; + int line_count; + char* lines = fill(text, width, &line_count); + sprintf(buf, "%d/%d\r\n\r\n", i + 1, n); + line_count += 2; + char* extra = xstrcat(buf, lines); + free(lines); + char* tokens = extra; + for (int j = 0;; ++j) { + char* line = strtok(tokens, "\n"); + tokens = 0; + if (!line) + break; + printf("\033[%d;%dH%s", ws.ws_row / 2 - line_count / 2 + j + 1, + ws.ws_col / 2 - strlen(line) / 2 + 1, line); + } + free(extra); + fflush(stdout); +} + +void usage(FILE* f, const char* prog) { + fprintf(f, + "usage: %s FILE\n" + "\n" + "here's an example of what your file should look like:\n" + "\n" + " # cosmopolitan flash cards\n" + " # california dmv drivers test\n" + " \n" + " which of the following point totals could result in " + "your license being suspended by the dmv?\n" + " 4 points in 12 months (middle)\n" + " \n" + " at 55 mph under good conditions a passenger vehicle can stop " + "within\n" + " 300 feet (not 200, not 400, middle)\n" + " \n" + " two sets of solid double yellow lines spaced two or more feet " + "apart indicate\n" + " a BARRIER (do not cross unless there's an opening)\n" + "\n" + "more specifically, empty lines are ignored, lines starting with\n" + "a hash are ignored, then an even number of lines must remain,\n" + "where each two lines is a card, holding question and answer.\n", + prog); +} + +int main(int argc, char* argv[]) { + + // show help + if (argc != 2) { + usage(stderr, argv[0]); + return 1; + } + if (!strcmp(argv[1], "-?") || // + !strcmp(argv[1], "-h") || // + !strcmp(argv[1], "--help")) { + usage(stdout, argv[0]); + return 0; + } + + // teletypewriter is required + if (!isatty(0) || !isatty(1)) { + perror("isatty"); + return 2; + } + + // load cards + FILE* f = fopen(argv[1], "r"); + if (!f) { + perror(argv[1]); + return 3; + } + int count = 0; + struct Card* cards = 0; + for (;;) { + struct Card card; + if (!(card.qa[0] = readline(f))) + break; + card.qa[0] = strdup(card.qa[0]); + if (!(card.qa[1] = readline(f))) { + fprintf(stderr, "%s: flash card file has odd number of lines\n", argv[1]); + exit(1); + } + card.qa[1] = strdup(card.qa[1]); + cards = xrealloc(cards, (count + 1) * sizeof(struct Card)); + cards[count++] = card; + } + fclose(f); + + // randomize + srand(time(0)); + shuffle(cards, count); + + // catch ctrl-c + struct sigaction sa; + sa.sa_flags = 0; + sa.sa_handler = onsig; + sigemptyset(&sa.sa_mask); + sigaction(SIGINT, &sa, 0); + + // enter raw mode + struct termios ot; + tcgetattr(1, &ot); + struct termios nt = ot; + cfmakeraw(&nt); + nt.c_lflag |= ISIG; + tcsetattr(1, TCSANOW, &nt); + printf("\033[?25l"); + + // show flash cards + int i = 0; + while (!g_done) { + show(cards[i / 2].qa[i % 2], i / 2, count); + + // press any key + char b[8] = {0}; + read(0, b, sizeof(b)); + + // q quits + if (b[0] == 'q') + break; + + // b or ctrl-b goes backward + if (b[0] == 'b' || // + b[0] == ('B' ^ 0100)) { + if (--i < 0) + i = count * 2 - 1; + i &= -2; + continue; + } + + // p or ctrl-p goes backward + if (b[0] == 'p' || // + b[0] == ('P' ^ 0100)) { + if (--i < 0) + i = count * 2 - 1; + i &= -2; + continue; + } + + // up arrow goes backward + if (b[0] == 033 && // + b[1] == '[' && // + b[2] == 'A') { + if (--i < 0) + i = count * 2 - 1; + i &= -2; + continue; + } + + // left arrow goes backward + if (b[0] == 033 && // + b[1] == '[' && // + b[2] == 'D') { + if (--i < 0) + i = count * 2 - 1; + i &= -2; + continue; + } + + // only advance + if (++i == count * 2) + i = 0; + } + + // free memory + for (int i = 0; i < count; ++i) + for (int j = 0; j < 2; ++j) + free(cards[i].qa[j]); + free(cards); + + // cleanup terminal and show cursor + tcsetattr(1, TCSANOW, &ot); + printf("\033[?25h"); + printf("\n"); +}