Skip to content

Commit bafcb10

Browse files
committed
update codec
1 parent 7b2c503 commit bafcb10

9 files changed

+306
-139
lines changed

.gitignore

+12-14
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,21 @@
1-
# If you prefer the allow list template instead of the deny list, see community template:
2-
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
3-
#
4-
# Binaries for programs and plugins
51
*.exe
62
*.exe~
73
*.dll
84
*.so
95
*.dylib
10-
11-
# Test binary, built with `go test -c`
6+
*.db
7+
*.db-journal
8+
*.mmdb
129
*.test
13-
14-
# Output of the go coverage tool, specifically when used with LiteIDE
1510
*.out
1611

17-
# Dependency directories (remove the comment below to include it)
18-
# vendor/
19-
20-
# Go workspace file
21-
go.work
22-
go.work.sum
12+
.idea/
13+
.vscode/
2314
.tools/
15+
16+
coverage.txt
17+
coverage.out
18+
19+
bin/
20+
vendor/
21+
build/

codec/encdec.go

+78
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/*
2+
* Copyright (c) 2024 Mikhail Knyazhev <[email protected]>. All rights reserved.
3+
* Use of this source code is governed by a BSD 3-Clause license that can be found in the LICENSE file.
4+
*/
5+
6+
package codec
7+
8+
import (
9+
"encoding/json"
10+
11+
"go.osspkg.com/errors"
12+
"go.osspkg.com/syncing"
13+
"gopkg.in/yaml.v3"
14+
)
15+
16+
const (
17+
EncoderYAML = ".yaml"
18+
EncoderJSON = ".json"
19+
)
20+
21+
var (
22+
errBadFormat = errors.New("format is not a supported")
23+
24+
_default = newEncoders().
25+
Add(".yml", yaml.Marshal, yaml.Unmarshal, mapMerge).
26+
Add(EncoderYAML, yaml.Marshal, yaml.Unmarshal, mapMerge).
27+
Add(EncoderJSON, json.Marshal, json.Unmarshal, mapMerge)
28+
)
29+
30+
type (
31+
Codec struct {
32+
Encode func(in interface{}) ([]byte, error)
33+
Decode func(b []byte, out interface{}) error
34+
Merge func(dst map[string]interface{}, src map[string]interface{}) error
35+
}
36+
encoders struct {
37+
list map[string]Codec
38+
mux syncing.Lock
39+
}
40+
)
41+
42+
func newEncoders() *encoders {
43+
return &encoders{
44+
list: make(map[string]Codec, 10),
45+
mux: syncing.NewLock(),
46+
}
47+
}
48+
49+
func AddCodec(ext string, c Codec) {
50+
_default.Add(ext, c.Encode, c.Decode, c.Merge)
51+
}
52+
53+
func (v *encoders) Add(
54+
ext string,
55+
enc func(interface{}) ([]byte, error),
56+
dec func([]byte, interface{}) error,
57+
merge func(map[string]interface{}, map[string]interface{}) error,
58+
) *encoders {
59+
v.mux.Lock(func() {
60+
v.list[ext] = Codec{
61+
Encode: enc,
62+
Decode: dec,
63+
Merge: merge,
64+
}
65+
})
66+
return v
67+
}
68+
69+
func (v *encoders) Get(ext string) (c Codec, err error) {
70+
v.mux.RLock(func() {
71+
var ok bool
72+
if c, ok = v.list[ext]; !ok {
73+
err = errBadFormat
74+
return
75+
}
76+
})
77+
return
78+
}

codec/encdec_blob.go

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* Copyright (c) 2024 Mikhail Knyazhev <[email protected]>. All rights reserved.
3+
* Use of this source code is governed by a BSD 3-Clause license that can be found in the LICENSE file.
4+
*/
5+
6+
package codec
7+
8+
import (
9+
"sync"
10+
)
11+
12+
type BlobEncoder struct {
13+
Blob []byte
14+
Ext string
15+
mux sync.RWMutex
16+
}
17+
18+
func (v *BlobEncoder) Decode(configs ...interface{}) error {
19+
v.mux.RLock()
20+
defer v.mux.RUnlock()
21+
22+
call, err := _default.Get(v.Ext)
23+
if err != nil {
24+
return err
25+
}
26+
if v.Blob == nil {
27+
return nil
28+
}
29+
for _, conf := range configs {
30+
if err = call.Decode(v.Blob, conf); err != nil {
31+
return err
32+
}
33+
}
34+
return nil
35+
}
36+
37+
func (v *BlobEncoder) Encode(configs ...interface{}) error {
38+
v.mux.Lock()
39+
defer v.mux.Unlock()
40+
41+
codec, err0 := _default.Get(v.Ext)
42+
if err0 != nil {
43+
return err0
44+
}
45+
46+
out := make(map[string]interface{}, 10)
47+
for _, conf := range configs {
48+
bb, err := codec.Encode(conf)
49+
if err != nil {
50+
return err
51+
}
52+
tmp := make(map[string]interface{}, 10)
53+
if err = codec.Decode(bb, &tmp); err != nil {
54+
return err
55+
}
56+
if err = codec.Merge(out, tmp); err != nil {
57+
return err
58+
}
59+
}
60+
v.Blob, err0 = codec.Encode(out)
61+
return err0
62+
}

