- SCOWL loader now loads american-words and british-words alongside english-words, tagging each with variant "us", "gb", or NULL (common) - Words appearing in both American and British lists get NULL variant - Add variant column to words table with schema migration - API: /define returns variant field for English, /random and /words accept ?variant=us|gb filter - Add total word count to server status line - Fix rows.Close leak in PopulateCMUTails, add rows.Err() check - Add tests for PopulateCMUTails, cmuTail, TotalWordCount - Expand test seed data and assertions to cover pt-PT definitions, synonyms, and validity checks - Update README with variant docs, /difficulty, /words, /rhyme endpoints, and current database statistics Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
109 lines
2.3 KiB
Go
109 lines
2.3 KiB
Go
package dictionary
|
|
|
|
import (
|
|
"database/sql"
|
|
"errors"
|
|
)
|
|
|
|
var (
|
|
ErrNoMatch = errors.New("dictionary: no word matches filters")
|
|
ErrNotSeeded = errors.New("dictionary: database not seeded, run: go run ./cmd/dictimport")
|
|
)
|
|
|
|
var supportedLangs = []string{"en", "fr", "pt-PT", "zh"}
|
|
|
|
func ValidLang(lang string) bool {
|
|
for _, l := range supportedLangs {
|
|
if l == lang {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
type Options struct {
|
|
MinLength int
|
|
MaxLength int
|
|
POS string // "", "noun", "verb", "adjective", "adverb"
|
|
MinFrequency int // 0 = no filter
|
|
MinDifficulty float64 // 0.0 = no filter
|
|
MaxDifficulty float64 // 0.0 = no filter
|
|
Variant string // "", "us", "gb" — empty means no filter
|
|
}
|
|
|
|
type Definition struct {
|
|
POS string `json:"pos"`
|
|
Gloss string `json:"gloss"`
|
|
Source string `json:"source"`
|
|
Priority int `json:"priority"`
|
|
}
|
|
|
|
type EnglishEquivalent struct {
|
|
Word string `json:"word"`
|
|
Definition string `json:"definition"`
|
|
Synset string `json:"synset"`
|
|
}
|
|
|
|
type Pronunciation struct {
|
|
Format string `json:"format"`
|
|
Value string `json:"value"`
|
|
Source string `json:"source"`
|
|
}
|
|
|
|
type Dictionary struct {
|
|
db *sql.DB
|
|
}
|
|
|
|
// New opens the database read-write (for import CLI).
|
|
func New(dbPath string) (*Dictionary, error) {
|
|
db, err := openDB(dbPath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
d := &Dictionary{db: db}
|
|
if err := d.checkSeeded(); err != nil {
|
|
db.Close()
|
|
return nil, err
|
|
}
|
|
return d, nil
|
|
}
|
|
|
|
// NewReadOnly opens the database read-only with concurrent read support (for server).
|
|
func NewReadOnly(dbPath string) (*Dictionary, error) {
|
|
db, err := openReadOnlyDB(dbPath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
d := &Dictionary{db: db}
|
|
if err := d.checkSeeded(); err != nil {
|
|
db.Close()
|
|
return nil, err
|
|
}
|
|
return d, nil
|
|
}
|
|
|
|
// NewFromDB creates a Dictionary from an existing *sql.DB (useful for testing).
|
|
func NewFromDB(db *sql.DB) *Dictionary {
|
|
return &Dictionary{db: db}
|
|
}
|
|
|
|
func (d *Dictionary) Close() error {
|
|
return d.db.Close()
|
|
}
|
|
|
|
func (d *Dictionary) checkSeeded() error {
|
|
var count int
|
|
err := d.db.QueryRow("SELECT COUNT(*) FROM meta").Scan(&count)
|
|
if err != nil {
|
|
return ErrNotSeeded
|
|
}
|
|
if count == 0 {
|
|
return ErrNotSeeded
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (d *Dictionary) SupportedLangs() []string {
|
|
return supportedLangs
|
|
}
|