go-reader - 解答

実装コード

package reader

import (
    "io"
    "syscall"
)

const bufferSize = 1024

// fdBuffers は各fdのバッファを保持
var fdBuffers = make(map[int][]byte)

// GetNextLine はファイルディスクリプタから1行読み込む
func GetNextLine(fd int) (string, error) {
    // バッファを取得または初期化
    buffer, exists := fdBuffers[fd]
    if !exists {
        buffer = make([]byte, 0)
    }

    for {
        // バッファ内で改行を探す
        for i, b := range buffer {
            if b == '\n' {
                line := string(buffer[:i+1])
                fdBuffers[fd] = buffer[i+1:]
                return line, nil
            }
        }

        // 新しいデータを読み込む
        tempBuf := make([]byte, bufferSize)
        n, err := syscall.Read(fd, tempBuf)

        if n > 0 {
            buffer = append(buffer, tempBuf[:n]...)
            fdBuffers[fd] = buffer
        }

        if err != nil || n == 0 {
            // EOF: 残りのバッファを返す
            if len(buffer) > 0 {
                line := string(buffer)
                fdBuffers[fd] = nil
                return line, nil
            }
            delete(fdBuffers, fd)
            return "", io.EOF
        }
    }
}

// LineReader は io.Reader からの行読み込み
type LineReader struct {
    reader io.Reader
    buffer []byte
}

// NewLineReader は新しい LineReader を作成
func NewLineReader(r io.Reader) *LineReader {
    return &LineReader{
        reader: r,
        buffer: make([]byte, 0),
    }
}

// ReadLine は1行読み込む
func (lr *LineReader) ReadLine() (string, error) {
    for {
        // バッファ内で改行を探す
        for i, b := range lr.buffer {
            if b == '\n' {
                line := string(lr.buffer[:i+1])
                lr.buffer = lr.buffer[i+1:]
                return line, nil
            }
        }

        // 新しいデータを読み込む
        tempBuf := make([]byte, bufferSize)
        n, err := lr.reader.Read(tempBuf)

        if n > 0 {
            lr.buffer = append(lr.buffer, tempBuf[:n]...)
        }

        if err != nil {
            if len(lr.buffer) > 0 {
                line := string(lr.buffer)
                lr.buffer = nil
                return line, nil
            }
            return "", err
        }
    }
}

テストコード

package reader

import (
    "io"
    "strings"
    "testing"
)

func TestLineReader(t *testing.T) {
    input := "line1\nline2\nline3"
    expected := []string{"line1\n", "line2\n", "line3"}

    lr := NewLineReader(strings.NewReader(input))

    for i, exp := range expected {
        line, err := lr.ReadLine()
        if i < len(expected)-1 && err != nil {
            t.Errorf("Unexpected error: %v", err)
        }
        if line != exp {
            t.Errorf("Line %d: got %q, want %q", i, line, exp)
        }
    }

    _, err := lr.ReadLine()
    if err != io.EOF {
        t.Errorf("Expected EOF, got %v", err)
    }
}