summaryrefslogtreecommitdiff
path: root/src/pkg/encoding/line/line.go
blob: 92dddcb996d9011fa9ae477bfedc80b9338eef48 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
// Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// This package implements a Reader which handles reading \r and \r\n
// deliminated lines.
package line

import (
	"io"
	"os"
)

// Reader reads lines from an io.Reader (which may use either '\n' or
// '\r\n').
type Reader struct {
	buf      []byte
	consumed int
	in       io.Reader
	err      os.Error
}

func NewReader(in io.Reader, maxLineLength int) *Reader {
	return &Reader{
		buf:      make([]byte, 0, maxLineLength),
		consumed: 0,
		in:       in,
	}
}

// ReadLine tries to return a single line, not including the end-of-line bytes.
// If the line was found to be longer than the maximum length then isPrefix is
// set and the beginning of the line is returned. The rest of the line will be
// returned from future calls. isPrefix will be false when returning the last
// fragment of the line.  The returned buffer points into the internal state of
// the Reader and is only valid until the next call to ReadLine. ReadLine
// either returns a non-nil line or it returns an error, never both.
func (l *Reader) ReadLine() (line []byte, isPrefix bool, err os.Error) {
	if l.consumed > 0 {
		n := copy(l.buf, l.buf[l.consumed:])
		l.buf = l.buf[:n]
		l.consumed = 0
	}

	if len(l.buf) == 0 && l.err != nil {
		err = l.err
		return
	}

	scannedTo := 0

	for {
		i := scannedTo
		for ; i < len(l.buf); i++ {
			if l.buf[i] == '\r' && len(l.buf) > i+1 && l.buf[i+1] == '\n' {
				line = l.buf[:i]
				l.consumed = i + 2
				return
			} else if l.buf[i] == '\n' {
				line = l.buf[:i]
				l.consumed = i + 1
				return
			}
		}

		if i == cap(l.buf) {
			line = l.buf[:i]
			l.consumed = i
			isPrefix = true
			return
		}

		if l.err != nil {
			line = l.buf
			l.consumed = i
			return
		}

		// We don't want to rescan the input that we just scanned.
		// However, we need to back up one byte because the last byte
		// could have been a '\r' and we do need to rescan that.
		scannedTo = i
		if scannedTo > 0 {
			scannedTo--
		}
		oldLen := len(l.buf)
		l.buf = l.buf[:cap(l.buf)]
		n, readErr := l.in.Read(l.buf[oldLen:])
		l.buf = l.buf[:oldLen+n]
		if readErr != nil {
			l.err = readErr
		}
	}
	panic("unreachable")
}