This adds a new generic result type (motivated by golang/go#70084) to try it out, and uses it in the new lineutil package (replacing the old lineread package), changing that package to return iterators: sometimes over []byte (when the input is all in memory), but sometimes iterators over results of []byte, if errors might happen at runtime. Updates #12912 Updates golang/go#70084 Change-Id: Iacdc1070e661b5fb163907b1e8b07ac7d51d3f83 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>main
parent
809a6eba80
commit
01185e436f
@ -0,0 +1,49 @@ |
|||||||
|
// Copyright (c) Tailscale Inc & AUTHORS
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
// Package result contains the Of result type, which is
|
||||||
|
// either a value or an error.
|
||||||
|
package result |
||||||
|
|
||||||
|
// Of is either a T value or an error.
|
||||||
|
//
|
||||||
|
// Think of it like Rust or Swift's result types.
|
||||||
|
// It's named "Of" because the fully qualified name
|
||||||
|
// for callers reads result.Of[T].
|
||||||
|
type Of[T any] struct { |
||||||
|
v T // valid if Err is nil; invalid if Err is non-nil
|
||||||
|
err error |
||||||
|
} |
||||||
|
|
||||||
|
// Value returns a new result with value v,
|
||||||
|
// without an error.
|
||||||
|
func Value[T any](v T) Of[T] { |
||||||
|
return Of[T]{v: v} |
||||||
|
} |
||||||
|
|
||||||
|
// Error returns a new result with error err.
|
||||||
|
// If err is nil, the returned result is equivalent
|
||||||
|
// to calling Value with T's zero value.
|
||||||
|
func Error[T any](err error) Of[T] { |
||||||
|
return Of[T]{err: err} |
||||||
|
} |
||||||
|
|
||||||
|
// MustValue returns r's result value.
|
||||||
|
// It panics if r.Err returns non-nil.
|
||||||
|
func (r Of[T]) MustValue() T { |
||||||
|
if r.err != nil { |
||||||
|
panic(r.err) |
||||||
|
} |
||||||
|
return r.v |
||||||
|
} |
||||||
|
|
||||||
|
// Value returns r's result value and error.
|
||||||
|
func (r Of[T]) Value() (T, error) { |
||||||
|
return r.v, r.err |
||||||
|
} |
||||||
|
|
||||||
|
// Err returns r's error, if any.
|
||||||
|
// When r.Err returns nil, it's safe to call r.MustValue without it panicking.
|
||||||
|
func (r Of[T]) Err() error { |
||||||
|
return r.err |
||||||
|
} |
||||||
@ -0,0 +1,72 @@ |
|||||||
|
// Copyright (c) Tailscale Inc & AUTHORS
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
// Package lineiter iterates over lines in things.
|
||||||
|
package lineiter |
||||||
|
|
||||||
|
import ( |
||||||
|
"bufio" |
||||||
|
"bytes" |
||||||
|
"io" |
||||||
|
"iter" |
||||||
|
"os" |
||||||
|
|
||||||
|
"tailscale.com/types/result" |
||||||
|
) |
||||||
|
|
||||||
|
// File returns an iterator that reads lines from the named file.
|
||||||
|
//
|
||||||
|
// The returned substrings don't include the trailing newline.
|
||||||
|
// Lines may be empty.
|
||||||
|
func File(name string) iter.Seq[result.Of[[]byte]] { |
||||||
|
f, err := os.Open(name) |
||||||
|
return reader(f, f, err) |
||||||
|
} |
||||||
|
|
||||||
|
// Bytes returns an iterator over the lines in bs.
|
||||||
|
// The returned substrings don't include the trailing newline.
|
||||||
|
// Lines may be empty.
|
||||||
|
func Bytes(bs []byte) iter.Seq[[]byte] { |
||||||
|
return func(yield func([]byte) bool) { |
||||||
|
for len(bs) > 0 { |
||||||
|
i := bytes.IndexByte(bs, '\n') |
||||||
|
if i < 0 { |
||||||
|
yield(bs) |
||||||
|
return |
||||||
|
} |
||||||
|
if !yield(bs[:i]) { |
||||||
|
return |
||||||
|
} |
||||||
|
bs = bs[i+1:] |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Reader returns an iterator over the lines in r.
|
||||||
|
//
|
||||||
|
// The returned substrings don't include the trailing newline.
|
||||||
|
// Lines may be empty.
|
||||||
|
func Reader(r io.Reader) iter.Seq[result.Of[[]byte]] { |
||||||
|
return reader(r, nil, nil) |
||||||
|
} |
||||||
|
|
||||||
|
func reader(r io.Reader, c io.Closer, err error) iter.Seq[result.Of[[]byte]] { |
||||||
|
return func(yield func(result.Of[[]byte]) bool) { |
||||||
|
if err != nil { |
||||||
|
yield(result.Error[[]byte](err)) |
||||||
|
return |
||||||
|
} |
||||||
|
if c != nil { |
||||||
|
defer c.Close() |
||||||
|
} |
||||||
|
bs := bufio.NewScanner(r) |
||||||
|
for bs.Scan() { |
||||||
|
if !yield(result.Value(bs.Bytes())) { |
||||||
|
return |
||||||
|
} |
||||||
|
} |
||||||
|
if err := bs.Err(); err != nil { |
||||||
|
yield(result.Error[[]byte](err)) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,32 @@ |
|||||||
|
// Copyright (c) Tailscale Inc & AUTHORS
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
package lineiter |
||||||
|
|
||||||
|
import ( |
||||||
|
"slices" |
||||||
|
"strings" |
||||||
|
"testing" |
||||||
|
) |
||||||
|
|
||||||
|
func TestBytesLines(t *testing.T) { |
||||||
|
var got []string |
||||||
|
for line := range Bytes([]byte("foo\n\nbar\nbaz")) { |
||||||
|
got = append(got, string(line)) |
||||||
|
} |
||||||
|
want := []string{"foo", "", "bar", "baz"} |
||||||
|
if !slices.Equal(got, want) { |
||||||
|
t.Errorf("got %q; want %q", got, want) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func TestReader(t *testing.T) { |
||||||
|
var got []string |
||||||
|
for line := range Reader(strings.NewReader("foo\n\nbar\nbaz")) { |
||||||
|
got = append(got, string(line.MustValue())) |
||||||
|
} |
||||||
|
want := []string{"foo", "", "bar", "baz"} |
||||||
|
if !slices.Equal(got, want) { |
||||||
|
t.Errorf("got %q; want %q", got, want) |
||||||
|
} |
||||||
|
} |
||||||
Loading…
Reference in new issue