aboutsummaryrefslogtreecommitdiff
path: root/cmd
diff options
context:
space:
mode:
Diffstat (limited to 'cmd')
-rw-r--r--cmd/dict/main.go27
-rw-r--r--cmd/importer/importer.go131
-rw-r--r--cmd/ui/ui.go105
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
+}