cue/cmd/cue: hoist placement logic from import
Change-Id: I81e5a9881f5883c38f77b72a454010dbb5f191d7
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/5026
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cmd/cue/cmd/common.go b/cmd/cue/cmd/common.go
index 976f9a0..26a1f74 100644
--- a/cmd/cue/cmd/common.go
+++ b/cmd/cue/cmd/common.go
@@ -16,8 +16,11 @@
import (
"bytes"
+ "fmt"
"io"
"os"
+ "path/filepath"
+ "regexp"
"strings"
"testing"
@@ -120,9 +123,14 @@
// If orphanFiles are mixed with CUE files and/or if placement flags are used,
// the instance is also included in insts.
- orphanedData []*build.File
- orphanedSchema []*build.File
- orphanInstance *build.Instance
+ forceOrphanProcessing bool
+ orphanedData []*build.File
+ orphanedSchema []*build.File
+ orphanInstance *build.Instance
+ // imported files are files that were orphaned in the build instance, but
+ // were placed in the instance by using one the --files, --list or --path
+ // flags.
+ imported []*ast.File
expressions []ast.Expr // only evaluate these expressions within results
schema ast.Expr // selects schema in instance for orphaned values
@@ -138,7 +146,7 @@
return buildInstances(b.cmd, b.insts)
}
-func parseArgs(cmd *Command, args []string, cfg *load.Config) (*buildPlan, error) {
+func parseArgs(cmd *Command, args []string, cfg *load.Config) (p *buildPlan, err error) {
if cfg == nil {
cfg = defaultConfig
}
@@ -148,21 +156,30 @@
}
decorateInstances(cmd, flagTags.StringArray(cmd), builds)
- p := &buildPlan{cmd: cmd}
+ p = &buildPlan{cmd: cmd, forceOrphanProcessing: cfg.DataFiles}
if err := p.parseFlags(); err != nil {
return nil, err
}
for _, b := range builds {
+ var ok bool
+ if b.User || p.forceOrphanProcessing {
+ ok, err = p.placeOrphans(b)
+ if err != nil {
+ return nil, err
+ }
+ }
if !b.User {
p.insts = append(p.insts, b)
continue
}
-
if len(b.BuildFiles) > 0 {
p.insts = append(p.insts, b)
}
+ if ok {
+ continue
+ }
if len(b.OrphanedFiles) > 0 {
if p.orphanInstance != nil {
@@ -215,6 +232,100 @@
return nil
}
+func (b *buildPlan) placeOrphans(i *build.Instance) (ok bool, err error) {
+ var (
+ perFile = flagFiles.Bool(b.cmd)
+ useList = flagList.Bool(b.cmd)
+ path = flagPath.StringArray(b.cmd)
+ useContext = flagWithContext.Bool(b.cmd)
+ pkg = flagPackage.String(b.cmd)
+ match = flagGlob.String(b.cmd)
+ )
+ if !b.forceOrphanProcessing && !perFile && !useList && len(path) == 0 {
+ if useContext {
+ return false, fmt.Errorf(
+ "flag %q must be used with at least one of flag %q, %q, or %q",
+ flagWithContext, flagPath, flagList, flagFiles,
+ )
+ }
+ return false, err
+ }
+
+ if pkg == "" {
+ pkg = i.PkgName
+ } else if pkg != "" && i.PkgName != pkg && !flagForce.Bool(b.cmd) {
+ return false, fmt.Errorf(
+ "%q flag clashes with existing package name (%s vs %s)",
+ flagPackage, pkg, i.PkgName,
+ )
+ }
+
+ var files []*ast.File
+
+ re, err := regexp.Compile(match)
+ if err != nil {
+ return false, err
+ }
+
+ for _, f := range i.OrphanedFiles {
+ if !re.MatchString(filepath.Base(f.Filename)) {
+ return false, nil
+ }
+
+ d := encoding.NewDecoder(f, b.encConfig)
+ defer d.Close()
+
+ var objs []ast.Expr
+
+ for ; !d.Done(); d.Next() {
+ if expr := d.Expr(); expr != nil {
+ objs = append(objs, expr)
+ continue
+ }
+ f := d.File()
+ f.Filename = newName(d.Filename(), d.Index())
+ files = append(files, f)
+ }
+
+ if perFile {
+ for i, obj := range objs {
+ f, err := placeOrphans(b.cmd, d.Filename(), pkg, obj)
+ if err != nil {
+ return false, err
+ }
+ f.Filename = newName(d.Filename(), i)
+ files = append(files, f)
+ }
+ continue
+ }
+ if len(objs) > 1 && len(path) == 0 && useList {
+ return false, fmt.Errorf(
+ "%s, %s, or %s flag needed to handle multiple objects in file %s",
+ flagPath, flagList, flagFiles, f.Filename)
+ }
+
+ f, err := placeOrphans(b.cmd, d.Filename(), pkg, objs...)
+ if err != nil {
+ return false, err
+ }
+ f.Filename = newName(d.Filename(), 0)
+ files = append(files, f)
+ }
+
+ b.imported = append(b.imported, files...)
+ for _, f := range files {
+ if err := i.AddSyntax(f); err != nil {
+ return false, err
+ }
+ i.BuildFiles = append(i.BuildFiles, &build.File{
+ Filename: f.Filename,
+ Encoding: build.CUE,
+ Source: f,
+ })
+ }
+ return true, nil
+}
+
func (b *buildPlan) singleInstance() *cue.Instance {
var p *build.Instance
switch len(b.insts) {
@@ -241,8 +352,6 @@
exitIfErr(cmd, inst, inst.Err, true)
}
- decorateInstances(cmd, flagTags.StringArray(cmd), binst)
-
if flagIgnore.Bool(cmd) {
return instances
}
diff --git a/cmd/cue/cmd/import.go b/cmd/cue/cmd/import.go
index 015e43d..18a440a 100644
--- a/cmd/cue/cmd/import.go
+++ b/cmd/cue/cmd/import.go
@@ -16,31 +16,24 @@
import (
"fmt"
- "io"
"io/ioutil"
"os"
"path/filepath"
- "regexp"
"strconv"
"strings"
- "sync"
"unicode"
"github.com/spf13/cobra"
- "golang.org/x/sync/errgroup"
"cuelang.org/go/cue/ast"
"cuelang.org/go/cue/ast/astutil"
- "cuelang.org/go/cue/build"
"cuelang.org/go/cue/format"
"cuelang.org/go/cue/literal"
"cuelang.org/go/cue/load"
"cuelang.org/go/cue/parser"
"cuelang.org/go/cue/token"
"cuelang.org/go/encoding/json"
- "cuelang.org/go/encoding/protobuf"
"cuelang.org/go/internal"
- "cuelang.org/go/internal/encoding"
"cuelang.org/go/internal/third_party/yaml"
)
@@ -231,7 +224,7 @@
cmd.Flags().BoolP(string(flagForce), "f", false, "force overwriting existing files")
cmd.Flags().Bool(string(flagDryrun), false, "only run simulation")
cmd.Flags().BoolP(string(flagRecursive), "R", false, "recursively parse string values")
- cmd.Flags().String("fix", "", "apply given fix")
+ cmd.Flags().String("fix", "", "apply given fix") // XXX
return cmd
}
@@ -245,131 +238,48 @@
// TODO: factor out rooting of orphaned files.
func runImport(cmd *Command, args []string) (err error) {
- var group errgroup.Group
-
- pkgFlag := flagPackage.String(cmd)
-
- done := map[string]bool{}
-
b, err := parseArgs(cmd, args, &load.Config{DataFiles: true})
if err != nil {
return err
}
- builds := b.insts
- if b.orphanInstance != nil {
- builds = append(builds, b.orphanInstance)
- }
- for _, pkg := range builds {
+
+ pkgFlag := flagPackage.String(cmd)
+ for _, pkg := range b.insts {
pkgName := pkgFlag
if pkgName == "" {
pkgName = pkg.PkgName
}
// TODO: allow if there is a unique package name.
- if pkgName == "" && len(builds) > 1 {
+ if pkgName == "" && len(b.insts) > 1 {
err = fmt.Errorf("must specify package name with the -p flag")
- break
- }
- if err = pkg.Err; err != nil {
- break
- }
- if done[pkg.Dir] {
- continue
- }
- done[pkg.Dir] = true
-
- for _, f := range pkg.OrphanedFiles {
- f := f // capture range var
- group.Go(func() error { return handleFile(b, pkgName, f) })
+ exitOnErr(cmd, err, true)
}
}
- err2 := group.Wait()
+ for _, f := range b.imported {
+ err := handleFile(b, f)
+ if err != nil {
+ return err
+ }
+ }
+
exitOnErr(cmd, err, true)
- exitOnErr(cmd, err2, true)
return nil
}
-func handleFile(b *buildPlan, pkg string, file *build.File) (err error) {
- filename := file.Filename
- // filter file names
- re, err := regexp.Compile(flagGlob.String(b.cmd))
- if err != nil {
- return err
- }
- if !re.MatchString(filepath.Base(filename)) {
- return nil
- }
-
- // TODO: consider unifying the two modes.
- var objs []ast.Expr
- i := encoding.NewDecoder(file, b.encConfig)
- defer i.Close()
- for ; !i.Done(); i.Next() {
- if expr := i.Expr(); expr != nil {
- objs = append(objs, expr)
- continue
- }
- if err := processFile(b.cmd, i.File()); err != nil {
- return err
- }
- }
-
- if len(objs) > 0 {
- if err := processStream(b.cmd, pkg, filename, objs); err != nil {
- return err
- }
- }
- return i.Err()
-}
-
-func processFile(cmd *Command, file *ast.File) (err error) {
- name := file.Filename + ".cue"
-
- b, err := format.Node(file)
- if err != nil {
- return err
- }
-
- return ioutil.WriteFile(name, b, 0644)
-}
-
-func processStream(cmd *Command, pkg, filename string, objs []ast.Expr) error {
- if flagFiles.Bool(cmd) {
- for i, f := range objs {
- err := combineExpressions(cmd, pkg, filename, i, f)
- if err != nil {
- return err
- }
- }
- return nil
- } else if len(objs) > 1 {
- if !flagList.Bool(cmd) && len(flagPath.StringArray(cmd)) == 0 && !flagFiles.Bool(cmd) {
- return fmt.Errorf("list, flag, or files flag needed to handle multiple objects in file %q", filename)
- }
- }
- return combineExpressions(cmd, pkg, filename, 0, objs...)
-}
-
-// TODO: implement a more fine-grained approach.
-var mutex sync.Mutex
-
-func combineExpressions(cmd *Command, pkg, filename string, idx int, objs ...ast.Expr) error {
- cueFile := newName(filename, idx)
-
- mutex.Lock()
- defer mutex.Unlock()
-
- if out := flagOut.String(cmd); out != "" {
+func handleFile(b *buildPlan, f *ast.File) (err error) {
+ cueFile := f.Filename
+ if out := flagOut.String(b.cmd); out != "" {
cueFile = out
}
if cueFile != "-" {
switch _, err := os.Stat(cueFile); {
case os.IsNotExist(err):
case err == nil:
- if !flagForce.Bool(cmd) {
+ if !flagForce.Bool(b.cmd) {
// TODO: mimic old behavior: write to stderr, but do not exit
// with error code. Consider what is best to do here.
- stderr := cmd.Command.OutOrStderr()
+ stderr := b.cmd.Command.OutOrStderr()
fmt.Fprintf(stderr, "skipping file %q: already exists\n", cueFile)
return nil
}
@@ -378,16 +288,15 @@
}
}
- f, err := placeOrphans(cmd, filename, pkg, objs)
- if err != nil {
- return err
- }
-
- if flagRecursive.Bool(cmd) {
+ if flagRecursive.Bool(b.cmd) {
h := hoister{fields: map[string]bool{}}
h.hoist(f)
}
+ return writeFile(b.cmd, f, cueFile)
+}
+
+func writeFile(cmd *Command, f *ast.File, cueFile string) error {
b, err := format.Node(f, format.Simplify())
if err != nil {
return fmt.Errorf("error formatting file: %v", err)
@@ -400,7 +309,7 @@
return ioutil.WriteFile(cueFile, b, 0644)
}
-func placeOrphans(cmd *Command, filename, pkg string, objs []ast.Expr) (*ast.File, error) {
+func placeOrphans(cmd *Command, filename, pkg string, objs ...ast.Expr) (*ast.File, error) {
f := &ast.File{}
index := newIndex()
@@ -534,10 +443,6 @@
return filename
}
-func handleProtoDef(cmd *Command, path string, r io.Reader) (f *ast.File, err error) {
- return protobuf.Extract(path, r, &protobuf.Config{Paths: flagProtoPath.StringArray(cmd)})
-}
-
type hoister struct {
fields map[string]bool
}