mirror of
https://github.com/go-sylixos/elvish.git
synced 2024-12-12 17:27:50 +08:00
Change all builtin commands that write to byte output to surface write errors.
Also replace (*Frame).OutputFile with (*Frame).ByteOutput, which returns a small interface for writing bytes and converts EPIPE to ReaderGone.
This commit is contained in:
parent
641f0ebf04
commit
067c809fc5
|
@ -70,7 +70,7 @@ func BenchmarkOutputCapture_Values(b *testing.B) {
|
|||
func BenchmarkOutputCapture_Bytes(b *testing.B) {
|
||||
bytesToWrite := []byte("test")
|
||||
benchmarkOutputCapture(b.N, func(fm *Frame) {
|
||||
fm.OutputFile().Write(bytesToWrite)
|
||||
fm.ByteOutput().Write(bytesToWrite)
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -78,7 +78,7 @@ func BenchmarkOutputCapture_Mixed(b *testing.B) {
|
|||
bytesToWrite := []byte("test")
|
||||
benchmarkOutputCapture(b.N, func(fm *Frame) {
|
||||
fm.OutputChan() <- false
|
||||
fm.OutputFile().Write(bytesToWrite)
|
||||
fm.ByteOutput().Write(bytesToWrite)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -84,14 +84,14 @@ func _gc() {
|
|||
//
|
||||
// This is only useful for debug purposes.
|
||||
|
||||
func _stack(fm *Frame) {
|
||||
out := fm.OutputFile()
|
||||
func _stack(fm *Frame) error {
|
||||
// TODO(xiaq): Dup with main.go.
|
||||
buf := make([]byte, 1024)
|
||||
for runtime.Stack(buf, true) == cap(buf) {
|
||||
buf = make([]byte, cap(buf)*2)
|
||||
}
|
||||
out.Write(buf)
|
||||
_, err := fm.ByteOutput().Write(buf)
|
||||
return err
|
||||
}
|
||||
|
||||
//elvdoc:fn -log
|
||||
|
|
|
@ -187,14 +187,21 @@ type printOpts struct{ Sep string }
|
|||
|
||||
func (o *printOpts) SetDefaultOptions() { o.Sep = " " }
|
||||
|
||||
func print(fm *Frame, opts printOpts, args ...interface{}) {
|
||||
out := fm.OutputFile()
|
||||
func print(fm *Frame, opts printOpts, args ...interface{}) error {
|
||||
out := fm.ByteOutput()
|
||||
for i, arg := range args {
|
||||
if i > 0 {
|
||||
out.WriteString(opts.Sep)
|
||||
_, err := out.WriteString(opts.Sep)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
_, err := out.WriteString(vals.ToString(arg))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
out.WriteString(vals.ToString(arg))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//elvdoc:fn printf
|
||||
|
@ -272,13 +279,14 @@ func print(fm *Frame, opts printOpts, args ...interface{}) {
|
|||
//
|
||||
// @cf print echo pprint repr
|
||||
|
||||
func printf(fm *Frame, template string, args ...interface{}) {
|
||||
func printf(fm *Frame, template string, args ...interface{}) error {
|
||||
wrappedArgs := make([]interface{}, len(args))
|
||||
for i, arg := range args {
|
||||
wrappedArgs[i] = formatter{arg}
|
||||
}
|
||||
|
||||
fmt.Fprintf(fm.OutputFile(), template, wrappedArgs...)
|
||||
_, err := fmt.Fprintf(fm.ByteOutput(), template, wrappedArgs...)
|
||||
return err
|
||||
}
|
||||
|
||||
type formatter struct {
|
||||
|
@ -372,9 +380,13 @@ func writeFmt(state fmt.State, v rune, val interface{}) {
|
|||
//
|
||||
// Etymology: Bourne sh.
|
||||
|
||||
func echo(fm *Frame, opts printOpts, args ...interface{}) {
|
||||
print(fm, opts, args...)
|
||||
fm.OutputFile().WriteString("\n")
|
||||
func echo(fm *Frame, opts printOpts, args ...interface{}) error {
|
||||
err := print(fm, opts, args...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = fm.ByteOutput().WriteString("\n")
|
||||
return err
|
||||
}
|
||||
|
||||
//elvdoc:fn pprint
|
||||
|
@ -404,12 +416,19 @@ func echo(fm *Frame, opts printOpts, args ...interface{}) {
|
|||
//
|
||||
// @cf repr
|
||||
|
||||
func pprint(fm *Frame, args ...interface{}) {
|
||||
out := fm.OutputFile()
|
||||
func pprint(fm *Frame, args ...interface{}) error {
|
||||
out := fm.ByteOutput()
|
||||
for _, arg := range args {
|
||||
out.WriteString(vals.Repr(arg, 0))
|
||||
out.WriteString("\n")
|
||||
_, err := out.WriteString(vals.Repr(arg, 0))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = out.WriteString("\n")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//elvdoc:fn repr
|
||||
|
@ -430,15 +449,22 @@ func pprint(fm *Frame, args ...interface{}) {
|
|||
//
|
||||
// Etymology: [Python](https://docs.python.org/3/library/functions.html#repr).
|
||||
|
||||
func repr(fm *Frame, args ...interface{}) {
|
||||
out := fm.OutputFile()
|
||||
func repr(fm *Frame, args ...interface{}) error {
|
||||
out := fm.ByteOutput()
|
||||
for i, arg := range args {
|
||||
if i > 0 {
|
||||
out.WriteString(" ")
|
||||
_, err := out.WriteString(" ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
_, err := out.WriteString(vals.Repr(arg, vals.NoPretty))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
out.WriteString(vals.Repr(arg, vals.NoPretty))
|
||||
}
|
||||
out.WriteString("\n")
|
||||
_, err := out.WriteString("\n")
|
||||
return err
|
||||
}
|
||||
|
||||
//elvdoc:fn show
|
||||
|
@ -462,9 +488,14 @@ func repr(fm *Frame, args ...interface{}) {
|
|||
// [tty 3], line 1: e = ?(fail lorem-ipsum)
|
||||
// ```
|
||||
|
||||
func show(fm *Frame, v diag.Shower) {
|
||||
fm.OutputFile().WriteString(v.Show(""))
|
||||
fm.OutputFile().WriteString("\n")
|
||||
func show(fm *Frame, v diag.Shower) error {
|
||||
out := fm.ByteOutput()
|
||||
_, err := out.WriteString(v.Show(""))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = out.WriteString("\n")
|
||||
return err
|
||||
}
|
||||
|
||||
const bytesReadBufferSize = 512
|
||||
|
@ -495,22 +526,8 @@ func onlyBytes(fm *Frame) error {
|
|||
// Make sure the goroutine has finished before returning.
|
||||
defer func() { <-valuesDone }()
|
||||
|
||||
// Forward bytes.
|
||||
buf := make([]byte, bytesReadBufferSize)
|
||||
for {
|
||||
nr, errRead := fm.InputFile().Read(buf[:])
|
||||
if nr > 0 {
|
||||
// Even when there are write errors, we will continue reading. So we
|
||||
// ignore the error.
|
||||
fm.OutputFile().Write(buf[:nr])
|
||||
}
|
||||
if errRead != nil {
|
||||
if errRead == io.EOF {
|
||||
return nil
|
||||
}
|
||||
return errRead
|
||||
}
|
||||
}
|
||||
_, err := io.Copy(fm.ByteOutput(), fm.InputFile())
|
||||
return err
|
||||
}
|
||||
|
||||
//elvdoc:fn only-values
|
||||
|
@ -711,9 +728,10 @@ func fromJSONInterface(v interface{}) (interface{}, error) {
|
|||
// @cf from-lines
|
||||
|
||||
func toLines(fm *Frame, inputs Inputs) {
|
||||
out := fm.OutputFile()
|
||||
out := fm.ByteOutput()
|
||||
|
||||
inputs(func(v interface{}) {
|
||||
// TODO: Don't ignore the error.
|
||||
fmt.Fprintln(out, vals.ToString(v))
|
||||
})
|
||||
}
|
||||
|
@ -738,7 +756,7 @@ func toLines(fm *Frame, inputs Inputs) {
|
|||
// @cf from-json
|
||||
|
||||
func toJSON(fm *Frame, inputs Inputs) error {
|
||||
encoder := json.NewEncoder(fm.OutputFile())
|
||||
encoder := json.NewEncoder(fm.ByteOutput())
|
||||
|
||||
var errEncode error
|
||||
inputs(func(v interface{}) {
|
||||
|
|
|
@ -473,7 +473,10 @@ func timeCmd(fm *Frame, opts timeOpt, f Callable) error {
|
|||
err = errCb
|
||||
}
|
||||
} else {
|
||||
fmt.Fprintln(fm.OutputFile(), dt)
|
||||
_, errWrite := fmt.Fprintln(fm.ByteOutput(), dt)
|
||||
if err == nil {
|
||||
err = errWrite
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
|
|
|
@ -21,6 +21,14 @@ func TestPipeline_Unix(t *testing.T) {
|
|||
"{ yes; reached = $true } | nop",
|
||||
"put $reached",
|
||||
).Puts(false),
|
||||
// Internal commands that encounters EPIPE also raises ReaderGone, which
|
||||
// is then suppressed.
|
||||
That("while $true { echo y } | nop").DoesNothing(),
|
||||
That(
|
||||
"var reached = $false",
|
||||
"{ while $true { echo y }; reached = $true } | nop",
|
||||
"put $reached",
|
||||
).Puts(false),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -100,12 +100,12 @@ func (fm *Frame) OutputChan() chan<- interface{} {
|
|||
return fm.ports[1].Chan
|
||||
}
|
||||
|
||||
// OutputFile returns a file onto which output can be written.
|
||||
func (fm *Frame) OutputFile() *os.File {
|
||||
return fm.ports[1].File
|
||||
// ByteOutput returns a handle for writing byte outputs.
|
||||
func (fm *Frame) ByteOutput() ByteOutput {
|
||||
return byteOutput{fm.ports[1].File}
|
||||
}
|
||||
|
||||
// ErrorFile returns a file onto which error messages can be written.
|
||||
// ByteErrorFile returns a file onto which error messages can be written.
|
||||
func (fm *Frame) ErrorFile() *os.File {
|
||||
return fm.ports[2].File
|
||||
}
|
||||
|
|
|
@ -6,7 +6,9 @@ import (
|
|||
"io"
|
||||
"os"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"src.elv.sh/pkg/eval/errs"
|
||||
"src.elv.sh/pkg/eval/vals"
|
||||
"src.elv.sh/pkg/strutil"
|
||||
)
|
||||
|
@ -231,3 +233,37 @@ func PortsFromFiles(files [3]*os.File, prefix string) ([]*Port, func()) {
|
|||
cleanup2()
|
||||
}
|
||||
}
|
||||
|
||||
// ByteOutput defines the interface through which builtin commands access the
|
||||
// byte output.
|
||||
//
|
||||
// It is a thin wrapper around the underlying *os.File value, only exposing
|
||||
// the necessary methods for writing bytes and strings, and converting any
|
||||
// syscall.EPIPE errors to errs.ReaderGone.
|
||||
type ByteOutput interface {
|
||||
io.Writer
|
||||
io.StringWriter
|
||||
}
|
||||
|
||||
type byteOutput struct {
|
||||
f *os.File
|
||||
}
|
||||
|
||||
func (bo byteOutput) Write(p []byte) (int, error) {
|
||||
n, err := bo.f.Write(p)
|
||||
return n, convertReaderGone(err)
|
||||
}
|
||||
|
||||
func (bo byteOutput) WriteString(s string) (int, error) {
|
||||
n, err := bo.f.WriteString(s)
|
||||
return n, convertReaderGone(err)
|
||||
}
|
||||
|
||||
func convertReaderGone(err error) error {
|
||||
if pathErr, ok := err.(*os.PathError); ok {
|
||||
if pathErr.Err == syscall.EPIPE {
|
||||
return errs.ReaderGone{}
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user