git home / emma home
logo

typie

Minimal typing practice tool.
git clone https://git.y1.nz/archives/typie.tar.gz
README | Files | Log | Refs | LICENSE

commit 09da1f9bea7fb12e327de2399a62ab3370f4f629
parent ef7cd656eb53cd9aa1e20ee29f3fe2bd1c2ce7ea
Author: Emma Weaver <emma@waeaves.com>
Date:   Sat,  2 May 2026 12:10:27 -0400

implemented time option, removed statistics option

Diffstat:
MREADME10+++++-----
Mexamples/lesson.sh24++++++++++++------------
Mexamples/words.sh2+-
Mmain.c61++++++++++++++++++++++++++++++++++++++++++-------------------
Mopt.c64++++++++++++++++++++++++++++++++++++++++++----------------------
Mopt.h3+--
Mtyper.c3+--
Mtyper.h4+++-
8 files changed, 107 insertions(+), 64 deletions(-)

diff --git a/README b/README @@ -6,8 +6,10 @@ examples for some intended use cases. Features: - minimal, only C and standard libs, short and clean code + - responsive UI with pretty colors - skip-to-next-chunk error determination (similar to monkeytype) - - statistics output + - timed mode + - output statistics (to stdout or to a file) -------------------------------------------------------------------------------- # Build/installation @@ -22,8 +24,6 @@ To install it, edit config.mk to reflect your system's configuration, then run -------------------------------------------------------------------------------- # TODO -------------------------------------------------------------------------------- - - multiple-line, auto-wrapped-at-80 input - - timed mode - - output file - - mistakes output + - wrap at 80 / support for long prompts + - output data about mistakes made diff --git a/examples/lesson.sh b/examples/lesson.sh @@ -24,21 +24,21 @@ echo "If it gets too hard, don't be afraid to ctrl+C and redo previous lessons." echo "" echo "Introducing the new letters..." -typie -l "$(./words.sh "$NEW_LETTERS" 6 12)" -typie -l "$(./words.sh "$NEW_LETTERS" 6 12)" +typie -lf /dev/null "$(./words.sh "$NEW_LETTERS" 6 12)" +typie -lf /dev/null "$(./words.sh "$NEW_LETTERS" 6 12)" echo "Integrating with some old letters..." -typie "$(./words.sh "$(old_letters $EASY)$NEW_LETTERS" 5 8)" -typie "$(./words.sh "$(old_letters $EASY)$NEW_LETTERS" 5 8)" -typie "$(./words.sh "$(old_letters $EASY)$NEW_LETTERS" 5 8)" -typie "$(./words.sh "$(old_letters $MEDIUM)$NEW_LETTERS" 5 8)" -typie "$(./words.sh "$(old_letters $MEDIUM)$NEW_LETTERS" 5 8)" -typie "$(./words.sh "$(old_letters $HARD)$NEW_LETTERS" 5 8)" -typie "$(./words.sh "$(old_letters $HARD)$NEW_LETTERS" 5 8)" +typie -f /dev/null "$(./words.sh "$(old_letters $EASY)$NEW_LETTERS" 5 8)" +typie -f /dev/null "$(./words.sh "$(old_letters $EASY)$NEW_LETTERS" 5 8)" +typie -f /dev/null "$(./words.sh "$(old_letters $EASY)$NEW_LETTERS" 5 8)" +typie -f /dev/null "$(./words.sh "$(old_letters $MEDIUM)$NEW_LETTERS" 5 8)" +typie -f /dev/null "$(./words.sh "$(old_letters $MEDIUM)$NEW_LETTERS" 5 8)" +typie -f /dev/null "$(./words.sh "$(old_letters $HARD)$NEW_LETTERS" 5 8)" +typie -f /dev/null "$(./words.sh "$(old_letters $HARD)$NEW_LETTERS" 5 8)" echo "All together now!" -typie "$(./words.sh "$OLD_LETTERS$NEW_LETTERS" 5 8)" -typie "$(./words.sh "$OLD_LETTERS$NEW_LETTERS" 5 8)" -typie "$(./words.sh "$OLD_LETTERS$NEW_LETTERS" 5 8)" +typie -f /dev/null "$(./words.sh "$OLD_LETTERS$NEW_LETTERS" 5 8)" +typie -f /dev/null "$(./words.sh "$OLD_LETTERS$NEW_LETTERS" 5 8)" +typie -f /dev/null "$(./words.sh "$OLD_LETTERS$NEW_LETTERS" 5 8)" echo "All done! :3" diff --git a/examples/words.sh b/examples/words.sh @@ -30,6 +30,6 @@ if [ $FEW_WORDS = "true" ]; then done echo "$PROMPT" else - echo "$WORDS" | shuf -n $LENGTH | tr '\n' ' ' + echo "$WORDS" | shuf -n $LENGTH | xargs echo fi diff --git a/main.c b/main.c @@ -13,17 +13,24 @@ char prompt_buffer[MAX_BUFFER_LENGTH]; int jumps_buffer[MAX_BUFFER_LENGTH]; const char * usage =\ -"Usage: typie [-ls] [-f FILE] PROMPT" "\n"\ +"Usage: typie [-l] [-f FILE] [-t TIME] PROMPT" "\n"\ "\n"\ " -l no backspacing, incorrect keys not accepted" "\n"\ -" -s output statistics after completion" "\n"\ " -f FILE file to write output to (stdout if unspecified)" "\n"\ +" -t TIME exit after TIME seconds" "\n"\ /* " -m output mistakes after completion" "\n" */\ -/* " -t TIME terminate after TIME milliseconds" "\n" */\ "\n"\ "See the man page for more details." "\n"\ "\n"; +long int +get_time_millis() +{ + struct timespec now; + timespec_get(&now, TIME_UTC); + return now.tv_sec * 1000 + now.tv_nsec / 1000000; +} + int type(State * state) { @@ -31,7 +38,22 @@ type(State * state) int started = FALSE; for (;;) { - keypress_kind = handle_keypress(state); + char c = getchar(); + + long int keypress_millis = get_time_millis(); + long int elapsed_millis = keypress_millis - state->start_millis; + if ( + started && + state->duration_millis != 0 && + elapsed_millis > state->duration_millis + ) { + state->timed_out = TRUE; + return 0; + } + + state->end_millis = keypress_millis; + + keypress_kind = handle_keypress(state, c); fflush(stdout); switch (keypress_kind) { @@ -46,7 +68,7 @@ type(State * state) state->chars_total++; if (!started) { started = TRUE; - state->start_millis = time(NULL); + state->start_millis = get_time_millis(); fflush(stdout); } break; @@ -70,27 +92,30 @@ write_output(State state) FILE * out; int correct = state.chars_correct, total = state.chars_total; - double time_millis = state.end_millis - state.start_millis; + + double time_millis = state.timed_out ? + state.duration_millis : + state.end_millis - state.start_millis; double time_seconds = time_millis / 1000; double time_minutes = time_seconds / 60; + int wpm = (correct / 5.0) / time_minutes; + int accuracy = ((double) correct / total) * 100; out = stdout; if (state.flags & FLAG_FILE) out = fopen(state.outfile, "w"); - if (state.flags & FLAG_STATISTICS_OUTPUT) { - fprintf( - out, - "time:" "\t\t" "%f" "\n"\ - "wpm:" "\t\t" "%d" "\n"\ - "accuracy:" "\t" "%d%%" "\n", - time_millis/1000, - wpm, - accuracy - ); - } + fprintf( + out, + "millis %d" "\n"\ + "wpm %d" "\n"\ + "accuracy %d%%" "\n", + (int) time_millis, + wpm, + accuracy + ); if (state.flags & FLAG_FILE) fclose(out); @@ -139,8 +164,6 @@ main(int argc, const char * argv[]) exit(1); } - state.end_millis = time(NULL); - restore_termios(termios_original); printf(COLOR_RESET); diff --git a/opt.c b/opt.c @@ -1,6 +1,7 @@ #include <stdlib.h> #include <stdio.h> +#include <limits.h> #include "opt.h" #include "typer.h" @@ -12,6 +13,18 @@ #define PROCESS_GROUP_SUCCESS 0 #define PROCESS_GROUP_FAILURE 1 #define PROCESS_GROUP_SKIP 2 +#define PARSE_INT_FAILURE -1 + +int +parse_int(const char * arg) +{ + char * endptr; + int ret; + ret = strtol(arg, &endptr, 10); + if (* endptr != '\0' || ret > INT_MAX || ret < INT_MIN) + return PARSE_INT_FAILURE; + return ret; +} int get_flag(char c) @@ -20,31 +33,12 @@ get_flag(char c) case 'l': return FLAG_LEARN_MODE; case 'f': return FLAG_FILE; case 't': return FLAG_TIME; - case 's': return FLAG_STATISTICS_OUTPUT; } return 0; } int -process_file_flag( - State * state, - const char * group, - const char * next, - int index -) -{ - if (next == NULL || group[index+1] != '\0') { - ERROR("Filename must follow -f." "\n\n"); - return PROCESS_GROUP_FAILURE; - } - - state->outfile = next; - - return PROCESS_GROUP_SKIP; -} - -int process_flag_group(State * state, const char * group, const char * next) { int index; @@ -75,10 +69,36 @@ process_flag_group(State * state, const char * group, const char * next) return PROCESS_GROUP_FAILURE; } - if (flag == FLAG_FILE) - return process_file_flag(state, group, next, index); - state->flags |= flag; + + if (flag == FLAG_FILE) { + if (next == NULL || group[index+1] != '\0') { + ERROR("Filename must follow -f." "\n\n"); + return PROCESS_GROUP_FAILURE; + } + + state->outfile = next; + + return PROCESS_GROUP_SKIP; + } + + if (flag == FLAG_TIME) { + long int duration_seconds; + if (next == NULL || group[index+1] != '\0') { + ERROR("Integer must follow -t." "\n\n"); + return PROCESS_GROUP_FAILURE; + } + + duration_seconds = parse_int(next); + if (duration_seconds == PARSE_INT_FAILURE) { + ERROR("Integer must follow -t." "\n\n"); + return PROCESS_GROUP_FAILURE; + } + state->duration_millis = duration_seconds * 1000; + + return PROCESS_GROUP_SKIP; + } + index++; } diff --git a/opt.h b/opt.h @@ -4,8 +4,7 @@ typedef enum { FLAG_LEARN_MODE = 1 << 0, FLAG_FILE = 1 << 1, - FLAG_TIME = 1 << 2, - FLAG_STATISTICS_OUTPUT = 1 << 3 + FLAG_TIME = 1 << 2 } Flag; int set_flags(State * state, int argc, const char * argv[]); diff --git a/typer.c b/typer.c @@ -225,9 +225,8 @@ start_typing(State * state) } int -handle_keypress(State * state) +handle_keypress(State * state, char c) { - char c = getchar(); char expected = state->prompt[state->index]; if (c == KEY_CTRL_C || c == KEY_CTRL_D || c == KEY_ESCAPE) diff --git a/typer.h b/typer.h @@ -55,16 +55,18 @@ typedef struct { /* options */ int flags; const char * outfile; + long int duration_millis; /* timing */ long int start_millis; long int end_millis; + int timed_out; } State; void setup_termios(struct termios termios0); void restore_termios(struct termios termios0); void start_typing(State * state); -int handle_keypress(State * state); +int handle_keypress(State * state, char c); #endif /* TYPERH */

This webpage is intended to be an accessible preview of this repository. To get a fuller picture, clone it and use the git CLI.