internal/filetypes: expose package test internally

Change-Id: Ia76a84d6123c37c42bf57a0028aae27c4d92227c
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/5402
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cue/load/loader.go b/cue/load/loader.go
index 405fa39..3e3c789 100644
--- a/cue/load/loader.go
+++ b/cue/load/loader.go
@@ -25,7 +25,6 @@
 	"strings"
 	"unicode"
 
-	"cuelang.org/go/cue/ast"
 	"cuelang.org/go/cue/build"
 	"cuelang.org/go/cue/errors"
 	"cuelang.org/go/cue/token"
@@ -54,7 +53,7 @@
 	// TODO: require packages to be placed before files. At some point this
 	// could be relaxed.
 	i := 0
-	for ; i < len(args) && isPkg(args[i]); i++ {
+	for ; i < len(args) && filetypes.IsPackage(args[i]); i++ {
 	}
 
 	a := []*build.Instance{}
@@ -81,40 +80,6 @@
 	return a
 }
 
-func isPkg(s string) bool {
-	if s == "." || s == ".." {
-		return true
-	}
-	if s == "-" {
-		return false
-	}
-
-	// This goes of the assumption that file names may not have a `:` in their
-	// name in cue.
-	// A filename must have an extension or be preceded by a qualifier argument.
-	// So strings of the form foo/bar:baz, where bar is a valid identifier and
-	// absolute package
-	if p := strings.LastIndexByte(s, ':'); p > 0 {
-		if !ast.IsValidIdent(s[p+1:]) {
-			return false
-		}
-		// For a non-pkg, the part before : may only be lowercase and '+'.
-		// In addition, a package necessarily must have a slash of some form.
-		return strings.ContainsAny(s[:p], `/.\`)
-	}
-
-	// Assuming we terminate search for packages once a scoped qualifier is
-	// found, we know that any file without an extension (except maybe '-')
-	// is invalid. We can therefore assume it is a package.
-	// The section may still contain a dot, for instance ./foo/. or ./foo/...
-	return strings.TrimLeft(filepath.Ext(s), ".") == ""
-
-	// NOTE/TODO: we have not needed to check whether it is an absolute package
-	// or whether the package starts with a dot. Potentially we could thus relax
-	// the requirement that packages be dots if it is clear that the package
-	// name will not interfere with command names in all circumstances.
-}
-
 // Mode flags for loadImport and download (in get.go).
 const (
 	// resolveImport means that loadImport should do import path expansion.
diff --git a/cue/load/loader_test.go b/cue/load/loader_test.go
index 524333a..4a05f0c 100644
--- a/cue/load/loader_test.go
+++ b/cue/load/loader_test.go
@@ -351,34 +351,3 @@
 		}
 	}
 }
-
-func TestIsPkg(t *testing.T) {
-	testCases := []struct {
-		in  string
-		out bool
-	}{
-		{".", true},
-		{"..", true},
-		{"../.../foo", true},
-		{".../foo", true},
-		{"./:foo", true},
-		{"foo.bar/foo", true},
-
-		// Not supported yet, but could be and isn't anything else valid.
-		{":foo", true},
-
-		{"foo.bar", false},
-		{"foo:", false},
-		{"foo:bar:baz", false},
-		{"-", false},
-		{"-:foo", false},
-	}
-	for _, tc := range testCases {
-		t.Run(tc.in, func(t *testing.T) {
-			got := isPkg(tc.in)
-			if got != tc.out {
-				t.Errorf("got %v; want %v", got, tc.out)
-			}
-		})
-	}
-}
diff --git a/internal/filetypes/util.go b/internal/filetypes/util.go
new file mode 100644
index 0000000..01d2760
--- /dev/null
+++ b/internal/filetypes/util.go
@@ -0,0 +1,58 @@
+// 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 filetypes
+
+import (
+	"path/filepath"
+	"strings"
+
+	"cuelang.org/go/cue/ast"
+)
+
+// IsPackage reports whether a command-line argument is a package based on its
+// lexical representation alone.
+func IsPackage(s string) bool {
+	if s == "." || s == ".." {
+		return true
+	}
+	if s == "-" {
+		return false
+	}
+
+	// This goes of the assumption that file names may not have a `:` in their
+	// name in cue.
+	// A filename must have an extension or be preceded by a qualifier argument.
+	// So strings of the form foo/bar:baz, where bar is a valid identifier and
+	// absolute package
+	if p := strings.LastIndexByte(s, ':'); p > 0 {
+		if !ast.IsValidIdent(s[p+1:]) {
+			return false
+		}
+		// For a non-pkg, the part before : may only be lowercase and '+'.
+		// In addition, a package necessarily must have a slash of some form.
+		return strings.ContainsAny(s[:p], `/.\`)
+	}
+
+	// Assuming we terminate search for packages once a scoped qualifier is
+	// found, we know that any file without an extension (except maybe '-')
+	// is invalid. We can therefore assume it is a package.
+	// The section may still contain a dot, for instance ./foo/. or ./foo/...
+	return strings.TrimLeft(filepath.Ext(s), ".") == ""
+
+	// NOTE/TODO: we have not needed to check whether it is an absolute package
+	// or whether the package starts with a dot. Potentially we could thus relax
+	// the requirement that packages be dots if it is clear that the package
+	// name will not interfere with command names in all circumstances.
+}
diff --git a/internal/filetypes/util_test.go b/internal/filetypes/util_test.go
new file mode 100644
index 0000000..4c92316
--- /dev/null
+++ b/internal/filetypes/util_test.go
@@ -0,0 +1,48 @@
+// 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 filetypes
+
+import "testing"
+
+func TestIsPackage(t *testing.T) {
+	testCases := []struct {
+		in  string
+		out bool
+	}{
+		{".", true},
+		{"..", true},
+		{"../.../foo", true},
+		{".../foo", true},
+		{"./:foo", true},
+		{"foo.bar/foo", true},
+
+		// Not supported yet, but could be and isn't anything else valid.
+		{":foo", true},
+
+		{"foo.bar", false},
+		{"foo:", false},
+		{"foo:bar:baz", false},
+		{"-", false},
+		{"-:foo", false},
+	}
+	for _, tc := range testCases {
+		t.Run(tc.in, func(t *testing.T) {
+			got := IsPackage(tc.in)
+			if got != tc.out {
+				t.Errorf("got %v; want %v", got, tc.out)
+			}
+		})
+	}
+}