summaryrefslogtreecommitdiff
path: root/src/pkg/encoding/line/line.go
blob: f46ce1c83a05711c4a0eb87d3ed241eaf94f600f (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
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
// 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.

// The line package implements a Reader that reads lines delimited by '\n' or ' \r\n'.
package line

import (
	"io"
	"os"
)

// Reader reads lines, delimited by '\n' or \r\n', from an io.Reader.
type Reader struct {
	buf      []byte
	consumed int
	in       io.Reader
	err      os.Error
}

// NewReader returns a new Reader that will read successive
// lines from the input Reader.
func NewReader(input io.Reader, maxLineLength int) *Reader {
	return &Reader{
		buf:      make([]byte, 0, maxLineLength),
		consumed: 0,
		in:       input,
	}
}

// Read reads from any buffered data past the last line read, or from the underlying
// io.Reader if the buffer is empty.
func (l *Reader) Read(p []byte) (n int, err os.Error) {
	l.removeConsumedFromBuffer()
	if len(l.buf) > 0 {
		n = copy(p, l.buf)
		l.consumed += n
		return
	}
	return l.in.Read(p)
}

func (l *Reader) removeConsumedFromBuffer() {
	if l.consumed > 0 {
		n := copy(l.buf, l.buf[l.consumed:])
		l.buf = l.buf[:n]
		l.consumed = 0
	}
}

// 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) {
	l.removeConsumedFromBuffer()

	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
			if len(l.buf) == 0 {
				return nil, false, readErr
			}
		}
	}
	panic("unreachable")
}