codec/encdec_blob_test.go

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* Copyright (c) 2024 Mikhail Knyazhev <[email protected]>. All rights reserved.
3+
* Use of this source code is governed by a BSD 3-Clause license that can be found in the LICENSE file.
4+
*/
5+
6+
package codec_test
7+
8+
import (
9+
"testing"
10+
11+
"go.osspkg.com/casecheck"
12+
"go.osspkg.com/ioutils/codec"
13+
)
14+
15+
func TestFile_Blob_EncodeDecode(t *testing.T) {
16+
type TestDataItem1 struct {
17+
AA string `yaml:"aa"`
18+
BB bool `yaml:"bb"`
19+
}
20+
type TestData1 struct {
21+
Data1 TestDataItem1 `yaml:"data-1"`
22+
}
23+
type TestDataItem2 struct {
24+
CC string `yaml:"cc"`
25+
DD int `yaml:"dd"`
26+
}
27+
type TestData2 struct {
28+
Data2 TestDataItem2 `yaml:"data-2"`
29+
}
30+
31+
model1 := &TestData1{Data1: TestDataItem1{AA: "123", BB: true}}
32+
model2 := &TestData2{Data2: TestDataItem2{CC: "qwer", DD: -100}}
33+
34+
b := &codec.BlobEncoder{
35+
Blob: nil,
36+
Ext: codec.EncoderJSON,
37+
}
38+
casecheck.NoError(t, b.Encode(model1, model2))
39+
casecheck.Equal(t, `{"Data1":{"AA":"123","BB":true},"Data2":{"CC":"qwer","DD":-100}}`, string(b.Blob))
40+
41+
b = &codec.BlobEncoder{
42+
Blob: nil,
43+
Ext: codec.EncoderYAML,
44+
}
45+
casecheck.NoError(t, b.Encode(model1, model2))
46+
casecheck.Equal(t, "data-1:\n aa: \"123\"\n bb: true\ndata-2:\n cc: qwer\n dd: -100\n", string(b.Blob))
47+
}

codec/encdec_file.go

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Copyright (c) 2024 Mikhail Knyazhev <[email protected]>. All rights reserved.
3+
* Use of this source code is governed by a BSD 3-Clause license that can be found in the LICENSE file.
4+
*/
5+
6+
package codec
7+
8+
import (
9+
"os"
10+
"path/filepath"
11+
)
12+
13+
type FileEncoder string
14+
15+
func (v FileEncoder) Decode(configs ...interface{}) error {
16+
data, err := os.ReadFile(string(v))
17+
if err != nil {
18+
return err
19+
}
20+
ext := filepath.Ext(string(v))
21+
blob := &BlobEncoder{
22+
Blob: data,
23+
Ext: ext,
24+
}
25+
return blob.Decode(configs...)
26+
}
27+
28+
func (v FileEncoder) Encode(configs ...interface{}) error {
29+
ext := filepath.Ext(string(v))
30+
blob := &BlobEncoder{
31+
Blob: nil,
32+
Ext: ext,
33+
}
34+
if err := blob.Encode(configs...); err != nil {
35+
return err
36+
}
37+
return os.WriteFile(string(v), blob.Blob, 0755)
38+
}

encdec_test.go renamed to codec/encdec_file_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* Use of this source code is governed by a BSD 3-Clause license that can be found in the LICENSE file.
44
*/
55

6-
package ioutils
6+
package codec
77

88
import (
99
"os"
@@ -12,7 +12,7 @@ import (
1212
"go.osspkg.com/casecheck"
1313
)
1414

15-
func TestFile_EncodeDecode(t *testing.T) {
15+
func TestFile_File_EncodeDecode(t *testing.T) {
1616
type TestDataItem1 struct {
1717
AA string `yaml:"aa"`
1818
BB bool `yaml:"bb"`

codec/map_merge.go

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/*
2+
* Copyright (c) 2024 Mikhail Knyazhev <[email protected]>. All rights reserved.
3+
* Use of this source code is governed by a BSD 3-Clause license that can be found in the LICENSE file.
4+
*/
5+
6+
package codec
7+
8+
func mapMerge(dst map[string]interface{}, src map[string]interface{}) error {
9+
for k, v := range src {
10+
vv, ok := dst[k]
11+
if !ok {
12+
dst[k] = v
13+
continue
14+
}
15+
vMap, vOk := v.(map[string]interface{})
16+
vvMap, vvOk := vv.(map[string]interface{})
17+
if vOk && vvOk {
18+
return mapMerge(vvMap, vMap)
19+
}
20+
dst[k] = v
21+
}
22+
23+
return nil
24+
}

codec/map_merge_test.go

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* Copyright (c) 2024 Mikhail Knyazhev <[email protected]>. All rights reserved.
3+
* Use of this source code is governed by a BSD 3-Clause license that can be found in the LICENSE file.
4+
*/
5+
6+
package codec
7+
8+
import (
9+
"testing"
10+
11+
"go.osspkg.com/casecheck"
12+
)
13+
14+
func TestUnit_mapMerge(t *testing.T) {
15+
mapA := map[string]interface{}{
16+
"qq": "ww",
17+
"aa": map[string]interface{}{
18+
"bb": "cc",
19+
},
20+
}
21+
mapB := map[string]interface{}{
22+
"zz": "xx",
23+
"aa": map[string]interface{}{
24+
"ss": "dd",
25+
"ee": map[string]interface{}{
26+
"rr": "tt",
27+
},
28+
},
29+
}
30+
31+
casecheck.NoError(t, mapMerge(mapA, mapB))
32+
casecheck.Equal(t, map[string]interface{}{
33+
"zz": "xx",
34+
"qq": "ww",
35+
"aa": map[string]interface{}{
36+
"ss": "dd",
37+
"bb": "cc",
38+
"ee": map[string]interface{}{
39+
"rr": "tt",
40+
},
41+
},
42+
}, mapA)
43+
}

0 commit comments

Comments
 (0)