cue/load: be more permissive with file loading

- allow single _tool.cue files
- allow hidden cue files (.foo.cue)

Fixes #127
Fixes #123
Fixes #136

Change-Id: I1909d078cf42dbde9e83fdaf8fafe31e5a7473cf
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/3482
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cmd/cue/cmd/common.go b/cmd/cue/cmd/common.go
index 0678480..2cec1e8 100644
--- a/cmd/cue/cmd/common.go
+++ b/cmd/cue/cmd/common.go
@@ -81,15 +81,15 @@
 }
 
 func buildFromArgs(cmd *Command, args []string) []*cue.Instance {
-	binst := loadFromArgs(cmd, args)
+	binst := loadFromArgs(cmd, args, nil)
 	if binst == nil {
 		return nil
 	}
 	return buildInstances(cmd, binst)
 }
 
-func loadFromArgs(cmd *Command, args []string) []*build.Instance {
-	binst := load.Instances(args, nil)
+func loadFromArgs(cmd *Command, args []string, cfg *load.Config) []*build.Instance {
+	binst := load.Instances(args, cfg)
 	if len(binst) == 0 {
 		return nil
 	}
@@ -135,7 +135,7 @@
 }
 
 func buildTools(cmd *Command, args []string) (*cue.Instance, error) {
-	binst := loadFromArgs(cmd, args)
+	binst := loadFromArgs(cmd, args, &load.Config{Tools: true})
 	if len(binst) == 0 {
 		return nil, nil
 	}
diff --git a/cmd/cue/cmd/get_go.go b/cmd/cue/cmd/get_go.go
index 7414e63..299d9d6 100644
--- a/cmd/cue/cmd/get_go.go
+++ b/cmd/cue/cmd/get_go.go
@@ -319,7 +319,7 @@
 
 func extract(cmd *Command, args []string) error {
 	// determine module root:
-	binst := loadFromArgs(cmd, []string{"."})[0]
+	binst := loadFromArgs(cmd, []string{"."}, nil)[0]
 
 	if err := initInterfaces(); err != nil {
 		return err
diff --git a/cmd/cue/cmd/testdata/script/hidden.txt b/cmd/cue/cmd/testdata/script/hidden.txt
new file mode 100644
index 0000000..cf6f0ec
--- /dev/null
+++ b/cmd/cue/cmd/testdata/script/hidden.txt
@@ -0,0 +1,9 @@
+cue eval
+cmp stdout expect-stdout
+
+-- expect-stdout --
+a: 42
+-- .foo.cue --
+package foo
+
+a: 42
\ No newline at end of file
diff --git a/cmd/cue/cmd/testdata/script/toolonly.txt b/cmd/cue/cmd/testdata/script/toolonly.txt
new file mode 100644
index 0000000..be535bc
--- /dev/null
+++ b/cmd/cue/cmd/testdata/script/toolonly.txt
@@ -0,0 +1,15 @@
+cue cmd foo
+cmp stdout expect-stdout
+
+-- expect-stdout --
+foo
+-- foo_tool.cue --
+package foo
+
+import "tool/cli"
+
+command foo task: {
+	foo: cli.Print & {
+		text: "foo"
+	}
+}
diff --git a/cmd/cue/cmd/trim.go b/cmd/cue/cmd/trim.go
index fede5e5..5101c13 100644
--- a/cmd/cue/cmd/trim.go
+++ b/cmd/cue/cmd/trim.go
@@ -102,7 +102,7 @@
 	internal.DropOptional = true
 	defer func() { internal.DropOptional = false }()
 
-	binst := loadFromArgs(cmd, args)
+	binst := loadFromArgs(cmd, args, nil)
 	if binst == nil {
 		return nil
 	}
diff --git a/cmd/cue/cmd/vet.go b/cmd/cue/cmd/vet.go
index 06c184e..45c9300 100644
--- a/cmd/cue/cmd/vet.go
+++ b/cmd/cue/cmd/vet.go
@@ -85,7 +85,7 @@
 }
 
 func doVet(cmd *Command, args []string) error {
-	builds := loadFromArgs(cmd, args)
+	builds := loadFromArgs(cmd, args, nil)
 	if builds == nil {
 		return nil
 	}
diff --git a/cue/load/errors.go b/cue/load/errors.go
index 3a573d3..9cadd55 100644
--- a/cue/load/errors.go
+++ b/cue/load/errors.go
@@ -116,10 +116,10 @@
 
 // TODO(localize)
 func (e *noCUEError) Error() string {
-	// Count files beginning with _ and ., which we will pretend don't exist at all.
+	// Count files beginning with _, which we will pretend don't exist at all.
 	dummy := 0
 	for _, name := range e.Package.IgnoredCUEFiles {
-		if strings.HasPrefix(name, "_") || strings.HasPrefix(name, ".") {
+		if strings.HasPrefix(name, "_") {
 			dummy++
 		}
 	}
@@ -129,7 +129,17 @@
 
 	if len(e.Package.IgnoredCUEFiles) > dummy {
 		// CUE files exist, but they were ignored due to build constraints.
-		return "build constraints exclude all CUE files in " + path
+		msg := "build constraints exclude all CUE files in " + path + " (ignored: "
+		files := e.Package.IgnoredCUEFiles
+		if len(files) > 4 {
+			files = append(files[:4], "...")
+		}
+		for i, f := range files {
+			files[i] = filepath.ToSlash(f)
+		}
+		msg += strings.Join(files, ", ")
+		msg += ")"
+		return msg
 	}
 	// if len(e.Package.TestCUEFiles) > 0 {
 	// 	// Test CUE files exist, but we're not interested in them.
diff --git a/cue/load/import.go b/cue/load/import.go
index b392804..b134898 100644
--- a/cue/load/import.go
+++ b/cue/load/import.go
@@ -256,12 +256,23 @@
 	}
 }
 
+func countCUEFiles(c *Config, p *build.Instance) int {
+	count := len(p.CUEFiles)
+	if c.Tools {
+		count += len(p.ToolCUEFiles)
+	}
+	if c.Tests {
+		count += len(p.TestCUEFiles)
+	}
+	return count
+}
+
 func (fp *fileProcessor) finalize() errors.Error {
 	p := fp.pkg
 	if fp.err != nil {
 		return fp.err
 	}
-	if len(p.CUEFiles) == 0 && !fp.c.DataFiles {
+	if countCUEFiles(fp.c, p) == 0 && !fp.c.DataFiles {
 		fp.err = errors.Append(fp.err, &noCUEError{Package: p, Dir: p.Dir, Ignored: len(p.IgnoredCUEFiles) > 0})
 		return fp.err
 	}
diff --git a/cue/load/loader_test.go b/cue/load/loader_test.go
index c51f55d..7251920 100644
--- a/cue/load/loader_test.go
+++ b/cue/load/loader_test.go
@@ -37,7 +37,10 @@
 		t.Fatal(err)
 	}
 	testdataDir := filepath.Join(cwd, testdata)
-	dirCfg := &Config{Dir: testdataDir}
+	dirCfg := &Config{
+		Dir:   testdataDir,
+		Tools: true,
+	}
 
 	args := str.StringList
 	testCases := []struct {
@@ -92,7 +95,7 @@
 		cfg:  dirCfg,
 		args: args("./anon"),
 		want: `
-err:    build constraints exclude all CUE files in ./anon
+err:    build constraints exclude all CUE files in ./anon (ignored: anon/anon.cue)
 path:   example.org/test/anon
 module: example.org/test
 root:   $CWD/testdata
@@ -150,7 +153,7 @@
 		cfg:  dirCfg,
 		args: args("example.org/test/hello:nonexist"),
 		want: `
-err:    build constraints exclude all CUE files in example.org/test/hello:nonexist
+err:    build constraints exclude all CUE files in example.org/test/hello:nonexist (ignored: hello/test.cue, anon.cue, test.cue)
 path:   example.org/test/hello:nonexist
 module: example.org/test
 root:   $CWD/testdata
@@ -215,6 +218,27 @@
 imports:
 	acme.com/catch: $CWD/testdata/pkg/acme.com/catch/catch.cue
 	acme.com/helper:helper1: $CWD/testdata/pkg/acme.com/helper/helper1.cue`,
+	}, {
+		cfg:  dirCfg,
+		args: args("./toolonly"),
+		want: `
+path:   example.org/test/toolonly:foo
+module: example.org/test
+root:   $CWD/testdata
+dir:    $CWD/testdata/toolonly
+display:./toolonly`,
+	}, {
+		cfg: &Config{
+			Dir: testdataDir,
+		},
+		args: args("./toolonly"),
+		want: `
+err:    build constraints exclude all CUE files in ./toolonly (ignored: anon.cue, test.cue)
+path:   example.org/test/toolonly:foo
+module: example.org/test
+root:   $CWD/testdata
+dir:    $CWD/testdata/toolonly
+display:./toolonly`,
 	}}
 	for i, tc := range testCases {
 		// if i != 5 {
diff --git a/cue/load/match.go b/cue/load/match.go
index 5f8af37..d751f2f 100644
--- a/cue/load/match.go
+++ b/cue/load/match.go
@@ -43,8 +43,7 @@
 // If allTags is non-nil, matchFile records any encountered build tag
 // by setting allTags[tag] = true.
 func matchFile(cfg *Config, dir, name string, returnImports, allFiles bool, allTags map[string]bool) (match bool, data []byte, filename string, err errors.Error) {
-	if strings.HasPrefix(name, "_") ||
-		strings.HasPrefix(name, ".") {
+	if strings.HasPrefix(name, "_") {
 		return
 	}
 
diff --git a/cue/load/testdata/toolonly/foo_tool.cue b/cue/load/testdata/toolonly/foo_tool.cue
new file mode 100644
index 0000000..6441366
--- /dev/null
+++ b/cue/load/testdata/toolonly/foo_tool.cue
@@ -0,0 +1,9 @@
+package foo
+
+import "tool/cli"
+
+command foo task: {
+	foo: cli.Print & {
+		text: "foo"
+	}
+}