diff --git a/dict/dict.go b/dict/dict.go index f71d532..c885945 100644 --- a/dict/dict.go +++ b/dict/dict.go @@ -46,6 +46,11 @@ type ReaderAtCloser interface { // Options are options for the dict data. type Options struct { + // SameTypeSequence is an option that indicates that each word in the .dict + // file will have the same sequence of data types. This is equivalent to + // the sametypesequence option from the .ifo file.. + // + // See: https://github.com/huzheng001/stardict-3/blob/master/dict/doc/StarDictFileFormat SameTypeSequence []DataType } @@ -227,9 +232,8 @@ func NewFromIfoPath(ifoPath string, options *Options) (*Dict, error) { f: f, } - dictExt := filepath.Ext(f.Name()) - //nolint:gocritic // strings.EqualFold should not be used here. - if strings.ToLower(dictExt) == ".dz" { + dictExt := strings.ToLower(filepath.Ext(f.Name())) + if dictExt == ".dz" { r.dz, err = dictzip.NewReader(f) if err != nil { return nil, fmt.Errorf("opening dictzip: %w", err) diff --git a/dict/dict_test.go b/dict/dict_test.go index 2759bb6..7ae1945 100644 --- a/dict/dict_test.go +++ b/dict/dict_test.go @@ -17,7 +17,6 @@ package dict_test import ( "io" "os" - "path/filepath" "strings" "testing" @@ -216,7 +215,7 @@ func TestDict_Word(t *testing.T) { } defer os.Remove(f.Name()) - _, err = f.Write(testutil.MakeDict(test.dict, test.sametypesequence)) + _, err = f.Write(testutil.MakeDict(t, test.dict, test.sametypesequence)) if err != nil { t.Fatal(err) } @@ -250,16 +249,17 @@ func TestDict_NewFromIfoPath(t *testing.T) { t.Parallel() tests := []struct { - name string - extension string - dict []*dict.Word - index *idx.Word - expected *dict.Word - sametypesequence []dict.DataType + name string + options *testutil.MakeDictOptions + dict []*dict.Word + index *idx.Word + expected *dict.Word }{ { - name: "utf", - extension: ".dict", + name: "utf", + options: &testutil.MakeDictOptions{ + Ext: ".dict", + }, dict: []*dict.Word{ { Data: []*dict.Data{ @@ -285,10 +285,12 @@ func TestDict_NewFromIfoPath(t *testing.T) { }, }, { - name: "utf sametype", - extension: ".DICT", - sametypesequence: []dict.DataType{ - dict.UTFTextType, + name: "utf sametype", + options: &testutil.MakeDictOptions{ + Ext: ".DICT", + SameTypeSequence: []dict.DataType{ + dict.UTFTextType, + }, }, dict: []*dict.Word{ { @@ -315,8 +317,10 @@ func TestDict_NewFromIfoPath(t *testing.T) { }, }, { - name: "file type", - extension: ".dict", + name: "file type", + options: &testutil.MakeDictOptions{ + Ext: ".dict", + }, dict: []*dict.Word{ { Data: []*dict.Data{ @@ -342,10 +346,12 @@ func TestDict_NewFromIfoPath(t *testing.T) { }, }, { - name: "file sametype", - extension: ".dict", - sametypesequence: []dict.DataType{ - dict.WavType, + name: "file sametype", + options: &testutil.MakeDictOptions{ + Ext: ".dict", + SameTypeSequence: []dict.DataType{ + dict.WavType, + }, }, dict: []*dict.Word{ { @@ -371,30 +377,48 @@ func TestDict_NewFromIfoPath(t *testing.T) { }, }, }, + { + name: "dictzip", + options: &testutil.MakeDictOptions{ + Ext: ".dict.dz", + DictZip: true, + }, + dict: []*dict.Word{ + { + Data: []*dict.Data{ + { + Type: dict.UTFTextType, + Data: []byte("hoge"), + }, + }, + }, + }, + index: &idx.Word{ + Word: "hoge", + Offset: uint64(0), + Size: uint32(6), + }, + expected: &dict.Word{ + Data: []*dict.Data{ + { + Type: dict.UTFTextType, + Data: []byte("hoge"), + }, + }, + }, + }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { t.Parallel() - f, err := os.CreateTemp("", "stardict.*"+test.extension) - if err != nil { - t.Fatal(err) - } - defer os.Remove(f.Name()) + path := testutil.MakeTempDict(t, test.dict, test.options) + defer os.Remove(path) + ifoPath := strings.TrimSuffix(path, test.options.Ext) + ".ifo" - _, err = f.Write(testutil.MakeDict(test.dict, test.sametypesequence)) - if err != nil { - t.Fatal(err) - } - _, err = f.Seek(0, io.SeekStart) - if err != nil { - t.Fatal(err) - } - - ifoPath := strings.TrimSuffix(f.Name(), filepath.Ext(f.Name())) + ".ifo" d, err := dict.NewFromIfoPath(ifoPath, &dict.Options{ - SameTypeSequence: test.sametypesequence, + SameTypeSequence: test.options.SameTypeSequence, }) if err != nil { t.Fatal(err) diff --git a/internal/testutil/dict.go b/internal/testutil/dict.go index 0562516..4efa7a5 100644 --- a/internal/testutil/dict.go +++ b/internal/testutil/dict.go @@ -15,15 +15,66 @@ package testutil import ( + "bytes" "encoding/binary" "fmt" "math" + "os" + "testing" + + "github.com/pebbe/dictzip" "github.com/ianlewis/go-stardict/dict" ) +type MakeDictOptions struct { + // Ext is the .dict file's extension (e.g. .dict). + Ext string + + // DictZip indicates that the dict file should be compressed with DictZip. + DictZip bool + + // SameTypeSequence is the sametypesequence option. + SameTypeSequence []dict.DataType +} + +// MakeTempDict creates a temporary .dict file and returns the path to the file. +func MakeTempDict(t *testing.T, words []*dict.Word, opts *MakeDictOptions) string { + t.Helper() + + f, err := os.CreateTemp("", "stardict.*"+opts.Ext) + if err != nil { + t.Fatal(err) + } + + d := MakeDict(t, words, opts.SameTypeSequence) + + if opts.DictZip { + // Just get the file name. + path := f.Name() + f.Close() + + fmt.Println(path) + err := dictzip.Write(bytes.NewReader(d), path, 9) + if err != nil { + t.Fatal(err) + } + + return path + } else { + defer f.Close() + + _, err = f.Write(d) + if err != nil { + t.Fatal(err) + } + + return f.Name() + } +} + // MakeDict creates a test .dict file. -func MakeDict(words []*dict.Word, sametypesequence []dict.DataType) []byte { +func MakeDict(t *testing.T, words []*dict.Word, sametypesequence []dict.DataType) []byte { b := []byte{} for _, w := range words { for i, d := range w.Data { @@ -38,7 +89,7 @@ func MakeDict(words []*dict.Word, sametypesequence []dict.DataType) []byte { sizeBytes := make([]byte, 4) dataLen := len(d.Data) if dataLen > math.MaxUint32 { - panic(fmt.Sprintf("word data too long: %d", dataLen)) + t.Fatalf("word data too long: %d", dataLen) } binary.BigEndian.PutUint32(sizeBytes, uint32(dataLen)) b = append(b, sizeBytes...) @@ -57,7 +108,7 @@ func MakeDict(words []*dict.Word, sametypesequence []dict.DataType) []byte { sizeBytes := make([]byte, 4) dataLen := len(d.Data) if dataLen > math.MaxUint32 { - panic(fmt.Sprintf("word data too long: %d", dataLen)) + t.Fatalf("word data too long: %d", dataLen) } binary.BigEndian.PutUint32(sizeBytes, uint32(dataLen)) b = append(b, sizeBytes...) diff --git a/stardict_test.go b/stardict_test.go index 3ccebfb..84db59b 100644 --- a/stardict_test.go +++ b/stardict_test.go @@ -31,7 +31,9 @@ type testDict struct { } // writeDict writes out a test dictionary set of files. -func writeDict(d testDict) string { +func writeDict(t *testing.T, d testDict) string { + t.Helper() + path, err := os.MkdirTemp("", "stardict") if err != nil { panic(err) @@ -43,7 +45,7 @@ func writeDict(d testDict) string { if err := os.WriteFile(filepath.Join(path, "dictionary.idx"), testutil.MakeIndex(d.idx, 32), 0o600); err != nil { panic(err) } - if err := os.WriteFile(filepath.Join(path, "dictionary.dict"), testutil.MakeDict(d.dict, nil), 0o600); err != nil { + if err := os.WriteFile(filepath.Join(path, "dictionary.dict"), testutil.MakeDict(t, d.dict, nil), 0o600); err != nil { panic(err) } @@ -94,7 +96,7 @@ idxfilesize=6`, t.Parallel() for _, td := range test.dicts { - path := writeDict(td) + path := writeDict(t, td) defer os.RemoveAll(path) s, err := Open(filepath.Join(path, "dictionary.ifo"))