diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 0b443f3..dfa61d3 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -19,7 +19,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v4 with: - go-version: '1.20' + go-version-file: 'go.mod' - name: Build run: go build -v ./... diff --git a/chunkfile.go b/chunkfile.go index 1117095..d883481 100644 --- a/chunkfile.go +++ b/chunkfile.go @@ -2,24 +2,27 @@ package external import ( "bufio" - "encoding/json" "errors" "io" "iter" "os" "slices" + + "github.com/ajiyoshi-vg/external/codec" ) type ChunkFile[T any] struct { data []T tmpFile *string length int + codec codec.Codec[T] } func NewChunkFile[T any](data []T) *ChunkFile[T] { return &ChunkFile[T]{ data: data, length: len(data), + codec: codec.JSON[T]{}, } } @@ -52,13 +55,7 @@ func (x *ChunkFile[T]) Store() error { func (x *ChunkFile[T]) store(w io.WriteCloser) error { buf := bufio.NewWriter(w) defer buf.Flush() - enc := json.NewEncoder(buf) - for _, v := range x.data { - if err := enc.Encode(v); err != nil { - return err - } - } - return nil + return x.codec.Encode(slices.Values(x.data), buf) } func (x *ChunkFile[T]) Restore() (iter.Seq[T], error) { @@ -69,21 +66,5 @@ func (x *ChunkFile[T]) Restore() (iter.Seq[T], error) { if err != nil { return nil, err } - return x.restore(tempFile), nil -} - -func (x *ChunkFile[T]) restore(r io.ReadCloser) iter.Seq[T] { - dec := json.NewDecoder(bufio.NewReader(r)) - return func(yield func(T) bool) { - defer r.Close() - for dec.More() { - var v T - if err := dec.Decode(&v); err != nil { - return - } - if !yield(v) { - return - } - } - } + return x.codec.Decode(tempFile), nil } diff --git a/codec/codec.go b/codec/codec.go new file mode 100644 index 0000000..1c14fd8 --- /dev/null +++ b/codec/codec.go @@ -0,0 +1,11 @@ +package codec + +import ( + "io" + "iter" +) + +type Codec[T any] interface { + Encode(seq iter.Seq[T], w io.Writer) error + Decode(r io.ReadCloser) iter.Seq[T] +} diff --git a/codec/json.go b/codec/json.go new file mode 100644 index 0000000..43785d7 --- /dev/null +++ b/codec/json.go @@ -0,0 +1,39 @@ +package codec + +import ( + "encoding/json" + "io" + "iter" + "log" +) + +type JSON[T any] struct{} + +var _ Codec[int] = (*JSON[int])(nil) + +func (JSON[T]) Encode(seq iter.Seq[T], w io.Writer) error { + enc := json.NewEncoder(w) + for x := range seq { + if err := enc.Encode(x); err != nil { + return err + } + } + return nil +} + +func (JSON[T]) Decode(r io.ReadCloser) iter.Seq[T] { + dec := json.NewDecoder(r) + return func(yield func(T) bool) { + defer r.Close() + for dec.More() { + var x T + if err := dec.Decode(&x); err != nil { + log.Printf("decode failed: %v", err) + return + } + if !yield(x) { + return + } + } + } +} diff --git a/codec/json_test.go b/codec/json_test.go new file mode 100644 index 0000000..9d07fc8 --- /dev/null +++ b/codec/json_test.go @@ -0,0 +1,36 @@ +package codec + +import ( + "bytes" + "io" + "slices" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestJSON(t *testing.T) { + cases := map[string]struct { + input []int + expect []int + }{ + "normal": { + input: []int{1, 2, 3}, + expect: []int{1, 2, 3}, + }, + } + + for title, c := range cases { + t.Run(title, func(t *testing.T) { + codec := &JSON[int]{} + seq := slices.Values(c.input) + var b bytes.Buffer + err := codec.Encode(seq, &b) + assert.NoError(t, err) + + r := io.NopCloser(&b) + actual := slices.Collect(codec.Decode(r)) + assert.Equal(t, c.expect, actual) + }) + } +}