Init state
This commit is contained in:
commit
d71b71f545
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
tipitypy
|
||||
*.log
|
||||
bin/*
|
||||
*.db
|
||||
|
||||
_test*
|
10
Makefile
Normal file
10
Makefile
Normal 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
59
cmd/scrapper/main.go
Normal 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
49
colorizer/colors.go
Normal 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
63
colorizer/easer.go
Normal 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
16
colorizer/reader.go
Normal 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
48
db/db.go
Normal 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
34
db/word.go
Normal 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
17
go.mod
Normal 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
16
go.sum
Normal 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
91
main.go
Normal 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
17
reader/db_reader.go
Normal 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
|
||||
}
|
Loading…
Reference in New Issue
Block a user