cmd/cue/cmd: add vet command

This is a simple vet command to avoid having to use eval
and printing oodles of output each time.

Also make validate obey RequireConcrete option.
This is relevant now eval allows non-concrete values by default

Change-Id: I52b4047014e308fdb708277f120d3a2c66db7768
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/1801
Reviewed-by: Marcel van Lohuizen <mpvl@google.com>
diff --git a/cmd/cue/cmd/common.go b/cmd/cue/cmd/common.go
index 5720d4e..4a03a17 100644
--- a/cmd/cue/cmd/common.go
+++ b/cmd/cue/cmd/common.go
@@ -15,7 +15,11 @@
 package cmd
 
 import (
+	"bytes"
 	"fmt"
+	"io"
+	"os"
+	"strings"
 
 	"cuelang.org/go/cue"
 	"cuelang.org/go/cue/build"
@@ -24,10 +28,31 @@
 	"github.com/spf13/cobra"
 )
 
+func init() {
+	s, err := os.Getwd()
+	if err == nil {
+		cwd = s
+	}
+}
+
+var cwd = "////"
+
+// printHeader is a hacky and unprincipled way to sanatize the package path.
+func printHeader(w io.Writer, inst *cue.Instance) {
+	head := strings.Replace(inst.Dir, cwd, ".", 1)
+	fmt.Fprintf(w, "--- %s\n", head)
+}
+
 func exitIfErr(cmd *cobra.Command, inst *cue.Instance, err error, fatal bool) {
 	if err != nil {
-		fmt.Fprintf(cmd.OutOrStderr(), "--- %s\n", inst.Dir)
-		errors.Print(cmd.OutOrStderr(), err)
+		w := &bytes.Buffer{}
+		printHeader(w, inst)
+		errors.Print(w, err)
+
+		// TODO: do something more principled than this.
+		b := w.Bytes()
+		b = bytes.ReplaceAll(b, []byte(cwd), []byte("."))
+		cmd.OutOrStderr().Write(b)
 		if fatal {
 			exit()
 		}
@@ -42,9 +67,15 @@
 	return buildInstances(cmd, binst)
 }
 
+var (
+	config = &load.Config{
+		Context: build.NewContext(),
+	}
+)
+
 func loadFromArgs(cmd *cobra.Command, args []string) []*build.Instance {
 	log.SetOutput(cmd.OutOrStderr())
-	binst := load.Instances(args, nil)
+	binst := load.Instances(args, config)
 	if len(binst) == 0 {
 		return nil
 	}
diff --git a/cmd/cue/cmd/custom.go b/cmd/cue/cmd/custom.go
index fd7ead9..b31982d 100644
--- a/cmd/cue/cmd/custom.go
+++ b/cmd/cue/cmd/custom.go
@@ -71,7 +71,7 @@
 			// - parse flags and env vars
 			// - constrain current config with config section
 
-			return doTasks(typ, name, tools)
+			return doTasks(cmd, typ, name, tools)
 		},
 	}
 	parent.AddCommand(sub)
@@ -110,9 +110,9 @@
 	return root.Lookup(k.typ, k.name, taskSection)
 }
 
