Replace prclose and pwclose with file:close

Rather than having specialized commands make a `file:pipe` object
indexable so we can use the generic `file:close` command. This does not
address existing problems; such as builtins not failing when writing to
a `file:pipe` object if the read-end is closed.

Related #1316
This commit is contained in:
Kurtis Rader 2021-05-22 19:43:21 -07:00
parent 4717debfec
commit 3ded2fb772
5 changed files with 59 additions and 57 deletions

View File

@ -33,6 +33,9 @@ or compiled, even if it is not executed:
- The `fopen` and `fclose` commands are deprecated. Use `file:open` and
`file:close` instead.
- The `prclose` and `pwclose` commands are deprecated. Use `file:close`
instead.
# Notable bugfixes
- Iterating over certain list slices no longer crash Elvish

View File

@ -132,9 +132,9 @@ func (cp *compiler) checkDeprecatedBuiltin(name string, r diag.Ranger) {
case "pipe~":
msg = `the "pipe" command is deprecated; use "file:pipe" instead`
case "prclose~":
msg = `the "prclose" command is deprecated; use "file:prclose" instead`
msg = `the "prclose" command is deprecated; use "file:close $p[r]" instead`
case "pwclose":
msg = `the "pwclose" command is deprecated; use "file:pwclose" instead`
msg = `the "pwclose" command is deprecated; use "file:close $p[w]" instead`
default:
return
}

View File

@ -10,11 +10,9 @@ import (
var Ns = eval.NsBuilder{}.AddGoFns("file:", fns).Ns()
var fns = map[string]interface{}{
"close": close,
"open": open,
"pipe": pipe,
"prclose": prclose,
"pwclose": pwclose,
"close": close,
"open": open,
"pipe": pipe,
}
//elvdoc:fn open
@ -64,16 +62,16 @@ func close(f vals.File) error {
// file:pipe
// ```
//
// Create a new Unix pipe that can be used in redirections.
//
// A pipe contains both the read FD and the write FD. When redirecting command
// input to a pipe with `<`, the read FD is used. When redirecting command output
// to a pipe with `>`, the write FD is used. It is not supported to redirect both
// input and output with `<>` to a pipe.
// Create a new pipe that can be used in redirections. A pipe contains a read-end and write-end.
// Each pipe object is a [pseudo-map](#pseudo-map) with fields `r` (the read-end [file
// object](./language.html#File)) and `w` (the write-end).
// When redirecting command input from a pipe with `<`, the read-end is used. When redirecting
// command output to a pipe with `>`, the write-end is used. Redirecting both input and output with
// `<>` to a pipe is not supported.
//
// Pipes have an OS-dependent buffer, so writing to a pipe without an active reader
// does not necessarily block. Pipes **must** be explicitly closed with `prclose`
// and `pwclose`.
// does not necessarily block. Pipes **must** be explicitly closed with `file:close`.
//
// Putting values into pipes will cause those values to be discarded.
//
@ -85,42 +83,14 @@ func close(f vals.File) error {
// ~> head -n1 < $p
// lorem ipsum
// ~> put 'lorem ipsum' > $p
// ~> head -n1 < $p
// # blocks
// # $p should be closed with prclose and pwclose afterwards
// ~> file:close $p[w] # close the write-end
// ~> head -n1 < $p # blocks unless the write-end is closed
// ~> file:close $p[r] # close the read-end
// ```
//
// @cf prclose pwclose
// @cf close
func pipe() (vals.Pipe, error) {
r, w, err := os.Pipe()
return vals.NewPipe(r, w), err
}
//elvdoc:fn prclose
//
// ```elvish
// file:prclose $pipe
// ```
//
// Close the read end of a pipe.
//
// @cf pwclose pipe
func prclose(p vals.Pipe) error {
return p.ReadEnd.Close()
}
//elvdoc:fn pwclose
//
// ```elvish
// file:pwclose $pipe
// ```
//
// Close the write end of a pipe.
//
// @cf prclose pipe
func pwclose(p vals.Pipe) error {
return p.WriteEnd.Close()
}

View File

@ -14,19 +14,37 @@ func TestFile(t *testing.T) {
}
_, cleanup := testutil.InTestDir()
defer cleanup()
TestWithSetup(t, setup,
That(
"echo haha > out3", "f = (file:open out3)",
"slurp < $f", "file:close $f").Puts("haha\n"),
That(`
echo haha > out3
f = (file:open out3)
slurp < $f
file:close $f
`).Puts("haha\n"),
That(`p = (file:pipe)`, `echo haha > $p `, `pwclose $p`,
`slurp < $p`, `prclose $p`).Puts("haha\n"),
That(`
p = (file:pipe)
echo haha > $p
file:close $p[w]
slurp < $p
file:close $p[r]
`).Puts("haha\n"),
That(`p = (file:pipe)`, `echo Zeppelin > $p`, `file:pwclose $p`,
`echo Sabbath > $p`, `slurp < $p`, `file:prclose $p`).Puts("Zeppelin\n"),
That(`
p = (file:pipe)
echo Zeppelin > $p
file:close $p[w]
echo Sabbath > $p
slurp < $p
file:close $p[r]
`).Puts("Zeppelin\n"),
That(`p = (file:pipe)`, `echo Legolas > $p`, `file:prclose $p`,
`slurp < $p`).Throws(AnyError),
That(`
p = (file:pipe)
echo Legolas > $p
file:close $p[r]
slurp < $p
`).Throws(AnyError),
)
}

View File

@ -44,3 +44,14 @@ func (p Pipe) Hash() uint32 {
func (p Pipe) Repr(int) string {
return fmt.Sprintf("<pipe{%v %v}>", p.ReadEnd.Fd(), p.WriteEnd.Fd())
}
// Index returns the desired pipe endpoint that satisfies a reference such as `$p[r]`.
func (p Pipe) Index(k interface{}) (interface{}, bool) {
switch k {
case "r":
return p.ReadEnd, true
case "w":
return p.WriteEnd, true
}
return nil, false
}