diff options
Diffstat (limited to 'cmd')
-rw-r--r-- | cmd/dict/main.go | 27 | ||||
-rw-r--r-- | cmd/importer/importer.go | 131 | ||||
-rw-r--r-- | cmd/ui/ui.go | 105 |
3 files changed, 263 insertions, 0 deletions
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 +} |