diff options
| author | David Schlachter <t480-debian-git@schlachter.ca> | 2026-01-11 23:10:26 -0500 |
|---|---|---|
| committer | David Schlachter <t480-debian-git@schlachter.ca> | 2026-01-11 23:10:26 -0500 |
| commit | 068ef1f9ac1fe551b97b9d5aec224369ebe015fd (patch) | |
| tree | 9efc20ddfc2471e2096c0de14203a6e58abdfb57 /setup.go | |
| parent | 18a0b77981bc1590f558341870f8d35f8aec23c9 (diff) | |
Move dictionary preparation into the bubbletea app
Diffstat (limited to 'setup.go')
| -rw-r--r-- | setup.go | 234 |
1 files changed, 114 insertions, 120 deletions
@@ -5,43 +5,42 @@ import ( "database/sql" "fmt" "html/template" - "log" "os" "strings" + tea "github.com/charmbracelet/bubbletea" "github.com/goccy/go-json" ) -func setupDatabase(rawDictionary string, db *sql.DB) error { +func setupTables(db *sql.DB) error { _, err := db.Exec("create table IF NOT EXISTS words (word text not null, definition text);") if err != nil { return fmt.Errorf("creating table: %s", err) } - row := db.QueryRow(`SELECT count(*) as count from words`) - var count int - err = row.Scan(&count) - if err != nil { - return fmt.Errorf("counting rows: %s", err) - } - - // Only populate the database if it is empty. - if count > 0 { - return nil - } - // Faster import performance. _, err = db.Exec("PRAGMA synchronous = OFF;") if err != nil { return fmt.Errorf("setting risky writes: %s", err) } - if err = populateDictionary(rawDictionary, db); err != nil { - return fmt.Errorf("failed to prepare dictionary: %s", err) - } return nil } +func isDatabaseEmpty(db *sql.DB) tea.Cmd { + return func() tea.Msg { + row := db.QueryRow(`SELECT count(*) as count from words`) + var count int + err := row.Scan(&count) + if err != nil { + return errMsg(fmt.Errorf("counting rows: %s", err)) + } + + // Only populate the database if it is empty. + return isDBEmptyMsg(count == 0) + } +} + type rawDictionaryEntry struct { Word string `json:"word"` LangCode string `json:"lang_code"` @@ -79,131 +78,126 @@ type SenseForDictionaryEntry struct { Example string } -func populateDictionary(rawDictionary string, db *sql.DB) error { - log.Printf("preparing sqlite database from raw dictionary data...") - - // Set up the template - tmpl, err := template.New("entry").Parse( - `<p>{{ .Word }} {{ .Sound }} <i>{{ .POS }} {{ .Gender }}</i></p> +func populateDictionary(rawDictionary string, db *sql.DB) tea.Cmd { + return func() tea.Msg { + // Set up the template + tmpl, err := template.New("entry").Parse( + `<p>{{ .Word }} {{ .Sound }} <i>{{ .POS }} {{ .Gender }}</i></p> <ol>{{ range .Senses}} <li class=sense>{{ .Sense }}<br> {{ if .Example }}<ul><li><i>{{ .Example }}</i></li></ul></li>{{ end }} {{ end }}</ol> {{ if .Etymology }}<p><i>Étymologie: {{ .Etymology }}</i>{{ end }}`) - if err != nil { - panic(err) - } + if err != nil { + panic(err) + } - tx, err := db.Begin() - if err != nil { - return fmt.Errorf("starting transaction: %w", err) - } + tx, err := db.Begin() + if err != nil { + return errMsg(fmt.Errorf("starting transaction: %w", err)) + } - // Set up a prepared statement - stmt, err := tx.Prepare("insert into words(word, definition) values(?, ?)") - if err != nil { - return fmt.Errorf("preparing statement: %w", err) - } - defer stmt.Close() + // Set up a prepared statement + stmt, err := tx.Prepare("insert into words(word, definition) values(?, ?)") + if err != nil { + return errMsg(fmt.Errorf("preparing statement: %w", err)) + } + defer stmt.Close() - file, err := os.Open(rawDictionary) - if err != nil { - return fmt.Errorf("opening: %w", err) - } - defer file.Close() + file, err := os.Open(rawDictionary) + if err != nil { + return errMsg(fmt.Errorf("opening: %w", err)) + } + defer file.Close() - var wordsAdded int - scanner := bufio.NewScanner(file) + var wordsAdded int + scanner := bufio.NewScanner(file) - maxCapacity := 2_000_000 - buf := make([]byte, maxCapacity) - scanner.Buffer(buf, maxCapacity) + maxCapacity := 2_000_000 + buf := make([]byte, maxCapacity) + scanner.Buffer(buf, maxCapacity) - for scanner.Scan() { - var result rawDictionaryEntry - json.Unmarshal([]byte(scanner.Text()), &result) - if result.LangCode != "fr" { - continue - } + for scanner.Scan() { + var result rawDictionaryEntry + json.Unmarshal([]byte(scanner.Text()), &result) + if result.LangCode != "fr" { + continue + } - // Clean up the word. Replace apostrophes (common in phrases) with - // single quotes (more likely to be typed by a user). - result.Word = strings.ReplaceAll(result.Word, `’`, `'`) + // Clean up the word. Replace apostrophes (common in phrases) with + // single quotes (more likely to be typed by a user). + result.Word = strings.ReplaceAll(result.Word, `’`, `'`) - // Create the definition text. - entry := templateReadyDictionaryEntry{ - Word: result.Word, - POS: strings.ToLower(result.POS), - } - if len(result.Etymology) > 0 { - entry.Etymology = result.Etymology[0] - } - if len(result.Sounds) > 0 { - entry.Sound = result.Sounds[0].IPA - } + // Create the definition text. + entry := templateReadyDictionaryEntry{ + Word: result.Word, + POS: strings.ToLower(result.POS), + } + if len(result.Etymology) > 0 { + entry.Etymology = result.Etymology[0] + } + if len(result.Sounds) > 0 { + entry.Sound = result.Sounds[0].IPA + } - var genders, numbers []string - for _, r := range result.Tags { - switch r { - case "masculine": - genders = append(genders, "masculin") - case "feminine": - genders = append(genders, "féminin") - case "plural": - numbers = append(numbers, "pluriel") - case "singular": - numbers = append(numbers, "singulier") + var genders, numbers []string + for _, r := range result.Tags { + switch r { + case "masculine": + genders = append(genders, "masculin") + case "feminine": + genders = append(genders, "féminin") + case "plural": + numbers = append(numbers, "pluriel") + case "singular": + numbers = append(numbers, "singulier") + } } - } - entry.Gender = strings.Join( - []string{ - strings.Join(genders, " / "), - strings.Join(numbers, " et "), - }, - " ", - ) - - for _, s := range result.Senses { - var example string - if len(s.Examples) > 0 { - example = s.Examples[0].Text + entry.Gender = strings.Join( + []string{ + strings.Join(genders, " / "), + strings.Join(numbers, " et "), + }, + " ", + ) + + for _, s := range result.Senses { + var example string + if len(s.Examples) > 0 { + example = s.Examples[0].Text + } + sense := strings.Join(s.Glosses, "; ") + entry.Senses = append(entry.Senses, SenseForDictionaryEntry{Sense: sense, Example: example}) } - sense := strings.Join(s.Glosses, "; ") - entry.Senses = append(entry.Senses, SenseForDictionaryEntry{Sense: sense, Example: example}) - } - formattedDefinition := strings.Builder{} - err := tmpl.Execute(&formattedDefinition, entry) - if err != nil { - return fmt.Errorf("failed to render: %w", err) - } + formattedDefinition := strings.Builder{} + err := tmpl.Execute(&formattedDefinition, entry) + if err != nil { + return errMsg(fmt.Errorf("failed to render: %w", err)) + } - // Insert the entry - _, err = stmt.Exec(entry.Word, formattedDefinition.String()) - if err != nil { - return fmt.Errorf("inserting '%s': %w", entry.Word, err) - } + // Insert the entry + _, err = stmt.Exec(entry.Word, formattedDefinition.String()) + if err != nil { + return errMsg(fmt.Errorf("inserting '%s': %w", entry.Word, err)) + } - wordsAdded++ - if wordsAdded%100_000 == 0 && wordsAdded > 1 { - log.Printf("processed %d lines (most recent word was '%s')", wordsAdded, entry.Word) + wordsAdded++ + + } + if err := scanner.Err(); err != nil { + return errMsg(fmt.Errorf("scanning: %w", err)) } - } - if err := scanner.Err(); err != nil { - return fmt.Errorf("scanning: %w", err) - } + if err := tx.Commit(); err != nil { + return errMsg(fmt.Errorf("committing: %w", err)) + } - if err := tx.Commit(); err != nil { - return fmt.Errorf("committing: %w", err) - } + _, err = db.Exec("create index wordindex on words(word);") + if err != nil { + return errMsg(fmt.Errorf("creating index: %s", err)) + } - _, err = db.Exec("create index wordindex on words(word);") - if err != nil { - return fmt.Errorf("creating index: %s", err) + return isDBEmptyMsg(false) } - - log.Printf("prepared %d dictionary entries", wordsAdded) - - return nil } |