-func doTasks(typ, command string, root *cue.Instance) error {
+func doTasks(cmd *cobra.Command, typ, command string, root *cue.Instance) error {
 	if err := executeTasks(typ, command, root); err != nil {
-		return fmt.Errorf("failed to run instance %q: %v", root.Dir, err)
+		exitIfErr(cmd, root, err, true)
 	}
 	return nil
 }
@@ -121,7 +121,7 @@
 //
 // All tasks are started at once, but will block until tasks that they depend
 // on will continue.
-func executeTasks(typ, command string, root *cue.Instance) error {
+func executeTasks(typ, command string, root *cue.Instance) (err error) {
 	spec := taskKey{typ, command, ""}
 	tasks := spec.lookupTasks(root)
 
diff --git a/cmd/cue/cmd/eval.go b/cmd/cue/cmd/eval.go
index 76a7b96..4f6d9f6 100644
--- a/cmd/cue/cmd/eval.go
+++ b/cmd/cue/cmd/eval.go
@@ -78,7 +78,13 @@
 				format.TabIndent(false),
 			}
 			if exprs == nil {
-				format.Node(w, inst.Value().Syntax(syn...), opts...)
+				v := inst.Value()
+				if *compile {
+					err := v.Validate(cue.RequireConcrete())
+					exitIfErr(cmd, inst, err, false)
+					continue
+				}
+				format.Node(w, v.Syntax(syn...), opts...)
 				fmt.Fprintln(w)
 			}
 			for _, e := range exprs {
diff --git a/cmd/cue/cmd/export.go b/cmd/cue/cmd/export.go
index 26ff01c..5f651d0 100644
--- a/cmd/cue/cmd/export.go
+++ b/cmd/cue/cmd/export.go
@@ -96,6 +96,6 @@
 func init() {
 	rootCmd.AddCommand(exportCmd)
 
-	exportCmd.Flags().StringP("output", "o", "json", "output format (json only for now)")
+	// exportCmd.Flags().StringP("output", "o", "json", "output format (json only for now)")
 	escape = exportCmd.Flags().BoolP("escape", "e", false, "use HTML escaping")
 }
diff --git a/cmd/cue/cmd/vet.go b/cmd/cue/cmd/vet.go
new file mode 100644
index 0000000..c2799f3
--- /dev/null
+++ b/cmd/cue/cmd/vet.go
@@ -0,0 +1,72 @@
+// Copyright 2018 The CUE Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package cmd
+
+import (
+	"cuelang.org/go/cue"
+	"cuelang.org/go/cue/ast"
+	"cuelang.org/go/cue/parser"
+	"cuelang.org/go/cue/token"
+	"github.com/spf13/cobra"
+)
+
+// vetCmd represents the vet command
+var vetCmd = &cobra.Command{
+	Use:   "vet",
+	Short: "A brief description of your command",
+	Long: `A longer description that spans multiple lines and likely contains examples
+and usage of using your command. For example:
+
+Cobra is a CLI library for Go that empowers applications.
+This application is a tool to generate the needed files
+to quickly create a Cobra application.`,
+	RunE: doVet,
+}
+
+func init() {
+	rootCmd.AddCommand(vetCmd)
+}
+
+func doVet(cmd *cobra.Command, args []string) error {
+
+	instances := buildFromArgs(cmd, args)
+
+	var exprs []ast.Expr
+	for _, e := range *expressions {
+		expr, err := parser.ParseExpr(token.NewFileSet(), "<expression flag>", e)
+		if err != nil {
+			return err
+		}
+		exprs = append(exprs, expr)
+	}
+
+	w := cmd.OutOrStdout()
+
+	for _, inst := range instances {
+		// TODO: use ImportPath or some other sanitized path.
+		opt := []cue.Option{
+			cue.Attributes(true),
+			cue.Optional(true),
+			cue.RequireConcrete(),
+			cue.Hidden(true),
+		}
+		err := inst.Value().Validate(opt...)
+		if *fVerbose || err != nil {
+			printHeader(w, inst)
+		}
+		exitIfErr(cmd, inst, err, false)
+	}
+	return nil
+}
diff --git a/cue/types.go b/cue/types.go
index 35a9576..5184904 100644
--- a/cue/types.go
+++ b/cue/types.go
@@ -1077,12 +1077,35 @@
 				return false // mostly to avoid some hypothetical cycle issue
 			}
 		}
+		if o.concrete {
+			if err := isGroundRecursive(v.ctx(), v.eval(v.ctx())); err != nil {
+				list.Add(err)
+			}
+		}
 		return true
 	}, nil)
 	if len(list) > 0 {
 		list.Sort()
 		// list.RemoveMultiples() // TODO: use RemoveMultiples when it is fixed
-		return list
+		// return list
+		return list[0]
+	}
+	return nil
+}
+
+func isGroundRecursive(ctx *context, v value) error {
+	switch x := v.(type) {
+	case *list:
+		for i := 0; i < len(x.a); i++ {
+			v := ctx.manifest(x.at(ctx, i))
+			if err := isGroundRecursive(ctx, v); err != nil {
+				return err
+			}
+		}
+	default:
+		if !x.kind().isGround() {
+			return ctx.mkErr(v, "incomplete value %q", debugStr(ctx, v))
+		}
 	}
 	return nil
 }