github.com/kolbycrouch/elvish@v0.14.1-0.20210614162631-215b9ac1c423/pkg/eval/builtin_fn_io.go (about) 1 package eval 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "io" 7 "io/ioutil" 8 "os" 9 "strconv" 10 "strings" 11 12 "src.elv.sh/pkg/diag" 13 "src.elv.sh/pkg/eval/vals" 14 "src.elv.sh/pkg/strutil" 15 ) 16 17 // Input and output. 18 19 func init() { 20 addBuiltinFns(map[string]interface{}{ 21 // Value output 22 "put": put, 23 24 // Bytes input 25 "read-upto": readUpto, 26 "read-line": readLine, 27 28 // Bytes output 29 "print": print, 30 "echo": echo, 31 "pprint": pprint, 32 "repr": repr, 33 "show": show, 34 "printf": printf, 35 36 // Only bytes or values 37 // 38 // These are now implemented as commands forwarding one part of input to 39 // output and discarding the other. A future optimization the evaler can 40 // do is to connect the relevant parts directly together without any 41 // kind of forwarding. 42 "only-bytes": onlyBytes, 43 "only-values": onlyValues, 44 45 // Bytes to value 46 "slurp": slurp, 47 "from-lines": fromLines, 48 "from-json": fromJSON, 49 50 // Value to bytes 51 "to-lines": toLines, 52 "to-json": toJSON, 53 54 // File and pipe 55 "fopen": fopen, 56 "fclose": fclose, 57 "pipe": pipe, 58 "prclose": prclose, 59 "pwclose": pwclose, 60 }) 61 } 62 63 //elvdoc:fn put 64 // 65 // ```elvish 66 // put $value... 67 // ``` 68 // 69 // Takes arbitrary arguments and write them to the structured stdout. 70 // 71 // Examples: 72 // 73 // ```elvish-transcript 74 // ~> put a 75 // ▶ a 76 // ~> put lorem ipsum [a b] { ls } 77 // ▶ lorem 78 // ▶ ipsum 79 // ▶ [a b] 80 // ▶ <closure 0xc4202607e0> 81 // ``` 82 // 83 // Etymology: Various languages, in particular 84 // [C](https://manpages.debian.org/stretch/manpages-dev/puts.3.en.html) and 85 // [Ruby](https://ruby-doc.org/core-2.2.2/IO.html#method-i-puts) as `puts`. 86 87 func put(fm *Frame, args ...interface{}) { 88 out := fm.OutputChan() 89 for _, a := range args { 90 out <- a 91 } 92 } 93 94 //elvdoc:fn read-upto 95 // 96 // ```elvish 97 // read-upto $delim 98 // ``` 99 // 100 // Reads byte input until `$delim` or end-of-file is encountered, and outputs 101 // the part of the input read as a string value. The output contains the 102 // trailing `$delim`, unless `read-upto` terminated at end-of-file. 103 // 104 // The `$delim` argument must be a single rune in the ASCII range. 105 // 106 // Examples: 107 // 108 // ```elvish-transcript 109 // ~> echo "a,b,c" | read-upto "," 110 // ▶ 'a,' 111 // ~> echo "foo\nbar" | read-upto "\n" 112 // ▶ "foo\n" 113 // ~> echo "a.elv\x00b.elv" | read-upto "\x00" 114 // ▶ "a.elv\x00" 115 // ~> print "foobar" | read-upto "\n" 116 // ▶ foobar 117 // ``` 118 119 func readUpto(fm *Frame, last string) (string, error) { 120 if len(last) != 1 { 121 return "", ErrArgs 122 } 123 in := fm.InputFile() 124 var buf []byte 125 for { 126 var b [1]byte 127 _, err := in.Read(b[:]) 128 if err != nil { 129 if err == io.EOF { 130 break 131 } 132 return "", err 133 } 134 buf = append(buf, b[0]) 135 if b[0] == last[0] { 136 break 137 } 138 } 139 return string(buf), nil 140 } 141 142 //elvdoc:fn read-line 143 // 144 // ```elvish 145 // read-line 146 // ``` 147 // 148 // Reads a single line from byte input, and writes the line to the value output, 149 // stripping the line ending. A line can end with `"\r\n"`, `"\n"`, or end of 150 // file. Examples: 151 // 152 // ```elvish-transcript 153 // ~> print line | read-line 154 // ▶ line 155 // ~> print "line\n" | read-line 156 // ▶ line 157 // ~> print "line\r\n" | read-line 158 // ▶ line 159 // ~> print "line-with-extra-cr\r\r\n" | read-line 160 // ▶ "line-with-extra-cr\r" 161 // ``` 162 163 func readLine(fm *Frame) (string, error) { 164 s, err := readUpto(fm, "\n") 165 if err != nil { 166 return "", err 167 } 168 return strutil.ChopLineEnding(s), nil 169 } 170 171 //elvdoc:fn print 172 // 173 // ```elvish 174 // print &sep=' ' $value... 175 // ``` 176 // 177 // Like `echo`, just without the newline. 178 // 179 // @cf echo 180 // 181 // Etymology: Various languages, in particular 182 // [Perl](https://perldoc.perl.org/functions/print.html) and 183 // [zsh](http://zsh.sourceforge.net/Doc/Release/Shell-Builtin-Commands.html), whose 184 // `print`s do not print a trailing newline. 185 186 type printOpts struct{ Sep string } 187 188 func (o *printOpts) SetDefaultOptions() { o.Sep = " " } 189 190 func print(fm *Frame, opts printOpts, args ...interface{}) { 191 out := fm.OutputFile() 192 for i, arg := range args { 193 if i > 0 { 194 out.WriteString(opts.Sep) 195 } 196 out.WriteString(vals.ToString(arg)) 197 } 198 } 199 200 //elvdoc:fn printf 201 // 202 // ```elvish 203 // printf $template $value... 204 // ``` 205 // 206 // Prints values to the byte stream according to a template. 207 // 208 // Like [`print`](#print), this command does not add an implicit newline; use 209 // an explicit `"\n"` in the formatting template instead. 210 // 211 // See Go's [`fmt`](https://golang.org/pkg/fmt/#hdr-Printing) package for 212 // details about the formatting verbs and the various flags that modify the 213 // default behavior, such as padding and justification. 214 // 215 // Unlike Go, each formatting verb has a single associated internal type, and 216 // accepts any argument that can reasonably be converted to that type: 217 // 218 // - The verbs `%s`, `%q` and `%v` convert the corresponding argument to a 219 // string in different ways: 220 // 221 // - `%s` uses [to-string](#to-string) to convert a value to string. 222 // 223 // - `%q` uses [repr](#repr) to convert a value to string. 224 // 225 // - `%v` is equivalent to `%s`, and `%#v` is equivalent to `%q`. 226 // 227 // - The verb `%t` first convert the corresponding argument to a boolean using 228 // [bool](#bool), and then uses its Go counterpart to format the boolean. 229 // 230 // - The verbs `%b`, `%c`, `%d`, `%o`, `%O`, `%x`, `%X` and `%U` first convert 231 // the corresponding argument to an integer using an internal algorithm, and 232 // use their Go counterparts to format the integer. 233 // 234 // - The verbs `%e`, `%E`, `%f`, `%F`, `%g` and `%G` first convert the 235 // corresponding argument to a floating-point number using 236 // [float64](#float64), and then use their Go counterparts to format the 237 // number. 238 // 239 // The special verb `%%` prints a literal `%` and consumes no argument. 240 // 241 // Verbs not documented above are not supported. 242 // 243 // Examples: 244 // 245 // ```elvish-transcript 246 // ~> printf "%10s %.2f\n" Pi $math:pi 247 // Pi 3.14 248 // ~> printf "%-10s %.2f %s\n" Pi $math:pi $math:pi 249 // Pi 3.14 3.141592653589793 250 // ~> printf "%d\n" 0b11100111 251 // 231 252 // ~> printf "%08b\n" 231 253 // 11100111 254 // ~> printf "list is: %q\n" [foo bar 'foo bar'] 255 // list is: [foo bar 'foo bar'] 256 // ``` 257 // 258 // **Note**: Compared to the [POSIX `printf` 259 // command](https://pubs.opengroup.org/onlinepubs/007908799/xcu/printf.html) 260 // found in other shells, there are 3 key differences: 261 // 262 // - The behavior of the formatting verbs are based on Go's 263 // [`fmt`](https://golang.org/pkg/fmt/) package instead of the POSIX 264 // specification. 265 // 266 // - The number of arguments after the formatting template must match the number 267 // of formatting verbs. The POSIX command will repeat the template string to 268 // consume excess values; this command does not have that behavior. 269 // 270 // - This command does not interpret escape sequences such as `\n`; just use 271 // [double-quoted strings](language.html#double-quoted-string). 272 // 273 // @cf print echo pprint repr 274 275 func printf(fm *Frame, template string, args ...interface{}) { 276 wrappedArgs := make([]interface{}, len(args)) 277 for i, arg := range args { 278 wrappedArgs[i] = formatter{arg} 279 } 280 281 fmt.Fprintf(fm.OutputFile(), template, wrappedArgs...) 282 } 283 284 type formatter struct { 285 wrapped interface{} 286 } 287 288 func (f formatter) Format(state fmt.State, r rune) { 289 wrapped := f.wrapped 290 switch r { 291 case 's': 292 writeFmt(state, 's', vals.ToString(wrapped)) 293 case 'q': 294 // TODO: Support using the precision flag to specify indentation. 295 writeFmt(state, 's', vals.Repr(wrapped, vals.NoPretty)) 296 case 'v': 297 var s string 298 if state.Flag('#') { 299 s = vals.Repr(wrapped, vals.NoPretty) 300 } else { 301 s = vals.ToString(wrapped) 302 } 303 writeFmt(state, 's', s) 304 case 't': 305 writeFmt(state, 't', vals.Bool(wrapped)) 306 case 'b', 'c', 'd', 'o', 'O', 'x', 'X', 'U': 307 var i int 308 if err := vals.ScanToGo(wrapped, &i); err != nil { 309 fmt.Fprintf(state, "%%!%c(%s)", r, err.Error()) 310 return 311 } 312 writeFmt(state, r, i) 313 case 'e', 'E', 'f', 'F', 'g', 'G': 314 var f float64 315 if err := vals.ScanToGo(wrapped, &f); err != nil { 316 fmt.Fprintf(state, "%%!%c(%s)", r, err.Error()) 317 return 318 } 319 writeFmt(state, r, f) 320 default: 321 fmt.Fprintf(state, "%%!%c(unsupported formatting verb)", r) 322 } 323 } 324 325 // Writes to State using the flag it stores, but with a potentially different 326 // verb and value. 327 func writeFmt(state fmt.State, v rune, val interface{}) { 328 // Reconstruct the verb string. 329 var sb strings.Builder 330 sb.WriteRune('%') 331 for _, f := range "+-# 0" { 332 if state.Flag(int(f)) { 333 sb.WriteRune(f) 334 } 335 } 336 if w, ok := state.Width(); ok { 337 sb.WriteString(strconv.Itoa(w)) 338 } 339 if p, ok := state.Precision(); ok { 340 sb.WriteRune('.') 341 sb.WriteString(strconv.Itoa(p)) 342 } 343 sb.WriteRune(v) 344 345 fmt.Fprintf(state, sb.String(), val) 346 } 347 348 //elvdoc:fn echo 349 // 350 // ```elvish 351 // echo &sep=' ' $value... 352 // ``` 353 // 354 // Print all arguments, joined by the `sep` option, and followed by a newline. 355 // 356 // Examples: 357 // 358 // ```elvish-transcript 359 // ~> echo Hello elvish 360 // Hello elvish 361 // ~> echo "Hello elvish" 362 // Hello elvish 363 // ~> echo &sep=, lorem ipsum 364 // lorem,ipsum 365 // ``` 366 // 367 // Notes: The `echo` builtin does not treat `-e` or `-n` specially. For instance, 368 // `echo -n` just prints `-n`. Use double-quoted strings to print special 369 // characters, and `print` to suppress the trailing newline. 370 // 371 // @cf print 372 // 373 // Etymology: Bourne sh. 374 375 func echo(fm *Frame, opts printOpts, args ...interface{}) { 376 print(fm, opts, args...) 377 fm.OutputFile().WriteString("\n") 378 } 379 380 //elvdoc:fn pprint 381 // 382 // ```elvish 383 // pprint $value... 384 // ``` 385 // 386 // Pretty-print representations of Elvish values. Examples: 387 // 388 // ```elvish-transcript 389 // ~> pprint [foo bar] 390 // [ 391 // foo 392 // bar 393 // ] 394 // ~> pprint [&k1=v1 &k2=v2] 395 // [ 396 // &k2= 397 // v2 398 // &k1= 399 // v1 400 // ] 401 // ``` 402 // 403 // The output format is subject to change. 404 // 405 // @cf repr 406 407 func pprint(fm *Frame, args ...interface{}) { 408 out := fm.OutputFile() 409 for _, arg := range args { 410 out.WriteString(vals.Repr(arg, 0)) 411 out.WriteString("\n") 412 } 413 } 414 415 //elvdoc:fn repr 416 // 417 // ```elvish 418 // repr $value... 419 // ``` 420 // 421 // Writes representation of `$value`s, separated by space and followed by a 422 // newline. Example: 423 // 424 // ```elvish-transcript 425 // ~> repr [foo 'lorem ipsum'] "aha\n" 426 // [foo 'lorem ipsum'] "aha\n" 427 // ``` 428 // 429 // @cf pprint 430 // 431 // Etymology: [Python](https://docs.python.org/3/library/functions.html#repr). 432 433 func repr(fm *Frame, args ...interface{}) { 434 out := fm.OutputFile() 435 for i, arg := range args { 436 if i > 0 { 437 out.WriteString(" ") 438 } 439 out.WriteString(vals.Repr(arg, vals.NoPretty)) 440 } 441 out.WriteString("\n") 442 } 443 444 //elvdoc:fn show 445 // 446 // ```elvish 447 // show $e 448 // ``` 449 // 450 // Shows the value to the output, which is assumed to be a VT-100-compatible 451 // terminal. 452 // 453 // Currently, the only type of value that can be showed is exceptions, but this 454 // will likely expand in future. 455 // 456 // Example: 457 // 458 // ```elvish-transcript 459 // ~> e = ?(fail lorem-ipsum) 460 // ~> show $e 461 // Exception: lorem-ipsum 462 // [tty 3], line 1: e = ?(fail lorem-ipsum) 463 // ``` 464 465 func show(fm *Frame, v diag.Shower) { 466 fm.OutputFile().WriteString(v.Show("")) 467 fm.OutputFile().WriteString("\n") 468 } 469 470 const bytesReadBufferSize = 512 471 472 //elvdoc:fn only-bytes 473 // 474 // ```elvish 475 // only-bytes 476 // ``` 477 // 478 // Passes byte input to output, and discards value inputs. 479 // 480 // Example: 481 // 482 // ```elvish-transcript 483 // ~> { put value; echo bytes } | only-bytes 484 // bytes 485 // ``` 486 487 func onlyBytes(fm *Frame) error { 488 // Discard values in a goroutine. 489 valuesDone := make(chan struct{}) 490 go func() { 491 for range fm.InputChan() { 492 } 493 close(valuesDone) 494 }() 495 // Make sure the goroutine has finished before returning. 496 defer func() { <-valuesDone }() 497 498 // Forward bytes. 499 buf := make([]byte, bytesReadBufferSize) 500 for { 501 nr, errRead := fm.InputFile().Read(buf[:]) 502 if nr > 0 { 503 // Even when there are write errors, we will continue reading. So we 504 // ignore the error. 505 fm.OutputFile().Write(buf[:nr]) 506 } 507 if errRead != nil { 508 if errRead == io.EOF { 509 return nil 510 } 511 return errRead 512 } 513 } 514 } 515 516 //elvdoc:fn only-values 517 // 518 // ```elvish 519 // only-values 520 // ``` 521 // 522 // Passes value input to output, and discards byte inputs. 523 // 524 // Example: 525 // 526 // ```elvish-transcript 527 // ~> { put value; echo bytes } | only-values 528 // ▶ value 529 // ``` 530 531 func onlyValues(fm *Frame) error { 532 // Forward values in a goroutine. 533 valuesDone := make(chan struct{}) 534 go func() { 535 for v := range fm.InputChan() { 536 fm.OutputChan() <- v 537 } 538 close(valuesDone) 539 }() 540 // Make sure the goroutine has finished before returning. 541 defer func() { <-valuesDone }() 542 543 // Discard bytes. 544 buf := make([]byte, bytesReadBufferSize) 545 for { 546 _, errRead := fm.InputFile().Read(buf[:]) 547 if errRead != nil { 548 if errRead == io.EOF { 549 return nil 550 } 551 return errRead 552 } 553 } 554 } 555 556 //elvdoc:fn slurp 557 // 558 // ```elvish 559 // slurp 560 // ``` 561 // 562 // Reads bytes input into a single string, and put this string on structured 563 // stdout. 564 // 565 // Example: 566 // 567 // ```elvish-transcript 568 // ~> echo "a\nb" | slurp 569 // ▶ "a\nb\n" 570 // ``` 571 // 572 // Etymology: Perl, as 573 // [`File::Slurp`](http://search.cpan.org/~uri/File-Slurp-9999.19/lib/File/Slurp.pm). 574 575 func slurp(fm *Frame) (string, error) { 576 b, err := ioutil.ReadAll(fm.InputFile()) 577 return string(b), err 578 } 579 580 //elvdoc:fn from-lines 581 // 582 // ```elvish 583 // from-lines 584 // ``` 585 // 586 // Splits byte input into lines, and writes them to the value output. Value 587 // input is ignored. 588 // 589 // ```elvish-transcript 590 // ~> { echo a; echo b } | from-lines 591 // ▶ a 592 // ▶ b 593 // ~> { echo a; put b } | from-lines 594 // ▶ a 595 // ``` 596 // 597 // @cf to-lines 598 599 func fromLines(fm *Frame) { 600 linesToChan(fm.InputFile(), fm.OutputChan()) 601 } 602 603 //elvdoc:fn from-json 604 // 605 // ```elvish 606 // from-json 607 // ``` 608 // 609 // Takes bytes stdin, parses it as JSON and puts the result on structured stdout. 610 // The input can contain multiple JSONs, which can, but do not have to, be 611 // separated with whitespaces. 612 // 613 // Examples: 614 // 615 // ```elvish-transcript 616 // ~> echo '"a"' | from-json 617 // ▶ a 618 // ~> echo '["lorem", "ipsum"]' | from-json 619 // ▶ [lorem ipsum] 620 // ~> echo '{"lorem": "ipsum"}' | from-json 621 // ▶ [&lorem=ipsum] 622 // ~> # multiple JSONs running together 623 // echo '"a""b"["x"]' | from-json 624 // ▶ a 625 // ▶ b 626 // ▶ [x] 627 // ~> # multiple JSONs separated by newlines 628 // echo '"a" 629 // {"k": "v"}' | from-json 630 // ▶ a 631 // ▶ [&k=v] 632 // ``` 633 // 634 // @cf to-json 635 636 func fromJSON(fm *Frame) error { 637 in := fm.InputFile() 638 out := fm.OutputChan() 639 640 dec := json.NewDecoder(in) 641 for { 642 var v interface{} 643 err := dec.Decode(&v) 644 if err != nil { 645 if err == io.EOF { 646 return nil 647 } 648 return err 649 } 650 converted, err := fromJSONInterface(v) 651 if err != nil { 652 return err 653 } 654 out <- converted 655 } 656 } 657 658 // Converts a interface{} that results from json.Unmarshal to an Elvish value. 659 func fromJSONInterface(v interface{}) (interface{}, error) { 660 switch v := v.(type) { 661 case nil, bool, string: 662 return v, nil 663 case float64: 664 return v, nil 665 case []interface{}: 666 vec := vals.EmptyList 667 for _, elem := range v { 668 converted, err := fromJSONInterface(elem) 669 if err != nil { 670 return nil, err 671 } 672 vec = vec.Cons(converted) 673 } 674 return vec, nil 675 case map[string]interface{}: 676 m := vals.EmptyMap 677 for key, val := range v { 678 convertedVal, err := fromJSONInterface(val) 679 if err != nil { 680 return nil, err 681 } 682 m = m.Assoc(key, convertedVal) 683 } 684 return m, nil 685 default: 686 return nil, fmt.Errorf("unexpected json type: %T", v) 687 } 688 } 689 690 //elvdoc:fn to-lines 691 // 692 // ```elvish 693 // to-lines $input? 694 // ``` 695 // 696 // Writes each value input to a separate line in the byte output. Byte input is 697 // ignored. 698 // 699 // ```elvish-transcript 700 // ~> put a b | to-lines 701 // a 702 // b 703 // ~> to-lines [a b] 704 // a 705 // b 706 // ~> { put a; echo b } | to-lines 707 // b 708 // a 709 // ``` 710 // 711 // @cf from-lines 712 713 func toLines(fm *Frame, inputs Inputs) { 714 out := fm.OutputFile() 715 716 inputs(func(v interface{}) { 717 fmt.Fprintln(out, vals.ToString(v)) 718 }) 719 } 720 721 //elvdoc:fn to-json 722 // 723 // ```elvish 724 // to-json 725 // ``` 726 // 727 // Takes structured stdin, convert it to JSON and puts the result on bytes stdout. 728 // 729 // ```elvish-transcript 730 // ~> put a | to-json 731 // "a" 732 // ~> put [lorem ipsum] | to-json 733 // ["lorem","ipsum"] 734 // ~> put [&lorem=ipsum] | to-json 735 // {"lorem":"ipsum"} 736 // ``` 737 // 738 // @cf from-json 739 740 func toJSON(fm *Frame, inputs Inputs) error { 741 encoder := json.NewEncoder(fm.OutputFile()) 742 743 var errEncode error 744 inputs(func(v interface{}) { 745 if errEncode != nil { 746 return 747 } 748 errEncode = encoder.Encode(v) 749 }) 750 return errEncode 751 } 752 753 //elvdoc:fn fopen 754 // 755 // ```elvish 756 // fopen $filename 757 // ``` 758 // 759 // Open a file. Currently, `fopen` only supports opening a file for reading. File 760 // must be closed with `fclose` explicitly. Example: 761 // 762 // ```elvish-transcript 763 // ~> cat a.txt 764 // This is 765 // a file. 766 // ~> f = (fopen a.txt) 767 // ~> cat < $f 768 // This is 769 // a file. 770 // ~> fclose $f 771 // ``` 772 // 773 // This function is deprecated; use [file:open](./file.html#open) instead. 774 // 775 // @cf fclose 776 777 func fopen(name string) (vals.File, error) { 778 // TODO support opening files for writing etc as well. 779 return os.Open(name) 780 } 781 782 //elvdoc:fn fclose 783 // 784 // ```elvish 785 // fclose $file 786 // ``` 787 // 788 // Close a file opened with `fopen`. 789 // 790 // This function is deprecated; use [file:close](./file.html#close) instead. 791 // 792 // @cf fopen 793 794 func fclose(f vals.File) error { 795 return f.Close() 796 } 797 798 //elvdoc:fn pipe 799 // 800 // ```elvish 801 // pipe 802 // ``` 803 // 804 // Create a new Unix pipe that can be used in redirections. 805 // 806 // A pipe contains both the read FD and the write FD. When redirecting command 807 // input to a pipe with `<`, the read FD is used. When redirecting command output 808 // to a pipe with `>`, the write FD is used. It is not supported to redirect both 809 // input and output with `<>` to a pipe. 810 // 811 // Pipes have an OS-dependent buffer, so writing to a pipe without an active reader 812 // does not necessarily block. Pipes **must** be explicitly closed with `prclose` 813 // and `pwclose`. 814 // 815 // Putting values into pipes will cause those values to be discarded. 816 // 817 // Examples (assuming the pipe has a large enough buffer): 818 // 819 // ```elvish-transcript 820 // ~> p = (pipe) 821 // ~> echo 'lorem ipsum' > $p 822 // ~> head -n1 < $p 823 // lorem ipsum 824 // ~> put 'lorem ipsum' > $p 825 // ~> head -n1 < $p 826 // # blocks 827 // # $p should be closed with prclose and pwclose afterwards 828 // ``` 829 // 830 // This function is deprecated; use [file:pipe](./file.html#pipe) instead. 831 // 832 // @cf prclose pwclose 833 834 func pipe() (vals.Pipe, error) { 835 r, w, err := os.Pipe() 836 return vals.NewPipe(r, w), err 837 } 838 839 //elvdoc:fn prclose 840 // 841 // ```elvish 842 // prclose $pipe 843 // ``` 844 // 845 // Close the read end of a pipe. 846 // 847 // This function is deprecated; use [file:prclose](./file.html#prclose) instead. 848 // 849 // @cf pwclose pipe 850 851 func prclose(p vals.Pipe) error { 852 return p.ReadEnd.Close() 853 } 854 855 //elvdoc:fn pwclose 856 // 857 // ```elvish 858 // pwclose $pipe 859 // ``` 860 // 861 // Close the write end of a pipe. 862 // 863 // This function is deprecated; use [file:pwclose](./file.html#pwclose) instead. 864 // 865 // @cf prclose pipe 866 867 func pwclose(p vals.Pipe) error { 868 return p.WriteEnd.Close() 869 }