Open
Description
Playground code:
package main
import (
"encoding/json"
"fmt"
"io"
"strings"
)
// (ab)using the fact that the JSON decoder has accumulated over all it's read plus it's working on the new buffer
// it's about to receive now.
type lineCounter struct {
committedLines int
committedBytes int64
uncommitted []byte
uncommittedLines int
}
func (l *lineCounter) LineNumberFromOffset(offset int64) int {
if offset < l.committedBytes {
panic("offset < l.committedBytes")
}
intoUncommitted := offset - l.committedBytes
linesIntoUncommitted := countNewlines(l.uncommitted[:intoUncommitted])
return l.committedLines + linesIntoUncommitted
}
var _ io.Writer = (*lineCounter)(nil)
func (l *lineCounter) Write(buf []byte) (int, error) {
// the fact that `Write()` was called means that the previous uncommitted buffer was consumed fully. "commit" now.
l.committedBytes += int64(len(l.uncommitted))
l.committedLines += l.uncommittedLines
l.uncommittedLines = countNewlines(buf)
l.uncommitted = buf
return len(buf), nil
}
func main() {
lc := &lineCounter{}
jd := json.NewDecoder(io.TeeReader(strings.NewReader(`{
"Foo": "bar"}`), lc))
err := jd.Decode(&struct{ Foo struct{} }{})
if err != nil {
fmt.Printf("err=%v pos=%d\n", err, lc.LineNumberFromOffset(jd.InputOffset()))
}
fmt.Println("Hello, 世界")
}
func countNewlines(buf []byte) int {
newlines := 0
for _, ch := range buf {
if ch == '\n' {
newlines++
}
}
return newlines
}