package main import ( "bufio" "fmt" "log" "os" "os/signal" "syscall" "time" "tipitypy/colorizer" "tipitypy/db" "tipitypy/reader" "golang.org/x/term" ) func finish(start time.Time, stat *db.Stat, finished bool) { past := time.Since(start) stat.Skipped = globalStat.Skipped stat.Correct = globalStat.Correct stat.False = globalStat.False stat.Words = globalStat.Words stat.Finished = finished stat.CPM = float64(globalStat.Correct) / past.Minutes() stat.WPM = float64(globalStat.Words+1) / past.Minutes() stat.TimeTaken = past.Milliseconds() fmt.Printf("\r\n%s", colorizer.Colors.Reset()) fmt.Println(past) } func ChooseToPrintColorized(r rune) string { if Color { return colorizer.Accept(r) } return string(r) } type ExitState int const ( ExitStateUnspecified = 1 + iota ExitStateBreak ExitStateQuit ExitStateSuccess ) func SingleCycle() (*db.Stat, int, error) { source, err := reader.GetSourceLine() if err != nil { return nil, ExitStateUnspecified, fmt.Errorf("reader.GetSourceLine: %w", err) } log.Print("Start of tipitypy") if Color { colorizer.PrintColorized(source) } else { fmt.Println(source) } fmt.Printf("\r\n") // p := []rune(source) i := 0 // startTime := time.Now() finished := true stat := &db.Stat{} defer finish(startTime, stat, finished) reader := bufio.NewReader(os.Stdin) for { r, _, err := reader.ReadRune() if err != nil { return stat, ExitStateUnspecified, fmt.Errorf("reader.ReadRune: %w", err) } log.Printf("Read %c ; %d as rune", r, r) // CTRL + Q if r == 17 { finished = false return stat, ExitStateQuit, nil } // CTRL + C if r == 3 { finished = false return stat, ExitStateBreak, nil } // CTRL + D if r == 4 { // TODO: Delete } // CTRL + S if r == 19 { globalStat.Skipped++ fmt.Printf("%s", ChooseToPrintColorized(p[i])) i++ continue } // Check if p[i] == r { if r == ' ' { globalStat.Words++ } globalStat.Correct++ fmt.Printf("%s", ChooseToPrintColorized(r)) i++ if i == len(p) { break } } else { globalStat.False++ } } globalStat.Words++ return stat, ExitStateSuccess, nil } func main() { logFile, err := os.OpenFile("ttt.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) if err != nil { panic(err) } defer logFile.Close() log.SetOutput(logFile) ParseOptions() sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM) // Put the terminal in raw mode to read characters as they are typed oldState, err := term.MakeRaw(int(os.Stdin.Fd())) if err != nil { fmt.Printf("term.MakeRaw: %v\n", err) os.Exit(1) } defer term.Restore(int(os.Stdin.Fd()), oldState) // Restore terminal state at the end // This way we can still defer term.Restore function c := make(chan bool, 1) c <- true if IsEndless { go func() { for { c <- true } }() } else { close(c) } // outer: for range c { stat, state, err := SingleCycle() if err != nil { log.Printf("ERROR: %v", err) } switch state { case ExitStateQuit: break outer case ExitStateSuccess: if err := db.Connect().Create(stat).Error; err != nil { log.Printf("ERROR: dbc.Create: %v", err) } } } }