Skip to content

Commit 1669180

Browse files
committed
Use v2 spellchecker
1 parent 19705db commit 1669180

12 files changed

Lines changed: 104 additions & 113 deletions

go.mod

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ module github.com/f1monkey/spellchecker-web
33
go 1.24
44

55
require (
6-
github.com/f1monkey/spellchecker v1.3.0
6+
github.com/agext/levenshtein v1.2.3
7+
github.com/f1monkey/spellchecker/v2 v2.0.1
78
github.com/go-chi/chi/v5 v5.2.2
89
github.com/stretchr/testify v1.8.4
910
github.com/swaggest/openapi-go v0.2.59
@@ -13,7 +14,6 @@ require (
1314
)
1415

1516
require (
16-
github.com/agext/levenshtein v1.2.3 // indirect
1717
github.com/davecgh/go-spew v1.1.1 // indirect
1818
github.com/f1monkey/bitmap v1.4.0 // indirect
1919
github.com/pmezard/go-difflib v1.0.0 // indirect

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
1212
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
1313
github.com/f1monkey/bitmap v1.4.0 h1:Is1PqZWrTawUowD/qE7Vnlh9fzXrEs/qxJHDQ47jZ3g=
1414
github.com/f1monkey/bitmap v1.4.0/go.mod h1:qOc9q5FQxdvMyjVDnmvfJxUtz8JIryqOGxpg4Vtg4nY=
15-
github.com/f1monkey/spellchecker v1.3.0 h1:3Y5mkLlYKUmQ7VFMJxuRc03/fVLyhtoqRiaT1C7R/l0=
16-
github.com/f1monkey/spellchecker v1.3.0/go.mod h1:62+fPnP8bII5+2alSPc1pA0XrH7vA0tyIFhAs6E0dN8=
15+
github.com/f1monkey/spellchecker/v2 v2.0.1 h1:bUJmlmgn/75koynPY4yIRXHC27kHDUb83fZkQpauho4=
16+
github.com/f1monkey/spellchecker/v2 v2.0.1/go.mod h1:fuw+e0Ibat071gwjzQBjVSrQ3+5uq3v2Ovqy5engZ+k=
1717
github.com/go-chi/chi/v5 v5.2.2 h1:CMwsvRVTbXVytCk1Wd72Zy1LAsAh9GxMmSNWLHCG618=
1818
github.com/go-chi/chi/v5 v5.2.2/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
1919
github.com/iancoleman/orderedmap v0.3.0 h1:5cbR2grmZR/DiVt+VJopEhtVs9YGInGIxAoMJn+Ichc=

internal/routes/dictionary_create.go

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ import (
44
"context"
55
"errors"
66

7-
f1mspellchecker "github.com/f1monkey/spellchecker"
87
"github.com/f1monkey/spellchecker-web/internal/spellchecker"
8+
f1mspellchecker "github.com/f1monkey/spellchecker/v2"
99
"github.com/swaggest/usecase"
1010
"github.com/swaggest/usecase/status"
1111
)
@@ -17,9 +17,7 @@ type registryAdder interface {
1717
type DictionaryCreateRequest struct {
1818
Code string `path:"code" minLength:"1"`
1919

20-
Alphabet string `json:"alphabet" minLength:"1"`
21-
MaxErrors uint `json:"maxErrors" minimum:"0" maximum:"5"`
22-
SimilarityThreshold float64 `json:"similarityThreshold" minimum:"0" maximum:"1"`
20+
Alphabet string `json:"alphabet" minLength:"1"`
2321
}
2422

2523
func dictionaryCreate(registry registryAdder) usecase.Interactor {

internal/routes/dictionary_create_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ import (
55
"errors"
66
"testing"
77

8-
f1mspellchecker "github.com/f1monkey/spellchecker"
98
"github.com/f1monkey/spellchecker-web/internal/spellchecker"
9+
f1mspellchecker "github.com/f1monkey/spellchecker/v2"
1010
"github.com/stretchr/testify/require"
1111
"github.com/swaggest/usecase/status"
1212
)

internal/routes/dictionary_fix.go

Lines changed: 60 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ import (
66
"regexp"
77
"unicode/utf8"
88

9-
f1mspellchecker "github.com/f1monkey/spellchecker"
109
"github.com/f1monkey/spellchecker-web/internal/spellchecker"
10+
f1mspellchecker "github.com/f1monkey/spellchecker/v2"
1111
"github.com/swaggest/usecase"
1212
"github.com/swaggest/usecase/status"
1313
)
@@ -19,8 +19,10 @@ type dictionaryGetter interface {
1919
type DictionaryFixRequest struct {
2020
Code string `path:"code" minLength:"1"`
2121

22-
Text string `json:"text" description:"Phrase to be checked"`
23-
Limit int `json:"limit" default:"5" desciption:"Max suggestions per word"`
22+
Text string `json:"text" description:"Phrase to be checked"`
23+
Limit int `json:"limit" default:"5" desciption:"Max suggestions per word"`
24+
MaxErrors int `json:"maxErrors" default:"2" desciption:"Max spellchecker errors allowed"`
25+
SimilarityThreshold float64 `json:"similarityThreshold" minimum:"0" maximum:"1" desciption:"Word similarity percent required"`
2426
}
2527

2628
type DictionaryFixResponse struct {
@@ -51,71 +53,78 @@ func dictionaryFix(registry dictionaryGetter, splitter *regexp.Regexp) usecase.I
5153
errorInvalidWord = "invalid_word"
5254
)
5355

54-
u := usecase.NewInteractor(func(ctx context.Context, input DictionaryFixRequest, output *DictionaryFixResponse) error {
55-
sc, err := registry.Get(input.Code)
56-
if errors.Is(spellchecker.ErrNotFound, err) {
57-
return status.Wrap(err, status.NotFound)
58-
} else if err != nil {
59-
return status.Wrap(err, status.Internal)
60-
}
61-
62-
if input.Text == "" {
63-
output.Fixes = make([]Fix, 0)
64-
return nil
65-
}
66-
67-
matches := splitter.FindAllStringIndex(input.Text, -1)
68-
fixes := make([]Fix, 0, len(matches))
69-
correct := make([]Correct, 0, len(matches))
70-
71-
for _, match := range matches {
72-
startByte, endByte := match[0], match[1]
73-
startRune := utf8.RuneCountInString(input.Text[:startByte])
74-
endRune := startRune + utf8.RuneCountInString(input.Text[startByte:endByte])
56+
u := usecase.NewInteractor(
57+
func(ctx context.Context, input DictionaryFixRequest, output *DictionaryFixResponse) error {
58+
sc, err := registry.Get(input.Code)
59+
if errors.Is(spellchecker.ErrNotFound, err) {
60+
return status.Wrap(err, status.NotFound)
61+
} else if err != nil {
62+
return status.Wrap(err, status.Internal)
63+
}
7564

76-
fix := Fix{
77-
Start: startRune,
78-
End: endRune,
65+
if input.Text == "" {
66+
output.Fixes = make([]Fix, 0)
67+
return nil
7968
}
8069

81-
word := input.Text[startByte:endByte]
70+
matches := splitter.FindAllStringIndex(input.Text, -1)
71+
fixes := make([]Fix, 0, len(matches))
72+
correct := make([]Correct, 0, len(matches))
8273

83-
suggestions := sc.SuggestScore(word, input.Limit)
74+
for _, match := range matches {
75+
startByte, endByte := match[0], match[1]
76+
startRune := utf8.RuneCountInString(input.Text[:startByte])
77+
endRune := startRune + utf8.RuneCountInString(input.Text[startByte:endByte])
8478

85-
if suggestions.ExactMatch {
86-
correct = append(correct, Correct{
79+
fix := Fix{
8780
Start: startRune,
8881
End: endRune,
89-
})
82+
}
9083

91-
continue
92-
}
84+
word := input.Text[startByte:endByte]
9385

94-
if len(suggestions.Suggestions) == 0 {
95-
fix.Error = errorUnknownWord
96-
} else {
97-
fix.Error = errorInvalidWord
98-
fix.Suggestions = make([]SpellcheckerSuggestion, 0, len(suggestions.Suggestions))
86+
suggestions := sc.Suggest(&f1mspellchecker.SearchOptions{
87+
MaxErrors: input.MaxErrors,
88+
FilterFunc: spellchecker.ScoringFunc(input.MaxErrors, input.SimilarityThreshold),
89+
}, word, input.Limit)
9990

100-
for _, s := range suggestions.Suggestions {
101-
fix.Suggestions = append(fix.Suggestions, SpellcheckerSuggestion{
102-
Text: s.Value,
103-
Score: s.Score,
91+
if suggestions.ExactMatch {
92+
correct = append(correct, Correct{
93+
Start: startRune,
94+
End: endRune,
10495
})
96+
97+
continue
10598
}
106-
}
10799

108-
fixes = append(fixes, fix)
109-
}
100+
if len(suggestions.Suggestions) == 0 {
101+
fix.Error = errorUnknownWord
102+
} else {
103+
fix.Error = errorInvalidWord
104+
fix.Suggestions = make([]SpellcheckerSuggestion, 0, len(suggestions.Suggestions))
105+
106+
for _, s := range suggestions.Suggestions {
107+
fix.Suggestions = append(fix.Suggestions, SpellcheckerSuggestion{
108+
Text: s.Value,
109+
Score: s.Score,
110+
})
111+
}
112+
}
110113

111-
output.Fixes = fixes
112-
output.Correct = correct
114+
fixes = append(fixes, fix)
115+
}
113116

114-
return nil
115-
})
117+
output.Fixes = fixes
118+
output.Correct = correct
119+
120+
return nil
121+
},
122+
)
116123

117124
u.SetTitle("Fix text")
118-
u.SetDescription("Performs spellchecking on the given input text. Returns misspelled words along with suggested corrections, up to the specified limit per word.")
125+
u.SetDescription(
126+
"Performs spellchecking on the given input text. Returns misspelled words along with suggested corrections, up to the specified limit per word.",
127+
)
119128
u.SetExpectedErrors(status.Internal, status.NotFound)
120129

121130
return u

internal/routes/dictionary_fix_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ import (
66
"regexp"
77
"testing"
88

9-
f1mspellchecker "github.com/f1monkey/spellchecker"
109
"github.com/f1monkey/spellchecker-web/internal/spellchecker"
10+
f1mspellchecker "github.com/f1monkey/spellchecker/v2"
1111
"github.com/stretchr/testify/assert"
1212
"github.com/stretchr/testify/require"
1313
"github.com/swaggest/usecase/status"
@@ -34,7 +34,7 @@ func Test_DictionaryFix(t *testing.T) {
3434
sc, err := f1mspellchecker.New(f1mspellchecker.DefaultAlphabet)
3535
require.NoError(t, err)
3636

37-
sc.Add("hello")
37+
sc.Add(nil, "hello")
3838

3939
tests := []struct {
4040
name string

internal/routes/dictionary_item_add.go

Lines changed: 28 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"regexp"
77

88
"github.com/f1monkey/spellchecker-web/internal/spellchecker"
9+
f1mspellchecker "github.com/f1monkey/spellchecker/v2"
910
"github.com/swaggest/usecase"
1011
"github.com/swaggest/usecase/status"
1112
)
@@ -26,39 +27,42 @@ type DictionaryItemAddResponse struct {
2627
}
2728

2829
func dictionaryItemAdd(registry dictionaryGetter, splitter *regexp.Regexp) usecase.Interactor {
29-
u := usecase.NewInteractor(func(ctx context.Context, input DictionaryItemAddRequest, output *DictionaryItemAddResponse) error {
30-
sc, err := registry.Get(input.Code)
31-
if errors.Is(spellchecker.ErrNotFound, err) {
32-
return status.Wrap(err, status.NotFound)
33-
} else if err != nil {
34-
return status.Wrap(err, status.Internal)
35-
}
30+
u := usecase.NewInteractor(
31+
func(ctx context.Context, input DictionaryItemAddRequest, output *DictionaryItemAddResponse) error {
32+
sc, err := registry.Get(input.Code)
33+
if errors.Is(spellchecker.ErrNotFound, err) {
34+
return status.Wrap(err, status.NotFound)
35+
} else if err != nil {
36+
return status.Wrap(err, status.Internal)
37+
}
3638

37-
wordCnt := 0
39+
wordCnt := 0
3840

39-
for i := range input.Phrases {
41+
for i := range input.Phrases {
42+
words := splitter.FindAllString(input.Phrases[i].Text, -1)
43+
if len(words) == 0 {
44+
continue
45+
}
4046

41-
words := splitter.FindAllString(input.Phrases[i].Text, -1)
42-
if len(words) == 0 {
43-
continue
44-
}
47+
weight := input.Phrases[i].Weight
48+
if weight == 0 {
49+
weight = 1
50+
}
4551

46-
weight := input.Phrases[i].Weight
47-
if weight == 0 {
48-
weight = 1
52+
sc.Add(&f1mspellchecker.AddOptions{Weight: weight}, words...)
53+
wordCnt += len(words)
4954
}
5055

51-
sc.AddWeight(weight, words...)
52-
wordCnt += len(words)
53-
}
54-
55-
output.Words = wordCnt
56+
output.Words = wordCnt
5657

57-
return nil
58-
})
58+
return nil
59+
},
60+
)
5961

6062
u.SetTitle("Add phrases/words to spellchecker")
61-
u.SetDescription("Adds one or more custom phrases or words to the spellchecker dictionary. Each phrase can have an optional weight to influence matching or prioritization.")
63+
u.SetDescription(
64+
"Adds one or more custom phrases or words to the spellchecker dictionary. Each phrase can have an optional weight to influence matching or prioritization.",
65+
)
6266
u.SetExpectedErrors(status.Internal)
6367

6468
return u

internal/routes/dictionary_item_add_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ import (
66
"regexp"
77
"testing"
88

9-
f1mspellchecker "github.com/f1monkey/spellchecker"
109
"github.com/f1monkey/spellchecker-web/internal/spellchecker"
10+
f1mspellchecker "github.com/f1monkey/spellchecker/v2"
1111
"github.com/stretchr/testify/require"
1212
"github.com/swaggest/usecase/status"
1313
)

internal/spellchecker/item.go

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import (
55
"encoding/json"
66
"fmt"
77

8-
"github.com/f1monkey/spellchecker"
8+
"github.com/f1monkey/spellchecker/v2"
99
)
1010

1111
type RegistryItem struct {
@@ -14,9 +14,7 @@ type RegistryItem struct {
1414
}
1515

1616
type Options struct {
17-
Alphabet string `json:"alphabet"`
18-
MaxErrors uint `json:"maxErrors"`
19-
SimilarityThreshold float64 `json:"similarityThreshold"`
17+
Alphabet string `json:"alphabet"`
2018
}
2119

2220
type src struct {
@@ -54,15 +52,6 @@ func (r *RegistryItem) UnmarshalJSON(data []byte) error {
5452
return err
5553
}
5654

57-
if err := sc.WithOpts(spellchecker.WithFilterFunc(
58-
ScoringFunc(
59-
int(value.Options.MaxErrors),
60-
value.Options.SimilarityThreshold,
61-
),
62-
)); err != nil {
63-
return err
64-
}
65-
6655
r.Spellchecker = sc
6756
r.Options = value.Options
6857

internal/spellchecker/registry.go

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ import (
99
"strings"
1010
"sync"
1111

12-
"github.com/f1monkey/spellchecker"
1312
"github.com/f1monkey/spellchecker-web/internal/logger"
13+
"github.com/f1monkey/spellchecker/v2"
1414
)
1515

1616
var (
@@ -72,16 +72,7 @@ func (r *Registry) Add(code string, options Options) (*spellchecker.Spellchecker
7272
return nil, ErrAlreadyExists
7373
}
7474

75-
result, err := spellchecker.New(
76-
options.Alphabet,
77-
spellchecker.WithMaxErrors(int(options.MaxErrors)),
78-
spellchecker.WithFilterFunc(
79-
ScoringFunc(
80-
int(options.MaxErrors),
81-
options.SimilarityThreshold,
82-
),
83-
),
84-
)
75+
result, err := spellchecker.New(options.Alphabet)
8576
if err != nil {
8677
return nil, ErrSpellcheckerInit
8778
}

0 commit comments

Comments
 (0)