Init state

This commit is contained in:
qowevisa 2025-02-15 19:06:22 +02:00
commit d71b71f545
13 changed files with 436 additions and 0 deletions

6
.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
tipitypy
*.log
bin/*
*.db
_test*

10
Makefile Normal file
View File

@ -0,0 +1,10 @@
def: scrapper tipitypy
@
scrapper:
go build -o ./bin/$@ ./cmd/$@
tipitypy:
go build
.PHONY: tipitypy

59
cmd/scrapper/main.go Normal file
View File

@ -0,0 +1,59 @@
package main
import (
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"strconv"
"tipitypy/db"
)
func AddToDbNWords(n int64) (int, error) {
url := fmt.Sprintf("https://random-word-api.herokuapp.com/word?number=%d", n)
resp, err := http.Get(url)
if err != nil {
return 0, fmt.Errorf("http.Get: %w", err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return 0, fmt.Errorf("io.ReadAll: %w", err)
}
var words []string
err = json.Unmarshal(body, &words)
if err != nil {
return 0, fmt.Errorf("json.Unmarshal: %w", err)
}
dbc := db.Connect()
successes := 0
for _, wordValue := range words {
word := &db.Word{Value: wordValue}
if err := dbc.Create(word).Error; err != nil {
continue
}
successes++
}
return successes, nil
}
func main() {
if len(os.Args) > 1 {
if val, err := strconv.ParseInt(os.Args[1], 10, 64); err == nil {
res, err := AddToDbNWords(val)
if err != nil {
fmt.Printf("ERROR: %v", err)
os.Exit(1)
}
fmt.Printf("Successfully added %d words to database!\n", res)
os.Exit(0)
}
}
res, err := AddToDbNWords(10)
if err != nil {
fmt.Printf("ERROR: %v", err)
os.Exit(1)
}
fmt.Printf("Successfully added %d words to database!\n", res)
}

49
colorizer/colors.go Normal file
View File

@ -0,0 +1,49 @@
package colorizer
type colors struct{}
var Colors colors
func (c colors) Reset() string {
return "\033[38;5;7m"
}
func (c colors) Red() string {
return "\033[38;5;1m"
}
func (c colors) Green() string {
return "\033[38;5;2m"
}
func (c colors) Yellow() string {
return "\033[38;5;3m"
}
func (c colors) Blue() string {
return "\033[38;5;4m"
}
func (c colors) Purple() string {
return "\033[38;5;5m"
}
func (c colors) Cyan() string {
return "\033[38;5;6m"
}
func (c colors) Red2() string {
return "\033[38;5;9m"
}
func (c colors) Green2() string {
return "\033[38;5;10m"
}
func (c colors) Yellow2() string {
return "\033[38;5;11m"
}
func (c colors) Blue2() string {
return "\033[38;5;12m"
}

63
colorizer/easer.go Normal file
View File

@ -0,0 +1,63 @@
package colorizer
import (
"fmt"
"log"
)
type RuneHandler func(rune) (bool, string)
type Handler func([]rune, string) RuneHandler
func General(accept []rune, color string) RuneHandler {
return func(r rune) (bool, string) {
for _, t := range accept {
if t == r {
return true, color
}
}
return false, ""
}
}
// Left
var LeftLittleFinger = General([]rune{'z', 'a', 'q', '1', '2'}, Colors.Red())
var LeftRingFinger = General([]rune{'x', 's', 'w', '3'}, Colors.Green())
var LeftMiddleFinger = General([]rune{'c', 'd', 'e', '4'}, Colors.Yellow())
var LeftIndexFinger = General([]rune{'v', 'f', 'r', '5', 'b', 'g', 't', '6'}, Colors.Blue())
var LeftThumpFinger = General([]rune{}, "")
// Right
var RightThumpFinger = General([]rune{}, "")
var RightIndexFinger = General([]rune{'n', 'h', 'y', '7', 'm', 'j', 'u', '8'}, Colors.Blue2())
var RightMiddleFinger = General([]rune{',', 'k', 'i', '9'}, Colors.Yellow2())
var RightRingFinger = General([]rune{'.', 'l', 'o', '0'}, Colors.Green2())
var RightLittleFinger = General([]rune{'/', ';', 'p', '-', '\\', '\'', '[', ']', '='}, Colors.Red2())
var handlers = [8]RuneHandler{
LeftLittleFinger,
LeftRingFinger,
LeftMiddleFinger,
LeftIndexFinger,
RightIndexFinger,
RightMiddleFinger,
RightRingFinger,
RightLittleFinger,
}
func Accept(r rune) string {
var buffer string
var wasReallyAccpeted bool
for i, h := range handlers {
accepted, color := h(r)
if accepted {
log.Printf("%sRune %c was accepted by handler with %d id%s", color, r, i, Colors.Reset())
buffer += fmt.Sprintf("%s%c", color, r)
wasReallyAccpeted = accepted
break
}
}
if !wasReallyAccpeted {
buffer += fmt.Sprintf("%s%c", Colors.Reset(), r)
}
return buffer
}

16
colorizer/reader.go Normal file
View File

@ -0,0 +1,16 @@
package colorizer
import (
"fmt"
"log"
)
func PrintColorized(s string) {
var buffer string
for _, r := range s {
log.Printf("Trying to colorize %c", r)
buffer += Accept(r)
}
buffer += Colors.Reset()
fmt.Println(buffer)
}

48
db/db.go Normal file
View File

@ -0,0 +1,48 @@
package db
import (
"log"
"os"
"sync"
"time"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
"gorm.io/gorm/logger"
)
var (
udb *gorm.DB
conMu sync.Mutex
)
func Connect() *gorm.DB {
conMu.Lock()
defer conMu.Unlock()
if udb != nil {
return udb
}
logFile, err := os.OpenFile("db.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
panic(err)
}
newLogger := logger.New(
log.New(logFile, "\r\n", log.LstdFlags),
logger.Config{
SlowThreshold: time.Second,
LogLevel: logger.Error,
IgnoreRecordNotFoundError: true,
ParameterizedQueries: true,
Colorful: false,
},
)
gormDB, err := gorm.Open(sqlite.Open("tipitypy.db"), &gorm.Config{
Logger: newLogger,
})
if err != nil {
log.Panic(err)
}
newUDB := gormDB
gormDB.AutoMigrate(&Word{})
return newUDB
}

34
db/word.go Normal file
View File

@ -0,0 +1,34 @@
package db
import (
"errors"
"gorm.io/gorm"
)
type Word struct {
gorm.Model
Value string
}
var (
ErrCardValueEmpty = errors.New("the 'Value' field for 'Word' cannot be empty")
ErrCardValueNotUnique = errors.New("the 'Value' field for 'Word' have to be unique for user")
)
func (c *Word) BeforeSave(tx *gorm.DB) error {
if c.Value == "" {
return ErrCardValueEmpty
}
var dup Word
if err := tx.Find(&dup, Word{Value: c.Value}).Error; err != nil {
return err
}
if c.ID != dup.ID && dup.ID != 0 {
return ErrCardValueNotUnique
}
return nil
}

17
go.mod Normal file
View File

@ -0,0 +1,17 @@
module tipitypy
go 1.21.6
require (
golang.org/x/term v0.17.0
gorm.io/driver/sqlite v1.5.7
gorm.io/gorm v1.25.12
)
require (
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/mattn/go-sqlite3 v1.14.22 // indirect
golang.org/x/sys v0.17.0 // indirect
golang.org/x/text v0.14.0 // indirect
)

16
go.sum Normal file
View File

@ -0,0 +1,16 @@
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
gorm.io/driver/sqlite v1.5.7 h1:8NvsrhP0ifM7LX9G4zPB97NwovUakUxc+2V2uuf3Z1I=
gorm.io/driver/sqlite v1.5.7/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4=
gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=

91
main.go Normal file
View File

@ -0,0 +1,91 @@
package main
import (
"bufio"
"fmt"
"log"
"os"
"time"
"tipitypy/colorizer"
"tipitypy/reader"
"golang.org/x/term"
)
func finish(start time.Time) {
past := time.Now().Sub(start)
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)
}
defer logFile.Close()
log.SetOutput(logFile)
source, err := reader.GetSourceLine()
if err != nil {
panic(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)
}
defer term.Restore(int(os.Stdin.Fd()), oldState) // Restore terminal state at the end
//
p := []rune(source)
i := 0
//
startTime := time.Now()
defer finish(startTime)
reader := bufio.NewReader(os.Stdin)
for {
r, _, err := reader.ReadRune()
if err != nil {
panic(err)
}
log.Printf("Read %c ; %d as rune", r, r)
// CTRL + C
if r == 3 {
break
}
// CTRL + D
if r == 4 {
// TODO: Delete
}
// CTRL + S
if r == 19 {
globalStat.Skipped++
fmt.Printf("%s", colorizer.Accept(p[i]))
i++
continue
}
// Check
if p[i] == r {
if r == ' ' {
globalStat.Words++
}
globalStat.Correct++
fmt.Printf("%s", colorizer.Accept(r))
i++
if i == len(p) {
break
}
} else {
globalStat.False++
}
}
}

17
reader/db_reader.go Normal file
View File

@ -0,0 +1,17 @@
package reader
import (
"fmt"
"strings"
"tipitypy/db"
)
func GetSourceLine() (string, error) {
dbc := db.Connect()
var res []string
err := dbc.Raw(`SELECT value FROM words WHERE deleted_at IS NULL ORDER BY RANDOM() LIMIT 10;`).Scan(&res).Error
if err != nil {
return "", fmt.Errorf("dbc.Raw: %w", err)
}
return strings.Join(res, " "), nil
}

10
stat.go Normal file
View File

@ -0,0 +1,10 @@
package main
type Stat struct {
Skipped uint
Correct uint
False uint
Words uint
}
var globalStat Stat