mirror of
https://github.com/thegeeklab/github-releases-notifier.git
synced 2024-11-10 23:00:38 +00:00
238 lines
4.9 KiB
Go
238 lines
4.9 KiB
Go
package logfmt
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"fmt"
|
|
"io"
|
|
"unicode/utf8"
|
|
)
|
|
|
|
// A Decoder reads and decodes logfmt records from an input stream.
|
|
type Decoder struct {
|
|
pos int
|
|
key []byte
|
|
value []byte
|
|
lineNum int
|
|
s *bufio.Scanner
|
|
err error
|
|
}
|
|
|
|
// NewDecoder returns a new decoder that reads from r.
|
|
//
|
|
// The decoder introduces its own buffering and may read data from r beyond
|
|
// the logfmt records requested.
|
|
func NewDecoder(r io.Reader) *Decoder {
|
|
dec := &Decoder{
|
|
s: bufio.NewScanner(r),
|
|
}
|
|
return dec
|
|
}
|
|
|
|
// ScanRecord advances the Decoder to the next record, which can then be
|
|
// parsed with the ScanKeyval method. It returns false when decoding stops,
|
|
// either by reaching the end of the input or an error. After ScanRecord
|
|
// returns false, the Err method will return any error that occurred during
|
|
// decoding, except that if it was io.EOF, Err will return nil.
|
|
func (dec *Decoder) ScanRecord() bool {
|
|
if dec.err != nil {
|
|
return false
|
|
}
|
|
if !dec.s.Scan() {
|
|
dec.err = dec.s.Err()
|
|
return false
|
|
}
|
|
dec.lineNum++
|
|
dec.pos = 0
|
|
return true
|
|
}
|
|
|
|
// ScanKeyval advances the Decoder to the next key/value pair of the current
|
|
// record, which can then be retrieved with the Key and Value methods. It
|
|
// returns false when decoding stops, either by reaching the end of the
|
|
// current record or an error.
|
|
func (dec *Decoder) ScanKeyval() bool {
|
|
dec.key, dec.value = nil, nil
|
|
if dec.err != nil {
|
|
return false
|
|
}
|
|
|
|
line := dec.s.Bytes()
|
|
|
|
// garbage
|
|
for p, c := range line[dec.pos:] {
|
|
if c > ' ' {
|
|
dec.pos += p
|
|
goto key
|
|
}
|
|
}
|
|
dec.pos = len(line)
|
|
return false
|
|
|
|
key:
|
|
const invalidKeyError = "invalid key"
|
|
|
|
start, multibyte := dec.pos, false
|
|
for p, c := range line[dec.pos:] {
|
|
switch {
|
|
case c == '=':
|
|
dec.pos += p
|
|
if dec.pos > start {
|
|
dec.key = line[start:dec.pos]
|
|
if multibyte && bytes.IndexRune(dec.key, utf8.RuneError) != -1 {
|
|
dec.syntaxError(invalidKeyError)
|
|
return false
|
|
}
|
|
}
|
|
if dec.key == nil {
|
|
dec.unexpectedByte(c)
|
|
return false
|
|
}
|
|
goto equal
|
|
case c == '"':
|
|
dec.pos += p
|
|
dec.unexpectedByte(c)
|
|
return false
|
|
case c <= ' ':
|
|
dec.pos += p
|
|
if dec.pos > start {
|
|
dec.key = line[start:dec.pos]
|
|
if multibyte && bytes.IndexRune(dec.key, utf8.RuneError) != -1 {
|
|
dec.syntaxError(invalidKeyError)
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
case c >= utf8.RuneSelf:
|
|
multibyte = true
|
|
}
|
|
}
|
|
dec.pos = len(line)
|
|
if dec.pos > start {
|
|
dec.key = line[start:dec.pos]
|
|
if multibyte && bytes.IndexRune(dec.key, utf8.RuneError) != -1 {
|
|
dec.syntaxError(invalidKeyError)
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
|
|
equal:
|
|
dec.pos++
|
|
if dec.pos >= len(line) {
|
|
return true
|
|
}
|
|
switch c := line[dec.pos]; {
|
|
case c <= ' ':
|
|
return true
|
|
case c == '"':
|
|
goto qvalue
|
|
}
|
|
|
|
// value
|
|
start = dec.pos
|
|
for p, c := range line[dec.pos:] {
|
|
switch {
|
|
case c == '=' || c == '"':
|
|
dec.pos += p
|
|
dec.unexpectedByte(c)
|
|
return false
|
|
case c <= ' ':
|
|
dec.pos += p
|
|
if dec.pos > start {
|
|
dec.value = line[start:dec.pos]
|
|
}
|
|
return true
|
|
}
|
|
}
|
|
dec.pos = len(line)
|
|
if dec.pos > start {
|
|
dec.value = line[start:dec.pos]
|
|
}
|
|
return true
|
|
|
|
qvalue:
|
|
const (
|
|
untermQuote = "unterminated quoted value"
|
|
invalidQuote = "invalid quoted value"
|
|
)
|
|
|
|
hasEsc, esc := false, false
|
|
start = dec.pos
|
|
for p, c := range line[dec.pos+1:] {
|
|
switch {
|
|
case esc:
|
|
esc = false
|
|
case c == '\\':
|
|
hasEsc, esc = true, true
|
|
case c == '"':
|
|
dec.pos += p + 2
|
|
if hasEsc {
|
|
v, ok := unquoteBytes(line[start:dec.pos])
|
|
if !ok {
|
|
dec.syntaxError(invalidQuote)
|
|
return false
|
|
}
|
|
dec.value = v
|
|
} else {
|
|
start++
|
|
end := dec.pos - 1
|
|
if end > start {
|
|
dec.value = line[start:end]
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
}
|
|
dec.pos = len(line)
|
|
dec.syntaxError(untermQuote)
|
|
return false
|
|
}
|
|
|
|
// Key returns the most recent key found by a call to ScanKeyval. The returned
|
|
// slice may point to internal buffers and is only valid until the next call
|
|
// to ScanRecord. It does no allocation.
|
|
func (dec *Decoder) Key() []byte {
|
|
return dec.key
|
|
}
|
|
|
|
// Value returns the most recent value found by a call to ScanKeyval. The
|
|
// returned slice may point to internal buffers and is only valid until the
|
|
// next call to ScanRecord. It does no allocation when the value has no
|
|
// escape sequences.
|
|
func (dec *Decoder) Value() []byte {
|
|
return dec.value
|
|
}
|
|
|
|
// Err returns the first non-EOF error that was encountered by the Scanner.
|
|
func (dec *Decoder) Err() error {
|
|
return dec.err
|
|
}
|
|
|
|
func (dec *Decoder) syntaxError(msg string) {
|
|
dec.err = &SyntaxError{
|
|
Msg: msg,
|
|
Line: dec.lineNum,
|
|
Pos: dec.pos + 1,
|
|
}
|
|
}
|
|
|
|
func (dec *Decoder) unexpectedByte(c byte) {
|
|
dec.err = &SyntaxError{
|
|
Msg: fmt.Sprintf("unexpected %q", c),
|
|
Line: dec.lineNum,
|
|
Pos: dec.pos + 1,
|
|
}
|
|
}
|
|
|
|
// A SyntaxError represents a syntax error in the logfmt input stream.
|
|
type SyntaxError struct {
|
|
Msg string
|
|
Line int
|
|
Pos int
|
|
}
|
|
|
|
func (e *SyntaxError) Error() string {
|
|
return fmt.Sprintf("logfmt syntax error at pos %d on line %d: %s", e.Pos, e.Line, e.Msg)
|
|
}
|