internal/encoding: support cue file type

Change-Id: Ic8a0bb00c5db92079f8738a9b7179bd86f7c49f5
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/5093
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cmd/cue/cmd/common.go b/cmd/cue/cmd/common.go
index a423b99..4471014 100644
--- a/cmd/cue/cmd/common.go
+++ b/cmd/cue/cmd/common.go
@@ -185,6 +185,7 @@
 					"builds contain two file packages")
 			}
 			p.orphanInstance = b
+			p.encConfig.Stream = true
 		}
 
 		for _, f := range b.OrphanedFiles {
@@ -205,6 +206,9 @@
 		}
 	}
 
+	if len(p.expressions) > 1 {
+		p.encConfig.Stream = true
+	}
 	return p, nil
 }
 
diff --git a/internal/encoding/encoder.go b/internal/encoding/encoder.go
index 305a13e..18d577e 100644
--- a/internal/encoding/encoder.go
+++ b/internal/encoding/encoder.go
@@ -19,27 +19,27 @@
 	"fmt"
 	"io"
 	"os"
+	"path/filepath"
 
 	"cuelang.org/go/cue"
 	"cuelang.org/go/cue/ast"
 	"cuelang.org/go/cue/build"
 	"cuelang.org/go/cue/errors"
+	"cuelang.org/go/cue/format"
 	"cuelang.org/go/cue/token"
+	"cuelang.org/go/internal/filetypes"
 	"cuelang.org/go/pkg/encoding/yaml"
 )
 
-// file.Format
-// file.Info
-
 // An Encoder converts CUE to various file formats, including CUE itself.
 // An Encoder allows
 type Encoder struct {
-	cfg       *Config
-	closer    io.Closer
-	interpret func(cue.Value) (*ast.File, error)
-	encFile   func(*ast.File) error
-	encValue  func(cue.Value) error
-	encInst   func(*cue.Instance) error
+	cfg          *Config
+	closer       io.Closer
+	interpret    func(cue.Value) (*ast.File, error)
+	encFile      func(*ast.File) error
+	encValue     func(cue.Value) error
+	autoSimplify bool
 }
 
 func (e Encoder) Close() error {
@@ -79,6 +79,60 @@
 	}
 
 	switch f.Encoding {
+	case build.CUE:
+		fi, err := filetypes.FromFile(f, cfg.Mode)
+		if err != nil {
+			return nil, err
+		}
+
+		synOpts := []cue.Option{}
+		if !fi.KeepDefaults || !fi.Incomplete {
+			synOpts = append(synOpts, cue.Final())
+		}
+
+		synOpts = append(synOpts,
+			cue.Docs(fi.Docs),
+			cue.Attributes(fi.Attributes),
+			cue.Optional(fi.Optional),
+			cue.Concrete(!fi.Incomplete),
+			cue.Definitions(fi.Definitions),
+			cue.ResolveReferences(!fi.References),
+			cue.DisallowCycles(!fi.Cycles),
+		)
+
+		opts := []format.Option{
+			format.UseSpaces(4),
+			format.TabIndent(false),
+		}
+		opts = append(opts, cfg.Format...)
+
+		useSep := false
+		format := func(name string, n ast.Node) error {
+			if name != "" && cfg.Stream {
+				// TODO: make this relative to DIR
+				fmt.Fprintf(w, "--- %s\n", filepath.Base(name))
+			} else if useSep {
+				fmt.Println("---")
+			}
+			useSep = true
+
+			opts := opts
+			if e.autoSimplify {
+				opts = append(opts, format.Simplify())
+			}
+
+			b, err := format.Node(n, opts...)
+			if err != nil {
+				return err
+			}
+			_, err = w.Write(b)
+			return err
+		}
+		e.encValue = func(v cue.Value) error {
+			return format("", v.Syntax(synOpts...))
+		}
+		e.encFile = func(f *ast.File) error { return format(f.Filename, f) }
+
 	case build.JSON, build.JSONL:
 		// SetEscapeHTML
 		d := json.NewEncoder(w)
@@ -126,6 +180,7 @@
 }
 
 func (e *Encoder) EncodeFile(f *ast.File) error {
+	e.autoSimplify = false
 	return e.encodeFile(f, e.interpret)
 }
 
@@ -134,6 +189,7 @@
 }
 
 func (e *Encoder) Encode(v cue.Value) error {
+	e.autoSimplify = true
 	if e.interpret != nil {
 		f, err := e.interpret(v)
 		if err != nil {
diff --git a/internal/encoding/encoding.go b/internal/encoding/encoding.go
index 300a3f1..22871ea 100644
--- a/internal/encoding/encoding.go
+++ b/internal/encoding/encoding.go
@@ -25,6 +25,8 @@
 	"cuelang.org/go/cue"
 	"cuelang.org/go/cue/ast"
 	"cuelang.org/go/cue/build"
+	"cuelang.org/go/cue/format"
+	"cuelang.org/go/cue/parser"
 	"cuelang.org/go/encoding/json"
 	"cuelang.org/go/encoding/protobuf"
 	"cuelang.org/go/internal/filetypes"
@@ -101,10 +103,12 @@
 	Stdin  io.Reader
 	Stdout io.Writer
 
-	Force bool // overwrite existing files.
+	Force  bool // overwrite existing files.
+	Stream bool // will potentially write more than one document per file
 
 	EscapeHTML bool
 	ProtoPath  []string
+	Format     []format.Option
 }
 
 // NewDecoder returns a stream of non-rooted data expressions. The encoding
@@ -129,6 +133,9 @@
 
 	path := f.Filename
 	switch f.Encoding {
+	case build.CUE:
+		i.file, i.err = parser.ParseFile(path, r, parser.ParseComments)
+		// TODO: verify input format
 	case build.JSON, build.JSONL:
 		i.next = json.NewDecoder(nil, path, r).Extract
 		i.Next()