cue/format: simplify Node API

Change-Id: Idc5b0c12128d22c71aa977c660e48d4eda5b3481
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/2362
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cmd/cue/cmd/eval.go b/cmd/cue/cmd/eval.go
index 82aa873..21e08fd 100644
--- a/cmd/cue/cmd/eval.go
+++ b/cmd/cue/cmd/eval.go
@@ -122,12 +122,14 @@
 					continue
 				}
 			}
-			format.Node(w, getSyntax(v, syn), opts...)
+			b, _ := format.Node(getSyntax(v, syn), opts...)
+			w.Write(b)
 		}
 		for _, e := range exprs {
 			if len(exprs) > 1 {
 				fmt.Fprint(w, "// ")
-				format.Node(w, e)
+				b, _ := format.Node(e)
+				w.Write(b)
 				fmt.Fprintln(w)
 			}
 			v := inst.Eval(e)
@@ -137,7 +139,8 @@
 					continue
 				}
 			}
-			format.Node(w, getSyntax(v, syn), opts...)
+			b, _ := format.Node(getSyntax(v, syn), opts...)
+			w.Write(b)
 			fmt.Fprintln(w)
 		}
 	}
diff --git a/cmd/cue/cmd/import.go b/cmd/cue/cmd/import.go
index 2bdae4d..44d48da 100644
--- a/cmd/cue/cmd/import.go
+++ b/cmd/cue/cmd/import.go
@@ -15,7 +15,6 @@
 package cmd
 
 import (
-	"bytes"
 	"encoding/json"
 	"fmt"
 	"io"
@@ -355,12 +354,12 @@
 func processFile(cmd *cobra.Command, file *ast.File) (err error) {
 	name := file.Filename + ".cue"
 
-	buf := &bytes.Buffer{}
-	if err := format.Node(buf, file); err != nil {
+	b, err := format.Node(file)
+	if err != nil {
 		return err
 	}
 
-	return ioutil.WriteFile(name, buf.Bytes(), 0644)
+	return ioutil.WriteFile(name, b, 0644)
 }
 
 func processStream(cmd *cobra.Command, pkg, filename string, objs []ast.Expr) error {
@@ -516,17 +515,16 @@
 		}
 	}
 
-	var buf bytes.Buffer
-	err := format.Node(&buf, f, format.Simplify())
+	b, err := format.Node(f, format.Simplify())
 	if err != nil {
 		return fmt.Errorf("error formatting file: %v", err)
 	}
 
 	if cueFile == "-" {
-		_, err := io.Copy(cmd.OutOrStdout(), &buf)
+		_, err := cmd.OutOrStdout().Write(b)
 		return err
 	}
-	return ioutil.WriteFile(cueFile, buf.Bytes(), 0644)
+	return ioutil.WriteFile(cueFile, b, 0644)
 }
 
 type listIndex struct {
diff --git a/cmd/cue/cmd/trim.go b/cmd/cue/cmd/trim.go
index 4d7528a..b19891d 100644
--- a/cmd/cue/cmd/trim.go
+++ b/cmd/cue/cmd/trim.go
@@ -17,7 +17,6 @@
 import (
 	"bytes"
 	"fmt"
-	"io"
 	"io/ioutil"
 	"os"
 	"strconv"
@@ -143,14 +142,13 @@
 				opts = append(opts, format.Simplify())
 			}
 
-			var buf bytes.Buffer
-			err := format.Node(&buf, f, opts...)
+			b, err := format.Node(f, opts...)
 			if err != nil {
 				return fmt.Errorf("error formatting file: %v", err)
 			}
 
 			if dst == "-" {
-				_, err := io.Copy(cmd.OutOrStdout(), &buf)
+				_, err := cmd.OutOrStdout().Write(b)
 				if err != nil {
 					return err
 				}
@@ -159,7 +157,7 @@
 				filename = dst
 			}
 
-			err = ioutil.WriteFile(filename, buf.Bytes(), 0644)
+			err = ioutil.WriteFile(filename, b, 0644)
 			if err != nil {
 				return err
 			}
diff --git a/cue/export_test.go b/cue/export_test.go
index dd4c8cd..392fb60 100644
--- a/cue/export_test.go
+++ b/cue/export_test.go
@@ -15,7 +15,6 @@
 package cue
 
 import (
-	"bytes"
 	"fmt"
 	"log"
 	"strings"
@@ -321,13 +320,12 @@
 			n := root.(*structLit).arcs[0].v
 			v := newValueRoot(ctx, n)
 
-			buf := &bytes.Buffer{}
 			opts := options{raw: !tc.eval}
-			err := format.Node(buf, export(ctx, v.eval(ctx), opts), format.Simplify())
+			b, err := format.Node(export(ctx, v.eval(ctx), opts), format.Simplify())
 			if err != nil {
 				log.Fatal(err)
 			}
-			if got := buf.String(); got != tc.out {
+			if got := string(b); got != tc.out {
 				t.Errorf("\ngot  %v;\nwant %v", got, tc.out)
 			}
 		})
@@ -374,13 +372,12 @@
 			v := inst.Value()
 			ctx := r.index().newContext()
 
-			buf := &bytes.Buffer{}
 			opts := options{raw: false}
-			err = format.Node(buf, export(ctx, v.eval(ctx), opts), format.Simplify())
+			b, err := format.Node(export(ctx, v.eval(ctx), opts), format.Simplify())
 			if err != nil {
 				log.Fatal(err)
 			}
-			if got := strings.TrimSpace(buf.String()); got != tc.out {
+			if got := strings.TrimSpace(string(b)); got != tc.out {
 				t.Errorf("\ngot:\n%v\nwant:\n%v", got, tc.out)
 			}
 		})
