mirror of https://github.com/tailscale/tailscale/
types/result, util/lineiter: add package for a result type, use it
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>pull/14036/head
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