cmd/cue/cmd: consolidate placement logic to single file

Change-Id: I0c1adb061eef67f3a00ad11983e78bd435118f47
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/5028
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cmd/cue/cmd/common.go b/cmd/cue/cmd/common.go
index 26a1f74..7ee0f4a 100644
--- a/cmd/cue/cmd/common.go
+++ b/cmd/cue/cmd/common.go
@@ -16,11 +16,8 @@
 
 import (
 	"bytes"
-	"fmt"
 	"io"
 	"os"
-	"path/filepath"
-	"regexp"
 	"strings"
 	"testing"
 
@@ -232,100 +229,6 @@
 	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) {
diff --git a/cmd/cue/cmd/import.go b/cmd/cue/cmd/import.go
index 18a440a..36e1381 100644
--- a/cmd/cue/cmd/import.go
+++ b/cmd/cue/cmd/import.go
@@ -18,8 +18,6 @@
 	"fmt"
 	"io/ioutil"
 	"os"
-	"path/filepath"
-	"strconv"
 	"strings"
 	"unicode"
 
@@ -33,7 +31,6 @@
 	"cuelang.org/go/cue/parser"
 	"cuelang.org/go/cue/token"
 	"cuelang.org/go/encoding/json"
-	"cuelang.org/go/internal"
 	"cuelang.org/go/internal/third_party/yaml"
 )
 
@@ -309,140 +306,6 @@
 	return ioutil.WriteFile(cueFile, b, 0644)
 }
 
-func placeOrphans(cmd *Command, filename, pkg string, objs ...ast.Expr) (*ast.File, error) {
-	f := &ast.File{}
-
-	index := newIndex()
-	for i, expr := range objs {
-
-		// Compute a path different from root.
-		var pathElems []ast.Label
-
-		switch {
-		case len(flagPath.StringArray(cmd)) > 0:
-			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 nil, err
-			}
-
-			for _, str := range flagPath.StringArray(cmd) {
-				l, err := parser.ParseExpr("--path", str)
-				if err != nil {
-					return nil, fmt.Errorf(`labels are of form "cue import -l foo -l 'strings.ToLower(bar)'": %v`, err)
-				}
-
-				str, err := inst.Eval(l).String()
-				if err != nil {
-					return nil, fmt.Errorf("unsupported label path type: %v", err)
-				}
-				pathElems = append(pathElems, ast.NewString(str))
-			}
-		}
-
-		if flagList.Bool(cmd) {
-			idx := index
-			for _, e := range pathElems {
-				idx = idx.label(e)
-			}
-			if idx.field.Value == nil {
-				idx.field.Value = &ast.ListLit{
-					Lbrack: token.NoSpace.Pos(),
-					Rbrack: token.NoSpace.Pos(),
-				}
-			}
-			list := idx.field.Value.(*ast.ListLit)
-			list.Elts = append(list.Elts, expr)
-		} else if len(pathElems) == 0 {
-			obj, ok := expr.(*ast.StructLit)
-			if !ok {
-				if _, ok := expr.(*ast.ListLit); ok {
-					return nil, fmt.Errorf("expected struct as object root, did you mean to use the --list flag?")
-				}
-				return nil, fmt.Errorf("cannot map non-struct to object root")
-			}
-			f.Decls = append(f.Decls, obj.Elts...)
-		} else {
-			field := &ast.Field{Label: pathElems[0]}
-			f.Decls = append(f.Decls, field)
-			for _, e := range pathElems[1:] {
-				newField := &ast.Field{Label: e}
-				newVal := ast.NewStruct(newField)
-				field.Value = newVal
-				field = newField
-			}
-			field.Value = expr
-		}
-	}
-
-	if pkg != "" {
-		p := &ast.Package{Name: ast.NewIdent(pkg)}
-		f.Decls = append([]ast.Decl{p}, f.Decls...)
-	}
-
-	if flagList.Bool(cmd) {
-		switch x := index.field.Value.(type) {
-		case *ast.StructLit:
-			f.Decls = append(f.Decls, x.Elts...)
-		case *ast.ListLit:
-			f.Decls = append(f.Decls, &ast.EmbedDecl{Expr: x})
-		default:
-			panic("unreachable")
-		}
-	}
-
-	return f, nil
-}
-
-type listIndex struct {
-	index map[string]*listIndex
-	field *ast.Field
-}
-
-func newIndex() *listIndex {
-	return &listIndex{
-		index: map[string]*listIndex{},
-		field: &ast.Field{},
-	}
-}
-
-func (x *listIndex) label(label ast.Label) *listIndex {
-	key := internal.DebugStr(label)
-	idx := x.index[key]
-	if idx == nil {
-		if x.field.Value == nil {
-			x.field.Value = &ast.StructLit{}
-		}
-		obj := x.field.Value.(*ast.StructLit)
-		newField := &ast.Field{Label: label}
-		obj.Elts = append(obj.Elts, newField)
-		idx = &listIndex{
-			index: map[string]*listIndex{},
-			field: newField,
-		}
-		x.index[key] = idx
-	}
-	return idx
-}
-
-func newName(filename string, i int) string {
-	ext := filepath.Ext(filename)
-	filename = filename[:len(filename)-len(ext)]
-	if i > 0 {
-		filename += fmt.Sprintf("-%d", i)
-	}
-	filename += ".cue"
-	return filename
-}
-
 type hoister struct {
 	fields map[string]bool
 }