diff --git a/cue/format/format.go b/cue/format/format.go
index dfa255a..517ea7e 100644
--- a/cue/format/format.go
+++ b/cue/format/format.go
@@ -18,7 +18,6 @@
 import (
 	"bytes"
 	"fmt"
-	"io"
 	"strings"
 	"text/tabwriter"
 
@@ -70,9 +69,9 @@
 // The function may return early (before the entire result is written) and
 // return a formatting error, for instance due to an incorrect AST.
 //
-func Node(dst io.Writer, node ast.Node, opt ...Option) error {
+func Node(node ast.Node, opt ...Option) ([]byte, error) {
 	cfg := newConfig(opt)
-	return cfg.fprint(dst, node)
+	return cfg.fprint(node)
 }
 
 // Source formats src in canonical cue fmt style and returns the result or an
@@ -97,11 +96,7 @@
 	}
 
 	// print AST
-	var buf bytes.Buffer
-	if err := cfg.fprint(&buf, f); err != nil {
-		return nil, fmt.Errorf("print: %s", err)
-	}
-	return buf.Bytes(), nil
+	return cfg.fprint(f)
 }
 
 type config struct {
@@ -126,11 +121,11 @@
 }
 
 // Config defines the output of Fprint.
-func (cfg *config) fprint(output io.Writer, node interface{}) (err error) {
+func (cfg *config) fprint(node interface{}) (out []byte, err error) {
 	var p printer
 	p.init(cfg)
 	if err = printNode(node, &p); err != nil {
-		return err
+		return p.output, err
 	}
 
 	padchar := byte('\t')
@@ -153,15 +148,14 @@
 
 	err = tw.Flush()
 	if err != nil {
-		return err
+		return buf.Bytes(), err
 	}
 
 	b := buf.Bytes()
 	if !cfg.TabIndent {
 		b = bytes.ReplaceAll(b, []byte{'\t'}, bytes.Repeat([]byte{' '}, cfg.Tabwidth))
 	}
-	_, err = output.Write(b)
-	return err
+	return b, nil
 }
 
 // A formatter walks a syntax.Node, interspersed with comments and spacing
diff --git a/cue/format/format_test.go b/cue/format/format_test.go
index a602529..6316c7b 100644
--- a/cue/format/format_test.go
+++ b/cue/format/format_test.go
@@ -209,13 +209,13 @@
 // Verify that the printer can be invoked during initialization.
 func init() {
 	const name = "foobar"
-	var buf bytes.Buffer
-	if err := Fprint(&buf, &ast.Ident{Name: name}); err != nil {
+	b, err := Fprint(&ast.Ident{Name: name})
+	if err != nil {
 		panic(err) // error in test
 	}
 	// in debug mode, the result contains additional information;
 	// ignore it
-	if s := buf.String(); !debug && s != name {
+	if s := string(b); !debug && s != name {
 		panic("got " + s + ", want " + name)
 	}
 }
@@ -228,10 +228,9 @@
 	if err == nil {
 		t.Error("expected illegal program") // error in test
 	}
-	var buf bytes.Buffer
-	Fprint(&buf, f)
-	if buf.String() != res {
-		t.Errorf("got %q, expected %q", buf.String(), res)
+	b, _ := Fprint(f)
+	if string(b) != res {
+		t.Errorf("got %q, expected %q", string(b), res)
 	}
 }
 func TestPackage(t *testing.T) {
@@ -246,13 +245,12 @@
 			},
 		},
 	}
-	var buf bytes.Buffer
-	err := Node(&buf, f)
+	b, err := Node(f)
 	if err != nil {
 		t.Fatal(err)
 	}
 	const want = "package foo\n\n1\n"
-	if got := buf.String(); got != want {
+	if got := string(b); got != want {
 		t.Errorf("got %q, expected %q", got, want)
 	}
 }
@@ -316,18 +314,16 @@
 	}
 
 	// pretty-print original
