cmd/cue/cmd: support -e for export

Closes #240

Change-Id: Ie6a28d355a1572177bfe558976469f2d1013f48c
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/5000
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cmd/cue/cmd/export.go b/cmd/cue/cmd/export.go
index 4a65036..9403070 100644
--- a/cmd/cue/cmd/export.go
+++ b/cmd/cue/cmd/export.go
@@ -22,6 +22,8 @@
 	"github.com/spf13/cobra"
 
 	"cuelang.org/go/cue"
+	"cuelang.org/go/cue/ast"
+	"cuelang.org/go/cue/parser"
 	"cuelang.org/go/pkg/encoding/yaml"
 )
 
@@ -93,6 +95,8 @@
 	flagMedia.Add(cmd)
 	cmd.Flags().Bool(string(flagEscape), false, "use HTML escaping")
 
+	cmd.Flags().StringArrayP(string(flagExpression), "e", nil, "export this expression only")
+
 	cmd.Flags().StringArrayP(string(flagTags), "t", nil,
 		"set the value of a tagged field")
 
@@ -103,25 +107,49 @@
 	instances := buildFromArgs(cmd, args)
 	w := cmd.OutOrStdout()
 
+	var exprs []ast.Expr
+	for _, e := range flagExpression.StringArray(cmd) {
+		expr, err := parser.ParseExpr("<expression flag>", e)
+		if err != nil {
+			return err
+		}
+		exprs = append(exprs, expr)
+	}
+
+	count := 0
+
 	for _, inst := range instances {
 		root := inst.Value()
-		switch media := flagMedia.String(cmd); media {
-		case "json":
-			err := outputJSON(cmd, w, root)
+		if exprs == nil {
+			err := exportValue(cmd, w, root, count)
 			exitIfErr(cmd, inst, err, true)
-		case "text":
-			err := outputText(w, root)
+			count++
+			continue
+		}
+		for _, e := range exprs {
+			v := inst.Eval(e)
+			exitIfErr(cmd, inst, v.Err(), true)
+			err := exportValue(cmd, w, v, count)
+			count++
 			exitIfErr(cmd, inst, err, true)
-		case "yaml":
-			err := outputYAML(w, root)
-			exitIfErr(cmd, inst, err, true)
-		default:
-			return fmt.Errorf("export: unknown format %q", media)
 		}
 	}
 	return nil
 }
 
+func exportValue(cmd *Command, w io.Writer, v cue.Value, i int) error {
+	switch media := flagMedia.String(cmd); media {
+	case "json":
+		return outputJSON(cmd, w, v)
+	case "text":
+		return outputText(w, v)
+	case "yaml":
+		return outputYAML(w, v, i)
+	default:
+		return fmt.Errorf("export: unknown format %q", media)
+	}
+}
+
 func outputJSON(cmd *Command, w io.Writer, v cue.Value) error {
 	e := json.NewEncoder(w)
 	e.SetIndent("", "    ")
@@ -146,7 +174,10 @@
 	return err
 }
 
-func outputYAML(w io.Writer, v cue.Value) error {
+func outputYAML(w io.Writer, v cue.Value, i int) error {
+	if i > 0 {
+		fmt.Fprintln(w, "---")
+	}
 	str, err := yaml.Marshal(v)
 	if err != nil {
 		return err
diff --git a/cmd/cue/cmd/testdata/script/export_expr.txt b/cmd/cue/cmd/testdata/script/export_expr.txt
new file mode 100644
index 0000000..445d471
--- /dev/null
+++ b/cmd/cue/cmd/testdata/script/export_expr.txt
@@ -0,0 +1,10 @@
+cue export data.cue -e a+c -e d.e.f --out yaml
+cmp stdout expect-stdout
+-- expect-stdout --
+4
+---
+jam
+-- data.cue --
+a: 1
+c: 3
+d: e: f: "jam"