cmd/cue/cmd: move injection mechanism to cue/load

Less cmd/cue-specific code and prepares for build tag
support.

Change-Id: Ie9e6397b5c046de601d48cf876dac1ae662bdc69
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/7063
Reviewed-by: CUE cueckoo <cueckoo@gmail.com>
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cmd/cue/cmd/common.go b/cmd/cue/cmd/common.go
index a7a75ab..0b03a1c 100644
--- a/cmd/cue/cmd/common.go
+++ b/cmd/cue/cmd/common.go
@@ -376,11 +376,12 @@
 		return nil, err
 	}
 
+	cfg.loadCfg.Tags = flagInject.StringArray(cmd)
+
 	builds := loadFromArgs(cmd, args, cfg.loadCfg)
 	if builds == nil {
 		return nil, errors.Newf(token.NoPos, "invalid args")
 	}
-	decorateInstances(cmd, flagInject.StringArray(cmd), builds)
 
 	for _, b := range builds {
 		if b.Err != nil {
@@ -571,7 +572,12 @@
 }
 
 func buildTools(cmd *Command, tags, args []string) (*cue.Instance, error) {
-	binst := loadFromArgs(cmd, args, &load.Config{Tools: true})
+
+	cfg := &load.Config{
+		Tags:  tags,
+		Tools: true,
+	}
+	binst := loadFromArgs(cmd, args, cfg)
 	if len(binst) == 0 {
 		return nil, nil
 	}
@@ -593,7 +599,6 @@
 		}
 		inst.Files = inst.Files[:k]
 	}
-	decorateInstances(cmd, tags, append(binst, ti))
 
 	insts, err := buildToolInstances(cmd, binst)
 	if err != nil {
diff --git a/cue/load/config.go b/cue/load/config.go
index 6a84732..d32f080 100644
--- a/cue/load/config.go
+++ b/cue/load/config.go
@@ -150,11 +150,62 @@
 	// If Dir is empty, the tool is run in the current directory.
 	Dir string
 
-	// The build and release tags specify build constraints that should be
-	// considered satisfied when processing +build lines. Clients creating a new
-	// context may customize BuildTags, which defaults to empty, but it is
-	// usually an error to customize ReleaseTags, which defaults to the list of
-	// CUE releases the current release is compatible with.
+	// Tags defines boolean tags or key-value pairs to select files to build
+	// or be injected as values in fields.
+	//
+	// Each string is of the form
+	//
+	//     key [ "=" value ]
+	//
+	// where key is a valid CUE identifier and value valid CUE scalar.
+	//
+	// The Tags values are used to both select which files get included in a
+	// build and to inject values into the AST.
+	//
+	//
+	// Value injection
+	//
+	// The Tags values are also used to inject values into fields with a
+	// @tag attribute.
+	//
+	// For any field of the form
+	//
+	//    field: x @tag(key)
+	//
+	// and Tags value for which the name matches key, the field will be
+	// modified to
+	//
+	//   field: x & "value"
+	//
+	// By default, the injected value is treated as a string. Alternatively, a
+	// "type" option of the @tag attribute allows a value to be interpreted as
+	// an int, number, or bool. For instance, for a field
+	//
+	//    field: x @tag(key,type=int)
+	//
+	// an entry "key=2" modifies the field to
+	//
+	//    field: x & 2
+	//
+	// Valid values for type are "int", "number", "bool", and "string".
+	//
+	// A @tag attribute can also define shorthand values, which can be injected
+	// into the fields without having to specify the key. For instance, for
+	//
+	//    environment: string @tag(env,short=prod|staging)
+	//
+	// the Tags entry "prod" sets the environment field to the value "prod".
+	// This is equivalent to a Tags entry of "env=prod".
+	//
+	// The use of @tag does not preclude using any of the usual CUE constraints
+	// to limit the possible values of a field. For instance
+	//
+	//    environment: "prod" | "staging" @tag(env,short=prod|staging)
+	//
+	// ensures the user may only specify "prod" or "staging".
+	Tags []string
+
+	// Deprecated: use Tags
 	BuildTags   []string
 	releaseTags []string
 
diff --git a/cue/load/loader.go b/cue/load/loader.go
index c589296..da5a590 100644
--- a/cue/load/loader.go
+++ b/cue/load/loader.go
@@ -84,6 +84,14 @@
 		a = append(a, l.cueFilesPackage(files))
 	}
 
+	// TODO(api): have API call that returns an error which is the aggregate
+	// of all build errors. Certain errors, like these, hold across builds.
+	if err := injectTags(c.Tags, l.tags); err != nil {
+		for _, p := range a {
+			p.ReportError(err)
+		}
+	}
+
 	return a
 }
 
@@ -102,8 +110,9 @@
 )
 
 type loader struct {
-	cfg *Config
-	stk importStack
+	cfg  *Config
+	stk  importStack
+	tags []tag // tags found in files
 }
 
 func (l *loader) abs(filename string) string {
@@ -195,6 +204,11 @@
 		}
 		d.Close()
 	}
+	tags, err := findTags(p)
+	if err != nil {
+		p.ReportError(err)
+	}
+	l.tags = append(l.tags, tags...)
 }
 
 func cleanImport(path string) string {
diff --git a/cmd/cue/cmd/tags.go b/cue/load/tags.go
similarity index 92%
rename from cmd/cue/cmd/tags.go
rename to cue/load/tags.go
index 00912ef..fad4d48 100644
--- a/cmd/cue/cmd/tags.go
+++ b/cue/load/tags.go
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package cmd
+package load
 
 import (
 	"strings"
@@ -26,13 +26,6 @@
 	"cuelang.org/go/internal/cli"
 )
 
-func decorateInstances(cmd *Command, tags []string, a []*build.Instance) {
-	if len(tags) == 0 {
-		return
-	}
-	exitOnErr(cmd, injectTags(tags, a), true)
-}
-
 // A tag binds an identifier to a field to allow passing command-line values.
 //
 // A tag is of the form
@@ -145,16 +138,7 @@
 	return tags, errs
 }
 
-func injectTags(tags []string, b []*build.Instance) errors.Error {
-	var a []tag
-	for _, p := range b {
-		x, err := findTags(p)
-		if err != nil {
-			return err
-		}
-		a = append(a, x...)
-	}
-
+func injectTags(tags []string, a []tag) errors.Error {
 	// Parses command line args
 	for _, s := range tags {
 		p := strings.Index(s, "=")