summaryrefslogtreecommitdiff
path: root/main.go
blob: 235be18355d126e12aedff1b41fe99a6cf3b9c30 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
// This program looks up words fromm Wiktionary, and creates Anki flashcards
// from them.
package main

import (
	"database/sql"
	"fmt"
	"log"
	"net/http"
	"time"

	"github.com/charmbracelet/bubbles/textinput"
	tea "github.com/charmbracelet/bubbletea"
	_ "github.com/mattn/go-sqlite3"
	"github.com/microcosm-cc/bluemonday"
)

const (
	rawDictionary = "/home/david/work/french-wiktionary-flashcards/raw-wiktextract-data.jsonl"
	dictionary    = "/home/david/work/french-wiktionary-flashcards/raw-wiktextract-data.sqlite3"

	apiURL    = "http://localhost:8765"
	deckName  = "Français"
	modelName = "Basic-830ae"
)

type model struct {
	wordInput         textinput.Model
	err               error
	currentWord       string
	currentDefinition string
	db                *sql.DB
	c                 *http.Client
	wordAddStatus     string
	p                 bluemonday.Policy
}

type (
	errMsg        error
	definitionMsg string
	wordAddedMsg  string
)

func initialModel(c *http.Client, db *sql.DB) model {
	ti := textinput.New()
	ti.Placeholder = ""
	ti.Focus()
	ti.CharLimit = 156
	ti.Width = 36

	return model{
		wordInput:     ti,
		err:           nil,
		db:            db,
		c:             c,
		wordAddStatus: "(Press 'Enter' to add this word and its definition to Anki)",
		p:             *bluemonday.StrictPolicy(),
	}
}

func (m model) Init() tea.Cmd {
	return textinput.Blink
}

func lookupWord(db *sql.DB, word string) tea.Cmd {
	return func() tea.Msg {
		var definition string
		row := db.QueryRow(`select definition from words where word = ? limit 1`, word)
		err := row.Scan(&definition)
		if err != nil {
			return errMsg(fmt.Errorf("looking up '%s': %s", word, err))
		}
		return definitionMsg(definition)
	}
}

func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
	var cmd tea.Cmd

	var textPickerLongerCmds []tea.Cmd

	switch msg := msg.(type) {
	case definitionMsg:
		m.currentDefinition = string(msg)
		return m, nil
	case wordAddedMsg:
		m.wordAddStatus = fmt.Sprintf("✅ Added '%s' to Anki", string(msg))
	case tea.KeyMsg:
		switch msg.Type {
		case tea.KeyCtrlC, tea.KeyEsc:
			return m, tea.Quit
		case tea.KeyEnter:
			return m, addCard(m.c, m.currentWord, m.currentDefinition)
		}

	case errMsg:
		m.err = msg
		return m, nil
	}

	if m.wordInput.Value() != m.currentWord {
		m.currentWord = m.wordInput.Value()
		textPickerLongerCmds = append(textPickerLongerCmds, lookupWord(m.db, m.currentWord))
	}

	m.wordInput, cmd = m.wordInput.Update(msg)
	textPickerLongerCmds = append(textPickerLongerCmds, cmd)
	return m, tea.Batch(textPickerLongerCmds...)
}

func (m model) View() string {
	return fmt.Sprintf(
		"Look up a word:\n\n%s\n\n%s\n%s\n\n%s\n%s",
		m.wordInput.View(),
		m.wordAddStatus,
		"(esc to quit)",
		"Current definition:\n",
		m.p.Sanitize(m.currentDefinition),
	) + "\n"
}

func main() {
	db, err := setupDatabase()
	if err != nil {
		log.Fatalf("setting up database: %s", err)
	}
	defer db.Close()

	c := http.DefaultClient
	c.Timeout = 5 * time.Second

	p := tea.NewProgram(initialModel(c, db))
	if _, err := p.Run(); err != nil {
		log.Fatal(err)
	}
}