github.com/markusbkk/elvish@v0.0.0-20231204143114-91dc52438621/pkg/eval/builtin_fn_io.go (about)

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