cmd/cue/cmd: add --with-context flag for import
This is relevant for computing labels.
Instead of evaluating labels within the context
of an imported file, it evaluates it within a struct
wrapper where the file contents are assigned to
data, while other contextual information, like
filename is added.
Fixes #193
Change-Id: Iad8119c86c8a64ba1c8f071c87970d4acdf87a0e
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/4904
Reviewed-by: roger peppe <rogpeppe@gmail.com>
diff --git a/cmd/cue/cmd/import.go b/cmd/cue/cmd/import.go
index fdf4164..ec6f45a 100644
--- a/cmd/cue/cmd/import.go
+++ b/cmd/cue/cmd/import.go
@@ -21,6 +21,7 @@
"os"
"path/filepath"
"regexp"
+ "strconv"
"strings"
"sync"
"unicode"
@@ -70,7 +71,7 @@
$ cue import ./... -type=json
-The -path flag
+The --path flag
By default the parsed files are included as emit values. This default can be
overridden by specifying a sequence of labels as you would in a CUE file.
@@ -78,15 +79,32 @@
evaluated within the context of the imported file. label expressions may also
refer to builtin packages, which will be implicitly imported.
+The --with-context flag can be used to evaluate the label expression within
+a struct with the following fields:
+
+{
+ // data holds the original source data
+ // (perhaps one of several records in a file).
+ data: _
+ // filename holds the full path to the file.
+ filename: string
+ // index holds the 0-based index element of the
+ // record within the file. For files containing only
+ // one record, this will be 0.
+ index: uint & <recordCount
+ // recordCount holds the total number of records
+ // within the file.
+ recordCount: int & >=1
+}
Handling multiple documents or streams
To handle Multi-document files, such as concatenated JSON objects or
YAML files with document separators (---) the user must specify either the
--path, -list, or -files flag. The -path flag assign each element to a path
+--path, --list, or --files flag. The -path flag assign each element to a path
(identical paths are treated as usual); -list concatenates the entries, and
--files causes each entry to be written to a different file. The -files flag
-may only be used if files are explicitly imported. The -list flag may be
+--files causes each entry to be written to a different file. The -files flag
+may only be used if files are explicitly imported. The --list flag may be
used in combination with the -path flag, concatenating each entry to the
mapped location.
@@ -135,7 +153,15 @@
name: "booster"
}
- deployment: booster: {
+ # base the path values on the input and file name
+ $ cue import -f --with-context -l '"\(path.Base(filename))" "\(data.kind)"' foo.yaml
+ $ cat foo.cue
+ "foo.yaml": Service: {
+ kind: "Service"
+ name: "booster"
+ }
+
+ "foo.yaml": Deployment: {
kind: "Deployment"
name: "booster
replicas: 1
@@ -208,6 +234,7 @@
cmd.Flags().Bool(string(flagList), false, "concatenate multiple objects into a list")
cmd.Flags().Bool(string(flagFiles), false, "split multiple entries into different files")
cmd.Flags().BoolP(string(flagRecursive), "R", false, "recursively parse string values")
+ cmd.Flags().Bool(string(flagWithContext), false, "import as object with contextual data")
cmd.Flags().String("fix", "", "apply given fix")
@@ -217,8 +244,9 @@
}
const (
- flagFiles flagName = "files"
- flagProtoPath flagName = "proto_path"
+ flagFiles flagName = "files"
+ flagProtoPath flagName = "proto_path"
+ flagWithContext flagName = "with-context"
)
type importStreamFunc func(path string, r io.Reader) ([]ast.Expr, error)
@@ -362,7 +390,7 @@
func processStream(cmd *Command, pkg, filename string, objs []ast.Expr) error {
if flagFiles.Bool(cmd) {
for i, f := range objs {
- err := combineExpressions(cmd, pkg, newName(filename, i), f)
+ err := combineExpressions(cmd, pkg, filename, i, f)
if err != nil {
return err
}
@@ -373,13 +401,15 @@
return fmt.Errorf("list, flag, or files flag needed to handle multiple objects in file %q", filename)
}
}
- return combineExpressions(cmd, pkg, newName(filename, 0), objs...)
+ return combineExpressions(cmd, pkg, filename, 0, objs...)
}
// TODO: implement a more fine-grained approach.
var mutex sync.Mutex
-func combineExpressions(cmd *Command, pkg, cueFile string, objs ...ast.Expr) error {
+func combineExpressions(cmd *Command, pkg, filename string, idx int, objs ...ast.Expr) error {
+ cueFile := newName(filename, idx)
+
mutex.Lock()
defer mutex.Unlock()
@@ -420,13 +450,22 @@
}
index := newIndex()
- for _, expr := range objs {
+ for i, expr := range objs {
// Compute a path different from root.
var pathElems []ast.Label
switch {
case flagPath.String(cmd) != "":
+ expr := expr
+ if flagWithContext.Bool(cmd) {
+ expr = ast.NewStruct(
+ "data", expr,
+ "filename", ast.NewString(filename),
+ "index", ast.NewLit(token.INT, strconv.Itoa(i)),
+ "recordCount", ast.NewLit(token.INT, strconv.Itoa(len(objs))),
+ )
+ }
inst, err := runtime.CompileExpr(expr)
if err != nil {
return err
diff --git a/cmd/cue/cmd/testdata/script/import_context.txt b/cmd/cue/cmd/testdata/script/import_context.txt
new file mode 100644
index 0000000..b2a3802
--- /dev/null
+++ b/cmd/cue/cmd/testdata/script/import_context.txt
@@ -0,0 +1,29 @@
+cue import -o - -f --with-context -l '"\(path.Ext(filename)):\(index+1)/\(recordCount)" "\(data["@name"])"' ./import
+cmp stdout expect-stdout
+-- expect-stdout --
+".jsonl:1/3": elem1: {
+ kind: "Service"
+ "@name": "elem1"
+}
+".jsonl:2/3": elem2: {
+ kind: "Deployment"
+ "@name": "elem2"
+}
+".jsonl:3/3": elem3: {
+ kind: "Service"
+ "@name": "elem3"
+}
+-- import/services.jsonl --
+{
+ "kind": "Service",
+ "@name": "elem1"
+}
+{
+ "kind": "Deployment",
+ "@name": "elem2"
+}
+{
+ "kind": "Service",
+ "@name": "elem3"
+}
+-- cue.mod --