-	var buf bytes.Buffer
-	err = (&config{UseSpaces: true, Tabwidth: 8}).fprint(&buf, f1)
+	b, err := (&config{UseSpaces: true, Tabwidth: 8}).fprint(f1)
 	if err != nil {
 		t.Fatal(err)
 	}
 
 	// parse pretty printed original
 	// (//line comments must be interpreted even w/o syntax.ParseComments set)
-	f2, err := parser.ParseFile("", buf.Bytes(),
-		parser.AllErrors, parser.ParseComments)
+	f2, err := parser.ParseFile("", b, parser.AllErrors, parser.ParseComments)
 	if err != nil {
-		t.Fatalf("%s\n%s", err, buf.Bytes())
+		t.Fatalf("%s\n%s", err, b)
 	}
 
 	// At this point the position information of identifiers in f2 should
@@ -363,7 +359,7 @@
 	}
 
 	if t.Failed() {
-		t.Logf("\n%s", buf.Bytes())
+		t.Logf("\n%s", b)
 	}
 }
 
@@ -379,13 +375,12 @@
 			panic(err) // error in test
 		}
 
-		var buf bytes.Buffer
-		err = Fprint(&buf, file.Decls) // only print declarations
+		b, err := Fprint(file.Decls) // only print declarations
 		if err != nil {
 			panic(err) // error in test
 		}
 
-		out := buf.String()
+		out := string(b)
 
 		if out != src {
 			t.Errorf("\ngot : %q\nwant: %q\n", out, src)
diff --git a/encoding/protobuf/examples_test.go b/encoding/protobuf/examples_test.go
index 9e8cba4..a3afe54 100644
--- a/encoding/protobuf/examples_test.go
+++ b/encoding/protobuf/examples_test.go
@@ -15,6 +15,7 @@
 package protobuf_test
 
 import (
+	"fmt"
 	"log"
 	"os"
 	"path/filepath"
@@ -37,7 +38,8 @@
 		log.Fatal(err, "")
 	}
 
-	format.Node(os.Stdout, f)
+	b, _ := format.Node(f)
+	fmt.Println(string(b))
 
 	// Output:
 	// //  Package basic is just that: basic.
diff --git a/encoding/protobuf/protobuf_test.go b/encoding/protobuf/protobuf_test.go
index 5049281..9864728 100644
--- a/encoding/protobuf/protobuf_test.go
+++ b/encoding/protobuf/protobuf_test.go
@@ -46,7 +46,8 @@
 			if f, err := Parse(filename, nil, c); err != nil {
 				fmt.Fprintln(out, err)
 			} else {
-				format.Node(out, f, format.Simplify())
+				b, _ := format.Node(f, format.Simplify())
+				out.Write(b)
 			}
 
 			wantFile := filepath.Join("testdata", filepath.Base(file)+".out.cue")
diff --git a/internal/third_party/yaml/decode_test.go b/internal/third_party/yaml/decode_test.go
index d33e56c..608fe8a 100644
--- a/internal/third_party/yaml/decode_test.go
+++ b/internal/third_party/yaml/decode_test.go
@@ -1,7 +1,6 @@
 package yaml_test
 
 import (
-	"bytes"
 	"errors"
 	"flag"
 	"fmt"
@@ -647,9 +646,8 @@
 			Decls: s.Elts,
 		}
 	}
-	buf := bytes.Buffer{}
-	format.Node(&buf, node)
-	return strings.TrimSpace(buf.String())
+	b, _ := format.Node(node)
+	return strings.TrimSpace(string(b))
 }
 
 func newDecoder(t *testing.T, data string) *yaml.Decoder {