diff --git a/cmd/cue/cmd/orphans.go b/cmd/cue/cmd/orphans.go
new file mode 100644
index 0000000..b6edfe4
--- /dev/null
+++ b/cmd/cue/cmd/orphans.go
@@ -0,0 +1,259 @@
+// Copyright 2020 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 (
+	"fmt"
+	"path/filepath"
+	"regexp"
+	"strconv"
+
+	"cuelang.org/go/cue/ast"
+	"cuelang.org/go/cue/build"
+	"cuelang.org/go/cue/parser"
+	"cuelang.org/go/cue/token"
+	"cuelang.org/go/internal"
+	"cuelang.org/go/internal/encoding"
+)
+
+// This file contains logic for placing orphan files within a CUE namespace.
+
+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 placeOrphans(cmd *Command, filename, pkg string, objs ...ast.Expr) (*ast.File, error) {
+	f := &ast.File{}
+
+	index := newIndex()
+	for i, expr := range objs {
+
+		// Compute a path different from root.
+		var pathElems []ast.Label
+
+		switch {
+		case len(flagPath.StringArray(cmd)) > 0:
+			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 nil, err
+			}
+
+			for _, str := range flagPath.StringArray(cmd) {
+				l, err := parser.ParseExpr("--path", str)
+				if err != nil {
+					return nil, fmt.Errorf(`labels are of form "cue import -l foo -l 'strings.ToLower(bar)'": %v`, err)
+				}
+
+				str, err := inst.Eval(l).String()
+				if err != nil {
+					return nil, fmt.Errorf("unsupported label path type: %v", err)
+				}
+				pathElems = append(pathElems, ast.NewString(str))
+			}
+		}
+
+		if flagList.Bool(cmd) {
+			idx := index
+			for _, e := range pathElems {
+				idx = idx.label(e)
+			}
+			if idx.field.Value == nil {
+				idx.field.Value = &ast.ListLit{
+					Lbrack: token.NoSpace.Pos(),
+					Rbrack: token.NoSpace.Pos(),
+				}
+			}
+			list := idx.field.Value.(*ast.ListLit)
+			list.Elts = append(list.Elts, expr)
+		} else if len(pathElems) == 0 {
+			obj, ok := expr.(*ast.StructLit)
+			if !ok {
+				if _, ok := expr.(*ast.ListLit); ok {
+					return nil, fmt.Errorf("expected struct as object root, did you mean to use the --list flag?")
+				}
+				return nil, fmt.Errorf("cannot map non-struct to object root")
+			}
+			f.Decls = append(f.Decls, obj.Elts...)
+		} else {
+			field := &ast.Field{Label: pathElems[0]}
+			f.Decls = append(f.Decls, field)
+			for _, e := range pathElems[1:] {
+				newField := &ast.Field{Label: e}
+				newVal := ast.NewStruct(newField)
+				field.Value = newVal
+				field = newField
+			}
+			field.Value = expr
+		}
+	}
+
+	if pkg != "" {
+		p := &ast.Package{Name: ast.NewIdent(pkg)}
+		f.Decls = append([]ast.Decl{p}, f.Decls...)
+	}
+
+	if flagList.Bool(cmd) {
+		switch x := index.field.Value.(type) {
+		case *ast.StructLit:
+			f.Decls = append(f.Decls, x.Elts...)
+		case *ast.ListLit:
+			f.Decls = append(f.Decls, &ast.EmbedDecl{Expr: x})
+		default:
+			panic("unreachable")
+		}
+	}
+
+	return f, nil
+}
+
+type listIndex struct {
+	index map[string]*listIndex
+	field *ast.Field
+}
+
+func newIndex() *listIndex {
+	return &listIndex{
+		index: map[string]*listIndex{},
+		field: &ast.Field{},
+	}
+}
+
+func (x *listIndex) label(label ast.Label) *listIndex {
+	key := internal.DebugStr(label)
+	idx := x.index[key]
+	if idx == nil {
+		if x.field.Value == nil {
+			x.field.Value = &ast.StructLit{}
+		}
+		obj := x.field.Value.(*ast.StructLit)
+		newField := &ast.Field{Label: label}
+		obj.Elts = append(obj.Elts, newField)
+		idx = &listIndex{
+			index: map[string]*listIndex{},
+			field: newField,
+		}
+		x.index[key] = idx
+	}
+	return idx
+}
+
+func newName(filename string, i int) string {
+	ext := filepath.Ext(filename)
+	filename = filename[:len(filename)-len(ext)]
+	if i > 0 {
+		filename += fmt.Sprintf("-%d", i)
+	}
+	filename += ".cue"
+	return filename
+}