builtin_fn_io.go 20 KB

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