commit 6cb461a040f4b53f0f01421da92007fc682a7310
Author: Louis Burda <quent.burda@gmail.com>
Date: Mon, 21 Jun 2021 12:05:58 +0200
Initial prototype
Diffstat:
A | .gitignore | | | 1 | + |
A | Makefile | | | 4 | ++++ |
A | main.c | | | 184 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
3 files changed, 189 insertions(+), 0 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -0,0 +1 @@
+typetest
diff --git a/Makefile b/Makefile
@@ -0,0 +1,4 @@
+CFLAGS=-I . -g
+
+typetest: main.c
+ $(CC) -o $@ $< $(CFLAGS) $(LDLIBS)
diff --git a/main.c b/main.c
@@ -0,0 +1,184 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdint.h>
+#include <termio.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <time.h>
+
+#define LINTERP(a,b,v) ((a) * (v) + (b) * (1 - (v)))
+
+enum {
+ C_EOF = 0x03,
+ C_EOT = 0x04,
+ C_DEL = 0x7f
+};
+
+enum {
+ STYLE_END = -1,
+
+ STYLE_RESET = 0,
+
+ STYLE_BOLD = 1,
+ STYLE_FAINT = 2,
+ STYLE_ITALIC = 3,
+ STYLE_UNDERLINE = 4,
+ STYLE_BLINK = 5,
+ STYLE_INVERT = 7,
+ STYLE_CROSSED_OUT = 9,
+
+ STYLE_BLACK_FG = 30,
+ STYLE_RED_FG = 31,
+ STYLE_GREEN_FG = 32,
+ STYLE_YELLOW_FG = 33,
+ STYLE_BLUE_FG = 34,
+ STYLE_MAGENTA_FG = 35,
+ STYLE_CYAN_FG = 36,
+ STYLE_WHITE_FG = 37,
+};
+
+static const int style_wrong[] = { STYLE_RED_FG, STYLE_BOLD, STYLE_END };
+static const int style_right[] = { STYLE_GREEN_FG, STYLE_END };
+static const int style_reset[] = { STYLE_RESET, STYLE_END };
+
+struct termios oldt;
+
+int typed = 0;
+float gcps;
+
+void
+die(const char *fmtstr, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmtstr);
+ vprintf(fmtstr, ap);
+ va_end(ap);
+
+ exit(EXIT_FAILURE);
+}
+
+void
+print_style(const int *props)
+{
+ const int *iter;
+
+ if (!props || *props == STYLE_END) return;
+
+ printf("\x1b[");
+ for (iter = props; *iter != STYLE_END; iter++)
+ printf("%i%c", *iter, (iter[1] == STYLE_END) ? 'm' : ';');
+}
+
+void
+time_sub(struct timespec *new, struct timespec *old)
+{
+ new->tv_sec -= old->tv_sec;
+ new->tv_nsec -= old->tv_nsec;
+
+ if (new->tv_nsec < 0) {
+ new->tv_sec -= 1;
+ new->tv_nsec += 1e9;
+ }
+}
+
+int
+type(const char *text)
+{
+ char input[1024];
+ int c, right, wrong, tlen;
+ struct timespec start = { 0 }, end;
+ float cps;
+
+ printf("%s", text);
+
+ tlen = strlen(text);
+ right = wrong = 0;
+ while ((c = getchar()) > 0) {
+ printf("\x1b[2K"); /* clear line */
+ printf("\r"); /* cursor to beginning of line */
+
+ if (!start.tv_sec)
+ clock_gettime(CLOCK_REALTIME, &start);
+
+ switch (c) {
+ case C_EOF:
+ case C_EOT:
+ return 1;
+ case C_DEL:
+ if (wrong) wrong--;
+ else right--;
+ break;
+ case u'\n':
+ break;
+ default:
+ if (right + wrong == tlen) break;
+ if (right + wrong == sizeof(input)) break;
+ if (c == text[right + wrong] && !wrong) right++;
+ else wrong++;
+ break;
+ }
+
+ print_style(style_right);
+ printf("%.*s", right, text);
+ print_style(style_wrong);
+ printf("%.*s", wrong, text + right);
+ print_style(style_reset);
+ printf("%.*s", tlen - right - wrong, text + right + wrong);
+
+ if (!wrong && right + wrong == tlen) break;
+ }
+
+ clock_gettime(CLOCK_REALTIME, &end);
+ time_sub(&end, &start);
+
+ cps = tlen / (end.tv_sec + end.tv_nsec / 1e9f);
+ if (!typed) gcps = cps;
+ else gcps = LINTERP(gcps, cps, typed * 1.f / (typed + tlen));
+
+ printf("\n\r");
+ return 0;
+}
+
+void
+reset()
+{
+ tcsetattr(0, TCSANOW, &oldt);
+}
+
+int
+main(int argc, const char **argv)
+{
+ struct termios t;
+ char linebuf[256];
+ FILE *f;
+
+ if (argc < 2)
+ die("USAGE: typetest <FILE>\n");
+
+ if (!(f = fopen(argv[1], "r")))
+ die("Failed to read file\n");
+
+ setvbuf(stdin, NULL, _IONBF, 0);
+ setvbuf(stdout, NULL, _IONBF, 0);
+
+ tcgetattr(0, &t);
+ memcpy(&oldt, &t, sizeof(struct termios));
+
+ cfmakeraw(&t);
+
+ if (!tcsetattr(0, TCSANOW, &t)) atexit(reset);
+
+ while (fgets(linebuf, sizeof(linebuf), f) > 0) {
+ if (!*linebuf) break;
+ if (linebuf[strlen(linebuf)-1] == '\n')
+ linebuf[strlen(linebuf)-1] = '\0';
+ if (!*linebuf) continue;
+ if (type(linebuf)) break;
+ }
+
+ printf("CPM: %0.2f\n\r", gcps * 60);
+
+ fclose(f);
+}