diff options
-rw-r--r-- | .gitignore | 4 | ||||
-rw-r--r-- | Makefile | 10 | ||||
-rw-r--r-- | app.go | 160 | ||||
-rw-r--r-- | cmd/dict/main.go | 27 | ||||
-rw-r--r-- | cmd/importer/importer.go | 131 | ||||
-rw-r--r-- | cmd/ui/ui.go | 105 | ||||
-rw-r--r-- | db/db.go (renamed from db.go) | 9 | ||||
-rw-r--r-- | go.mod | 6 | ||||
-rw-r--r-- | go.sum | 8 | ||||
-rw-r--r-- | main.go | 25 |
10 files changed, 290 insertions, 195 deletions
@@ -1 +1,5 @@ bin/ +ext/*.so + +dict.txt +main.dict @@ -1,13 +1,17 @@ BIN?=bin/dict +GO_BUILD=go build -v --tags "fts5" +GO_RUN=go run -v --tags "fts5" + buid: ext - go build -v --tags "fts5" -o $(BIN) . + $(GO_BUILD) -o $(BIN) ./cmd/dict/main.go run: ext - go run -v --tags "fts5" . + $(GO_RUN) ./cmd/dict/main.go + import: ext - go run -v --tags "fts5" . import + $(GO_RUN) ./cmd/dict/main.go import ext: gcc -shared -o ext/libsqlite3ext.so -fPIC ext/spellfix.c @@ -1,160 +0,0 @@ -package main - -import ( - "bufio" - "bytes" - "context" - "fmt" - "io" - "log/slog" - "math" - "os" - "strings" - - "github.com/rivo/tview" -) - -const ( - memory = ":memory:" -) - -func run(ctx context.Context, name string) error { - db, err := Open(memory) - if err != nil { - return err - } - - err = db.Restore(ctx, name) - if err != nil { - return err - } - - list := tview.NewList() - - input := tview.NewInputField(). - SetLabel("S:"). - SetChangedFunc(func(v string) { - list.Clear() - - words, err := db.SelectDict(ctx, v, 100) - if err != nil { - return - } - - for _, w := range words { - list.AddItem(w.Word, w.Line, 0, nil) - } - }). - SetAutocompleteFunc(func(v string) []string { - if len(v) == 0 { - return []string{} - } - - vs, err := db.SelectSpell(ctx, v) - if err != nil { - slog.Error("Error select spelling", "error", err) - return []string{} - } - - return vs - }) - - grid := tview.NewGrid(). - SetRows(1, 0, 3). - AddItem(input, 0, 0, 1, 3, 0, 0, false). - AddItem(list, 1, 0, 1, 3, 0, 0, false) - - err = tview.NewApplication(). - SetRoot(grid, true). - SetFocus(input). - Run() - - return err -} - -func importDict(ctx context.Context, name string) error { - db, err := Open(memory) - if err != nil { - return err - } - err = db.Migrate(ctx) - if err != nil { - return err - } - - file, err := os.Open("dict.txt") - if err != nil { - return err - } - defer file.Close() - - count := 0 - total, err := LineCounter(file) - if err != nil { - return err - } - - _, err = file.Seek(0, 0) - if err != nil { - return err - } - - scanner := bufio.NewScanner(file) - for scanner.Scan() { - if strings.HasPrefix(scanner.Text(), "#") || scanner.Text() == "" { - continue - } - - if err := db.InsertLine(ctx, scanner.Text()); err != nil { - return err - } - count++ - - if (count % 1234) == 0 { - fmt.Print("\033[G\033[K") // move the cursor left and clear the line - per := math.Ceil((float64(count) / float64(total)) * 100.0) - fmt.Printf("%d/%d (%.0f%%)", count, total, per) - } - } - - fmt.Printf("Consolidating") - err = db.Consolidade(ctx) - if err != nil { - return err - } - - err = db.Backup(ctx, name) - if err != nil { - return err - } - return nil -} - -func LineCounter(r io.Reader) (int, error) { - var count int - const lineBreak = '\n' - - buf := make([]byte, bufio.MaxScanTokenSize) - - for { - bufferSize, err := r.Read(buf) - if err != nil && err != io.EOF { - return 0, err - } - - var buffPosition int - for { - i := bytes.IndexByte(buf[buffPosition:], lineBreak) - if i == -1 || bufferSize == buffPosition { - break - } - buffPosition += i + 1 - count++ - } - if err == io.EOF { - break - } - } - - return count, nil -} diff --git a/cmd/dict/main.go b/cmd/dict/main.go new file mode 100644 index 0000000..09e9412 --- /dev/null +++ b/cmd/dict/main.go @@ -0,0 +1,27 @@ +package main + +import ( + "log/slog" + "os" + + "github.com/urfave/cli/v2" + + "git.gabrielgio.me/dict/cmd/importer" + "git.gabrielgio.me/dict/cmd/ui" +) + +func main() { + app := &cli.App{ + Name: "dict", + Usage: "interactive dictionary", + Commands: []*cli.Command{ + importer.ImportCommand, + ui.UICommand, + }, + } + + if err := app.Run(os.Args); err != nil { + slog.Error("Error running application", "error", err) + os.Exit(1) + } +} diff --git a/cmd/importer/importer.go b/cmd/importer/importer.go new file mode 100644 index 0000000..18a7a7b --- /dev/null +++ b/cmd/importer/importer.go @@ -0,0 +1,131 @@ +package importer + +import ( + "bufio" + "bytes" + "context" + "fmt" + "io" + "math" + "os" + "strings" + + "github.com/urfave/cli/v2" + + "git.gabrielgio.me/dict/db" +) + +var ImportCommand = &cli.Command{ + Name: "import", + Usage: "convert dict.cc dictionary into a queryable sqlite format.", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "output", + Value: "main.dict", + Usage: "Dictionary database location", + }, + &cli.StringFlag{ + Name: "input", + Value: "dict.txt", + Usage: "Dict.cc txt dictionary file", + }, + }, + Action: func(cCtx *cli.Context) error { + input := cCtx.String("input") + output := cCtx.String("output") + return Import(context.Background(), input, output) + }, +} + +func Import(ctx context.Context, txtInput, sqliteOutput string) error { + db, err := db.Open(":memory:") + if err != nil { + return err + } + err = db.Migrate(ctx) + if err != nil { + return err + } + + file, err := os.Open(txtInput) + if err != nil { + return err + } + defer file.Close() + + count := 0 + total, err := lineCounter(file) + if err != nil { + return err + } + + _, err = file.Seek(0, 0) + if err != nil { + return err + } + + scanner := bufio.NewScanner(file) + for scanner.Scan() { + if strings.HasPrefix(scanner.Text(), "#") || scanner.Text() == "" { + continue + } + + var ( + p = strings.SplitN(scanner.Text(), "\t", 2) + word = p[0] + line = strings.ReplaceAll(p[1], "\t", " ") + ) + + if err := db.InsertLine(ctx, word, line); err != nil { + return err + } + count++ + + if (count % 1234) == 0 { + fmt.Print("\033[G\033[K") // move the cursor left and clear the line + per := math.Ceil((float64(count) / float64(total)) * 100.0) + fmt.Printf("%d/%d (%.0f%%)", count, total, per) + } + } + + fmt.Printf("Consolidating") + err = db.Consolidade(ctx) + if err != nil { + return err + } + + err = db.Backup(ctx, sqliteOutput) + if err != nil { + return err + } + return nil +} + +func lineCounter(r io.Reader) (int, error) { + var count int + const lineBreak = '\n' + + buf := make([]byte, bufio.MaxScanTokenSize) + + for { + bufferSize, err := r.Read(buf) + if err != nil && err != io.EOF { + return 0, err + } + + var buffPosition int + for { + i := bytes.IndexByte(buf[buffPosition:], lineBreak) + if i == -1 || bufferSize == buffPosition { + break + } + buffPosition += i + 1 + count++ + } + if err == io.EOF { + break + } + } + + return count, nil +} diff --git a/cmd/ui/ui.go b/cmd/ui/ui.go new file mode 100644 index 0000000..82c0bc5 --- /dev/null +++ b/cmd/ui/ui.go @@ -0,0 +1,105 @@ +package ui + +import ( + "context" + "fmt" + "log/slog" + + "github.com/gdamore/tcell/v2" + "github.com/rivo/tview" + "github.com/urfave/cli/v2" + + "git.gabrielgio.me/dict/db" +) + +const ( + memory = ":memory:" +) + +var UICommand = &cli.Command{ + Name: "ui", + Usage: "interactive dictionary", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "filename", + Value: "main.dict", + Usage: "Dictionary database location", + }, + }, + Action: func(cCtx *cli.Context) error { + name := cCtx.String("lang") + return Run(context.Background(), name) + }, +} + +func Run(ctx context.Context, name string) error { + db, err := db.Open(memory) + if err != nil { + return err + } + + err = db.Restore(ctx, name) + if err != nil { + return err + } + + textView := tview.NewTextView(). + SetDynamicColors(true). + SetRegions(true) + + input := tview.NewInputField(). + SetChangedFunc(func(v string) { + textView.Clear() + + words, err := db.SelectDict(ctx, v, 100) + if err != nil { + return + } + + lastWord := "" + for _, w := range words { + + if lastWord == w.Word { + fmt.Fprintf(textView, "%s\n", w.Line) + } else if lastWord == "" { + fmt.Fprintf(textView, "[bold]%s[normal]\n", w.Word) + fmt.Fprintf(textView, "%s\n", w.Line) + } else { + fmt.Fprintf(textView, "\n[bold]%s[normal]\n", w.Word) + fmt.Fprintf(textView, "%s\n", w.Line) + } + + lastWord = w.Word + } + }). + SetAutocompleteFunc(func(v string) []string { + if len(v) == 0 { + return []string{} + } + + vs, err := db.SelectSpell(ctx, v) + if err != nil { + slog.Error("Error select spelling", "error", err) + return []string{} + } + + return vs + }) + + input.SetDoneFunc(func(key tcell.Key) { + textView.Clear() + input.SetText("") + }) + + grid := tview.NewGrid(). + SetRows(1, 0, 3). + AddItem(input, 0, 0, 1, 3, 0, 0, false). + AddItem(textView, 1, 0, 1, 3, 0, 0, false) + + err = tview.NewApplication(). + SetRoot(grid, true). + SetFocus(input). + Run() + + return err +} @@ -1,10 +1,9 @@ -package main +package db import ( "context" "database/sql" "fmt" - "strings" "github.com/mattn/go-sqlite3" ) @@ -105,13 +104,11 @@ func (d *DB) SelectSpell(ctx context.Context, query string) ([]string, error) { } -func (d *DB) InsertLine(ctx context.Context, line string) error { - p := strings.SplitN(line, "\t", 2) - +func (d *DB) InsertLine(ctx context.Context, word, line string) error { _, err := d.db.ExecContext( ctx, `INSERT INTO words (WORD, LINE) VALUES(?, ?);`, - p[0], strings.ReplaceAll(p[1], "\t", " "), + word, line, ) if err != nil { return err @@ -4,15 +4,19 @@ go 1.21.9 require ( github.com/gdamore/tcell/v2 v2.7.4 + github.com/mattn/go-sqlite3 v1.14.22 github.com/rivo/tview v0.0.0-20240413115534-b0d41c484b95 + github.com/urfave/cli/v2 v2.27.1 ) require ( + github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect github.com/gdamore/encoding v1.0.0 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect - github.com/mattn/go-sqlite3 v1.14.22 // indirect github.com/rivo/uniseg v0.4.7 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect golang.org/x/sys v0.17.0 // indirect golang.org/x/term v0.17.0 // indirect golang.org/x/text v0.14.0 // indirect @@ -1,3 +1,5 @@ +github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko= github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg= github.com/gdamore/tcell/v2 v2.7.4 h1:sg6/UnTM9jGpZU+oFYAsDahfchWAFW8Xx2yFinNSAYU= @@ -14,6 +16,12 @@ github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJ github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/urfave/cli/v2 v2.27.1 h1:8xSQ6szndafKVRmfyeUMxkNUJQMjL1F2zmsZ+qHpfho= +github.com/urfave/cli/v2 v2.27.1/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ= +github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= +github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= diff --git a/main.go b/main.go deleted file mode 100644 index 5ba494f..0000000 --- a/main.go +++ /dev/null @@ -1,25 +0,0 @@ -package main - -import ( - "context" - "log/slog" - "os" -) - -func main() { - ctx := context.Background() - - if len(os.Args) > 1 && os.Args[1] == "import" { - err := importDict(ctx, "main.dict") - if err != nil { - slog.Error("Error importing", "error", err) - return - } - } else { - err := run(ctx, "main.dict") - if err != nil { - slog.Error("Error running", "error", err) - return - } - } -} |