diff --git a/main.go b/main.go index dbf470f..27bdcc4 100644 --- a/main.go +++ b/main.go @@ -5,62 +5,83 @@ import ( "fmt" "log" "os" + "os/signal" + "syscall" "time" "tipitypy/colorizer" + "tipitypy/db" "tipitypy/reader" "golang.org/x/term" ) -func finish(start time.Time) { - past := time.Now().Sub(start) +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.Printf("Correct: %d ; False: %d ; Skipped: %d\r\n", globalStat.Correct, globalStat.False, globalStat.Skipped) fmt.Println(past) - fmt.Print("\r\n") - fmt.Printf("CPM: %.2f ;; WPM: %.2f\r\n", - float64(globalStat.Correct)/past.Minutes(), - float64(globalStat.Words+1)/past.Minutes()) } -func main() { - logFile, err := os.OpenFile("ttt.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) - if err != nil { - panic(err) +func ChooseToPrintColorized(r rune) string { + if Color { + return colorizer.Accept(r) } - defer logFile.Close() - log.SetOutput(logFile) + 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 { - panic(err) + return nil, ExitStateUnspecified, fmt.Errorf("reader.GetSourceLine: %w", err) } log.Print("Start of tipitypy") - colorizer.PrintColorized(source) - fmt.Printf("\n") - // - // Put the terminal in raw mode to read characters as they are typed - oldState, err := term.MakeRaw(int(os.Stdin.Fd())) - if err != nil { - panic(err) + if Color { + colorizer.PrintColorized(source) + } else { + fmt.Println(source) } - defer term.Restore(int(os.Stdin.Fd()), oldState) // Restore terminal state at the end + fmt.Printf("\r\n") // p := []rune(source) i := 0 // startTime := time.Now() - defer finish(startTime) + finished := true + stat := &db.Stat{} + defer finish(startTime, stat, finished) reader := bufio.NewReader(os.Stdin) for { r, _, err := reader.ReadRune() if err != nil { - panic(err) + 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 { - break + finished = false + return stat, ExitStateBreak, nil } // CTRL + D if r == 4 { @@ -69,7 +90,7 @@ func main() { // CTRL + S if r == 19 { globalStat.Skipped++ - fmt.Printf("%s", colorizer.Accept(p[i])) + fmt.Printf("%s", ChooseToPrintColorized(p[i])) i++ continue } @@ -79,7 +100,7 @@ func main() { globalStat.Words++ } globalStat.Correct++ - fmt.Printf("%s", colorizer.Accept(r)) + fmt.Printf("%s", ChooseToPrintColorized(r)) i++ if i == len(p) { break @@ -88,4 +109,55 @@ func main() { 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) + } + } + } } diff --git a/options.go b/options.go new file mode 100644 index 0000000..87e159c --- /dev/null +++ b/options.go @@ -0,0 +1,24 @@ +package main + +import "os" + +var ( + IsEndless = false + Color = true +) + +const ( + EndlessOpt = "-e" + NoColorOpt = "-nc" +) + +func ParseOptions() { + for _, arg := range os.Args { + switch arg { + case EndlessOpt: + IsEndless = true + case NoColorOpt: + Color = false + } + } +}