···11+// Copyright 2018 The CUE Authors
22+//
33+// Licensed under the Apache License, Version 2.0 (the "License");
44+// you may not use this file except in compliance with the License.
55+// You may obtain a copy of the License at
66+//
77+// http://www.apache.org/licenses/LICENSE-2.0
88+//
99+// Unless required by applicable law or agreed to in writing, software
1010+// distributed under the License is distributed on an "AS IS" BASIS,
1111+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212+// See the License for the specific language governing permissions and
1313+// limitations under the License.
1414+1515+package load
1616+1717+import (
1818+ "os"
1919+ "path/filepath"
2020+ "runtime"
2121+2222+ "cuelang.org/go/cue/build"
2323+)
2424+2525+const (
2626+ cueSuffix = ".cue"
2727+ defaultDir = "cue"
2828+ modFile = "cue.mod"
2929+)
3030+3131+// FromArgsUsage is a partial usage message that applications calling
3232+// FromArgs may wish to include in their -help output.
3333+//
3434+// Some of the aspects of this documentation, like flags and handling '--' need
3535+// to be implemented by the tools.
3636+const FromArgsUsage = `
3737+<args> is a list of arguments denoting a set of instances.
3838+It may take one of two forms:
3939+4040+1. A list of *.cue source files.
4141+4242+ All of the specified files are loaded, parsed and type-checked
4343+ as a single instance.
4444+4545+2. A list of relative directories to denote a package instance.
4646+4747+ Each directory matching the pattern is loaded as a separate instance.
4848+ The instance contains all files in this directory and ancestor directories,
4949+ up to the module root, with the same package name. The package name must
5050+ be either uniquely determined by the files in the given directory, or
5151+ explicitly defined using the '-p' flag.
5252+5353+ Files without a package clause are ignored.
5454+5555+ Files ending in *_test.cue files are only loaded when testing.
5656+5757+3. A list of import paths, each denoting a package.
5858+5959+ The package's directory is loaded from the package cache. The version of the
6060+ package is defined in the modules cue.mod file.
6161+6262+A '--' argument terminates the list of packages.
6363+`
6464+6565+// A Config configures load behavior.
6666+type Config struct {
6767+ // Context specifies the context for the load operation.
6868+ // If the context is cancelled, the loader may stop early
6969+ // and return an ErrCancelled error.
7070+ // If Context is nil, the load cannot be cancelled.
7171+ Context *build.Context
7272+7373+ loader *loader
7474+7575+ modRoot string // module root for package paths ("" if unknown)
7676+7777+ // cache specifies the package cache in which to look for packages.
7878+ cache string
7979+8080+ // Package defines the name of the package to be loaded. In this is not set,
8181+ // the package must be uniquely defined from its context.
8282+ Package string
8383+8484+ // Dir is the directory in which to run the build system's query tool
8585+ // that provides information about the packages.
8686+ // If Dir is empty, the tool is run in the current directory.
8787+ Dir string
8888+8989+ // The build and release tags specify build constraints that should be
9090+ // considered satisfied when processing +build lines. Clients creating a new
9191+ // context may customize BuildTags, which defaults to empty, but it is
9292+ // usually an error to customize ReleaseTags, which defaults to the list of
9393+ // CUE releases the current release is compatible with.
9494+ BuildTags []string
9595+ releaseTags []string
9696+9797+ // If Tests is set, the loader includes not just the packages
9898+ // matching a particular pattern but also any related test packages.
9999+ Tests bool
100100+101101+ // If Tools is set, the loader includes tool files associated with
102102+ // a package.
103103+ Tools bool
104104+105105+ // If DataFiles is set, the loader includes entries for directories that
106106+ // have no CUE files, but have recognized data files that could be converted
107107+ // to CUE.
108108+ DataFiles bool
109109+110110+ fileSystem
111111+}
112112+113113+func (c Config) newInstance(path string) *build.Instance {
114114+ i := c.Context.NewInstance(path, nil)
115115+ i.DisplayPath = path
116116+ return i
117117+}
118118+119119+func (c Config) newErrInstance(m *match, path string, err error) *build.Instance {
120120+ i := c.Context.NewInstance(path, nil)
121121+ i.DisplayPath = path
122122+ i.ReportError(err)
123123+ return i
124124+}
125125+126126+func (c Config) complete() (cfg *Config, err error) {
127127+ // Each major CUE release should add a tag here.
128128+ // Old tags should not be removed. That is, the cue1.x tag is present
129129+ // in all releases >= CUE 1.x. Code that requires CUE 1.x or later should
130130+ // say "+build cue1.x", and code that should only be built before CUE 1.x
131131+ // (perhaps it is the stub to use in that case) should say "+build !cue1.x".
132132+ c.releaseTags = []string{"cue0.1"}
133133+134134+ if c.Dir == "" {
135135+ c.Dir, err = os.Getwd()
136136+ if err != nil {
137137+ return nil, err
138138+ }
139139+ }
140140+141141+ c.loader = &loader{cfg: &c}
142142+143143+ if c.Context == nil {
144144+ c.Context = build.NewContext(build.Loader(c.loader.loadFunc(c.Dir)))
145145+ }
146146+147147+ if c.cache == "" {
148148+ c.cache = filepath.Join(home(), defaultDir)
149149+ // os.MkdirAll(c.Cache, 0755) // TODO: tools task
150150+ }
151151+152152+ // TODO: determine root on a package basis. Maybe we even need a
153153+ // pkgname.cue.mod
154154+ // Look to see if there is a cue.mod.
155155+ if c.modRoot == "" {
156156+ abs, err := findRoot(c.Dir)
157157+ if err != nil {
158158+ // Not using modules: only consider the current directory.
159159+ c.modRoot = c.Dir
160160+ } else {
161161+ c.modRoot = abs
162162+ }
163163+ }
164164+ return &c, nil
165165+}
166166+167167+func findRoot(dir string) (string, error) {
168168+ abs, err := filepath.Abs(dir)
169169+ if err != nil {
170170+ return "", err
171171+ }
172172+ for {
173173+ info, err := os.Stat(filepath.Join(abs, modFile))
174174+ if err == nil && !info.IsDir() {
175175+ break
176176+ }
177177+ d := filepath.Dir(abs)
178178+ if len(d) >= len(abs) {
179179+ return "", err // reached top of file system, no cue.mod
180180+ }
181181+ abs = d
182182+ }
183183+ return abs, nil
184184+}
185185+186186+func home() string {
187187+ env := "HOME"
188188+ if runtime.GOOS == "windows" {
189189+ env = "USERPROFILE"
190190+ } else if runtime.GOOS == "plan9" {
191191+ env = "home"
192192+ }
193193+ return os.Getenv(env)
194194+}
+16
cue/load/doc.go
···11+// Copyright 2018 The CUE Authors
22+//
33+// Licensed under the Apache License, Version 2.0 (the "License");
44+// you may not use this file except in compliance with the License.
55+// You may obtain a copy of the License at
66+//
77+// http://www.apache.org/licenses/LICENSE-2.0
88+//
99+// Unless required by applicable law or agreed to in writing, software
1010+// distributed under the License is distributed on an "AS IS" BASIS,
1111+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212+// See the License for the specific language governing permissions and
1313+// limitations under the License.
1414+1515+// Package load loads CUE instances.
1616+package load // import "cuelang.org/go/cue/load"
+150
cue/load/errors.go
···11+// Copyright 2018 The CUE Authors
22+//
33+// Licensed under the Apache License, Version 2.0 (the "License");
44+// you may not use this file except in compliance with the License.
55+// You may obtain a copy of the License at
66+//
77+// http://www.apache.org/licenses/LICENSE-2.0
88+//
99+// Unless required by applicable law or agreed to in writing, software
1010+// distributed under the License is distributed on an "AS IS" BASIS,
1111+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212+// See the License for the specific language governing permissions and
1313+// limitations under the License.
1414+1515+package load
1616+1717+import (
1818+ "fmt"
1919+ "path/filepath"
2020+ "strings"
2121+2222+ build "cuelang.org/go/cue/build"
2323+ "cuelang.org/go/cue/token"
2424+)
2525+2626+func lastError(p *build.Instance) *packageError {
2727+ if p == nil {
2828+ return nil
2929+ }
3030+ switch v := p.Err.(type) {
3131+ case *packageError:
3232+ return v
3333+ }
3434+ return nil
3535+}
3636+3737+func report(p *build.Instance, err *packageError) {
3838+ if err != nil {
3939+ p.ReportError(err)
4040+ }
4141+}
4242+4343+// shortPath returns an absolute or relative name for path, whatever is shorter.
4444+func shortPath(cwd, path string) string {
4545+ if cwd == "" {
4646+ return path
4747+ }
4848+ if rel, err := filepath.Rel(cwd, path); err == nil && len(rel) < len(path) {
4949+ return rel
5050+ }
5151+ return path
5252+}
5353+5454+// A packageError describes an error loading information about a package.
5555+type packageError struct {
5656+ ImportStack []string // shortest path from package named on command line to this one
5757+ Pos string // position of error
5858+ Err string // the error itself
5959+ IsImportCycle bool `json:"-"` // the error is an import cycle
6060+ Hard bool `json:"-"` // whether the error is soft or hard; soft errors are ignored in some places
6161+}
6262+6363+func (l *loader) errPkgf(importPos []token.Position, format string, args ...interface{}) *packageError {
6464+ err := &packageError{
6565+ ImportStack: l.stk.Copy(),
6666+ Err: fmt.Sprintf(format, args...),
6767+ }
6868+ err.fillPos(l.cfg.Dir, importPos)
6969+ return err
7070+}
7171+7272+func (p *packageError) fillPos(cwd string, positions []token.Position) {
7373+ if len(positions) > 0 && p.Pos == "" {
7474+ pos := positions[0]
7575+ pos.Filename = shortPath(cwd, pos.Filename)
7676+ p.Pos = pos.String()
7777+ }
7878+}
7979+8080+func (p *packageError) Error() string {
8181+ // Import cycles deserve special treatment.
8282+ if p.IsImportCycle {
8383+ return fmt.Sprintf("%s\npackage %s\n", p.Err, strings.Join(p.ImportStack, "\n\timports "))
8484+ }
8585+ if p.Pos != "" {
8686+ // Omit import stack. The full path to the file where the error
8787+ // is the most important thing.
8888+ return p.Pos + ": " + p.Err
8989+ }
9090+ if len(p.ImportStack) == 0 {
9191+ return p.Err
9292+ }
9393+ return "package " + strings.Join(p.ImportStack, "\n\timports ") + ": " + p.Err
9494+}
9595+9696+// noCUEError is the error used by Import to describe a directory
9797+// containing no buildable Go source files. (It may still contain
9898+// test files, files hidden by build tags, and so on.)
9999+type noCUEError struct {
100100+ Package *build.Instance
101101+102102+ Dir string
103103+ Ignored bool // whether any Go files were ignored due to build tags
104104+}
105105+106106+// func (e *noCUEError) Error() string {
107107+// msg := "no buildable CUE config files in " + e.Dir
108108+// if e.Ignored {
109109+// msg += " (.cue files ignored due to build tags)"
110110+// }
111111+// return msg
112112+// }
113113+114114+func (e *noCUEError) Error() string {
115115+ // Count files beginning with _ and ., which we will pretend don't exist at all.
116116+ dummy := 0
117117+ for _, name := range e.Package.IgnoredCUEFiles {
118118+ if strings.HasPrefix(name, "_") || strings.HasPrefix(name, ".") {
119119+ dummy++
120120+ }
121121+ }
122122+123123+ // path := shortPath(e.Package.Root, e.Package.Dir)
124124+ path := e.Package.DisplayPath
125125+126126+ if len(e.Package.IgnoredCUEFiles) > dummy {
127127+ // CUE files exist, but they were ignored due to build constraints.
128128+ return "build constraints exclude all CUE files in " + path
129129+ }
130130+ // if len(e.Package.TestCUEFiles) > 0 {
131131+ // // Test CUE files exist, but we're not interested in them.
132132+ // // The double-negative is unfortunate but we want e.Package.Dir
133133+ // // to appear at the end of error message.
134134+ // return "no non-test CUE files in " + e.Package.Dir
135135+ // }
136136+ return "no CUE files in " + path
137137+}
138138+139139+// multiplePackageError describes a directory containing
140140+// multiple buildable Go source files for multiple packages.
141141+type multiplePackageError struct {
142142+ Dir string // directory containing files
143143+ Packages []string // package names found
144144+ Files []string // corresponding files: Files[i] declares package Packages[i]
145145+}
146146+147147+func (e *multiplePackageError) Error() string {
148148+ // Error string limited to two entries for compatibility.
149149+ return fmt.Sprintf("found packages %s (%s) and %s (%s) in %s", e.Packages[0], e.Files[0], e.Packages[1], e.Files[1], e.Dir)
150150+}
+173
cue/load/fs.go
···11+// Copyright 2018 The CUE Authors
22+//
33+// Licensed under the Apache License, Version 2.0 (the "License");
44+// you may not use this file except in compliance with the License.
55+// You may obtain a copy of the License at
66+//
77+// http://www.apache.org/licenses/LICENSE-2.0
88+//
99+// Unless required by applicable law or agreed to in writing, software
1010+// distributed under the License is distributed on an "AS IS" BASIS,
1111+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212+// See the License for the specific language governing permissions and
1313+// limitations under the License.
1414+1515+package load
1616+1717+import (
1818+ "io"
1919+ "io/ioutil"
2020+ "os"
2121+ "path/filepath"
2222+ "strings"
2323+)
2424+2525+// TODO: remove this file if we know we don't need it.
2626+2727+// A fileSystem specifies the supporting context for a build.
2828+type fileSystem struct {
2929+ // By default, Import uses the operating system's file system calls
3030+ // to read directories and files. To read from other sources,
3131+ // callers can set the following functions. They all have default
3232+ // behaviors that use the local file system, so clients need only set
3333+ // the functions whose behaviors they wish to change.
3434+3535+ // JoinPath joins the sequence of path fragments into a single path.
3636+ // If JoinPath is nil, Import uses filepath.Join.
3737+ JoinPath func(elem ...string) string
3838+3939+ // SplitPathList splits the path list into a slice of individual paths.
4040+ // If SplitPathList is nil, Import uses filepath.SplitList.
4141+ SplitPathList func(list string) []string
4242+4343+ // IsAbsPath reports whether path is an absolute path.
4444+ // If IsAbsPath is nil, Import uses filepath.IsAbs.
4545+ IsAbsPath func(path string) bool
4646+4747+ // IsDir reports whether the path names a directory.
4848+ // If IsDir is nil, Import calls os.Stat and uses the result's IsDir method.
4949+ IsDir func(path string) bool
5050+5151+ // HasSubdir reports whether dir is a subdirectory of
5252+ // (perhaps multiple levels below) root.
5353+ // If so, HasSubdir sets rel to a slash-separated path that
5454+ // can be joined to root to produce a path equivalent to dir.
5555+ // If HasSubdir is nil, Import uses an implementation built on
5656+ // filepath.EvalSymlinks.
5757+ HasSubdir func(root, dir string) (rel string, ok bool)
5858+5959+ // ReadDir returns a slice of os.FileInfo, sorted by Name,
6060+ // describing the content of the named directory.
6161+ // If ReadDir is nil, Import uses ioutil.ReadDir.
6262+ ReadDir func(dir string) ([]os.FileInfo, error)
6363+6464+ // OpenFile opens a file (not a directory) for reading.
6565+ // If OpenFile is nil, Import uses os.Open.
6666+ OpenFile func(path string) (io.ReadCloser, error)
6767+}
6868+6969+// JoinPath calls ctxt.JoinPath (if not nil) or else filepath.Join.
7070+func (ctxt *fileSystem) joinPath(elem ...string) string {
7171+ if f := ctxt.JoinPath; f != nil {
7272+ return f(elem...)
7373+ }
7474+ return filepath.Join(elem...)
7575+}
7676+7777+// splitPathList calls ctxt.SplitPathList (if not nil) or else filepath.SplitList.
7878+func (ctxt *fileSystem) splitPathList(s string) []string {
7979+ if f := ctxt.SplitPathList; f != nil {
8080+ return f(s)
8181+ }
8282+ return filepath.SplitList(s)
8383+}
8484+8585+// isAbsPath calls ctxt.IsAbsPath (if not nil) or else filepath.IsAbs.
8686+func (ctxt *fileSystem) isAbsPath(path string) bool {
8787+ if f := ctxt.IsAbsPath; f != nil {
8888+ return f(path)
8989+ }
9090+ return filepath.IsAbs(path)
9191+}
9292+9393+// isDir calls ctxt.IsDir (if not nil) or else uses os.Stat.
9494+func (ctxt *fileSystem) isDir(path string) bool {
9595+ if f := ctxt.IsDir; f != nil {
9696+ return f(path)
9797+ }
9898+ fi, err := os.Stat(path)
9999+ return err == nil && fi.IsDir()
100100+}
101101+102102+// hasSubdir calls ctxt.HasSubdir (if not nil) or else uses
103103+// the local file system to answer the question.
104104+func (ctxt *fileSystem) hasSubdir(root, dir string) (rel string, ok bool) {
105105+ if f := ctxt.HasSubdir; f != nil {
106106+ return f(root, dir)
107107+ }
108108+109109+ // Try using paths we received.
110110+ if rel, ok = hasSubdir(root, dir); ok {
111111+ return
112112+ }
113113+114114+ // Try expanding symlinks and comparing
115115+ // expanded against unexpanded and
116116+ // expanded against expanded.
117117+ rootSym, _ := filepath.EvalSymlinks(root)
118118+ dirSym, _ := filepath.EvalSymlinks(dir)
119119+120120+ if rel, ok = hasSubdir(rootSym, dir); ok {
121121+ return
122122+ }
123123+ if rel, ok = hasSubdir(root, dirSym); ok {
124124+ return
125125+ }
126126+ return hasSubdir(rootSym, dirSym)
127127+}
128128+129129+func hasSubdir(root, dir string) (rel string, ok bool) {
130130+ const sep = string(filepath.Separator)
131131+ root = filepath.Clean(root)
132132+ if !strings.HasSuffix(root, sep) {
133133+ root += sep
134134+ }
135135+ dir = filepath.Clean(dir)
136136+ if !strings.HasPrefix(dir, root) {
137137+ return "", false
138138+ }
139139+ return filepath.ToSlash(dir[len(root):]), true
140140+}
141141+142142+// ReadDir calls ctxt.ReadDir (if not nil) or else ioutil.ReadDir.
143143+func (ctxt *fileSystem) readDir(path string) ([]os.FileInfo, error) {
144144+ if f := ctxt.ReadDir; f != nil {
145145+ return f(path)
146146+ }
147147+ return ioutil.ReadDir(path)
148148+}
149149+150150+// openFile calls ctxt.OpenFile (if not nil) or else os.Open.
151151+func (ctxt *fileSystem) openFile(path string) (io.ReadCloser, error) {
152152+ if fn := ctxt.OpenFile; fn != nil {
153153+ return fn(path)
154154+ }
155155+156156+ f, err := os.Open(path)
157157+ if err != nil {
158158+ return nil, err // nil interface
159159+ }
160160+ return f, nil
161161+}
162162+163163+// isFile determines whether path is a file by trying to open it.
164164+// It reuses openFile instead of adding another function to the
165165+// list in Context.
166166+func (ctxt *fileSystem) isFile(path string) bool {
167167+ f, err := ctxt.openFile(path)
168168+ if err != nil {
169169+ return false
170170+ }
171171+ f.Close()
172172+ return true
173173+}
+570
cue/load/import.go
···11+// Copyright 2018 The CUE Authors
22+//
33+// Licensed under the Apache License, Version 2.0 (the "License");
44+// you may not use this file except in compliance with the License.
55+// You may obtain a copy of the License at
66+//
77+// http://www.apache.org/licenses/LICENSE-2.0
88+//
99+// Unless required by applicable law or agreed to in writing, software
1010+// distributed under the License is distributed on an "AS IS" BASIS,
1111+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212+// See the License for the specific language governing permissions and
1313+// limitations under the License.
1414+1515+package load
1616+1717+import (
1818+ "bytes"
1919+ "fmt"
2020+ "log"
2121+ pathpkg "path"
2222+ "path/filepath"
2323+ "sort"
2424+ "strconv"
2525+ "strings"
2626+ "unicode"
2727+ "unicode/utf8"
2828+2929+ "cuelang.org/go/cue/ast"
3030+ build "cuelang.org/go/cue/build"
3131+ "cuelang.org/go/cue/encoding"
3232+ "cuelang.org/go/cue/parser"
3333+ "cuelang.org/go/cue/token"
3434+)
3535+3636+// An importMode controls the behavior of the Import method.
3737+type importMode uint
3838+3939+const (
4040+ // If findOnly is set, Import stops after locating the directory
4141+ // that should contain the sources for a package. It does not
4242+ // read any files in the directory.
4343+ findOnly importMode = 1 << iota
4444+4545+ // If importComment is set, parse import comments on package statements.
4646+ // Import returns an error if it finds a comment it cannot understand
4747+ // or finds conflicting comments in multiple source files.
4848+ // See golang.org/s/go14customimport for more information.
4949+ importComment
5050+5151+ allowAnonymous
5252+)
5353+5454+// importPkg returns details about the CUE package named by the import path,
5555+// interpreting local import paths relative to the srcDir directory.
5656+// If the path is a local import path naming a package that can be imported
5757+// using a standard import path, the returned package will set p.ImportPath
5858+// to that path.
5959+//
6060+// In the directory and ancestor directories up to including one with a
6161+// cue.mod file, all .cue files are considered part of the package except for:
6262+//
6363+// - files starting with _ or . (likely editor temporary files)
6464+// - files with build constraints not satisfied by the context
6565+//
6666+// If an error occurs, importPkg sets the error in the returned instance,
6767+// which then may contain partial information.
6868+//
6969+func (l *loader) importPkg(path, srcDir string) *build.Instance {
7070+ l.stk.Push(path)
7171+ defer l.stk.Pop()
7272+7373+ cfg := l.cfg
7474+ ctxt := &cfg.fileSystem
7575+7676+ parentPath := path
7777+ if isLocalImport(path) {
7878+ parentPath = filepath.Join(srcDir, path)
7979+ }
8080+ p := cfg.Context.NewInstance(path, l.loadFunc(parentPath))
8181+ p.DisplayPath = path
8282+8383+ isLocal := isLocalImport(path)
8484+ var modDir string
8585+ // var modErr error
8686+ if !isLocal {
8787+ // TODO(mpvl): support module lookup
8888+ }
8989+9090+ p.Local = isLocal
9191+9292+ if err := updateDirs(cfg, p, path, srcDir, 0); err != nil {
9393+ p.ReportError(err)
9494+ return p
9595+ }
9696+9797+ if modDir == "" && path != cleanImport(path) {
9898+ report(p, l.errPkgf(nil,
9999+ "non-canonical import path: %q should be %q", path, pathpkg.Clean(path)))
100100+ p.Incomplete = true
101101+ return p
102102+ }
103103+104104+ fp := newFileProcessor(cfg, p)
105105+106106+ root := p.Dir
107107+108108+ for dir := p.Dir; ctxt.isDir(dir); {
109109+ files, err := ctxt.readDir(dir)
110110+ if err != nil {
111111+ p.ReportError(err)
112112+ return p
113113+ }
114114+ rootFound := false
115115+ for _, f := range files {
116116+ if f.IsDir() {
117117+ continue
118118+ }
119119+ if fp.add(dir, f.Name(), importComment) {
120120+ root = dir
121121+ }
122122+ if f.Name() == "cue.mod" {
123123+ root = dir
124124+ rootFound = true
125125+ }
126126+ }
127127+128128+ if rootFound || dir == p.Root || fp.pkg.PkgName == "" {
129129+ break
130130+ }
131131+132132+ // From now on we just ignore files that do not belong to the same
133133+ // package.
134134+ fp.ignoreOther = true
135135+136136+ parent, _ := filepath.Split(filepath.Clean(dir))
137137+ if parent == dir {
138138+ break
139139+ }
140140+ dir = parent
141141+ }
142142+143143+ rewriteFiles(p, root, false)
144144+ if err := fp.finalize(); err != nil {
145145+ p.ReportError(err)
146146+ return p
147147+ }
148148+149149+ for _, f := range p.CUEFiles {
150150+ if !filepath.IsAbs(f) {
151151+ f = filepath.Join(root, f)
152152+ }
153153+ p.AddFile(f, nil)
154154+ }
155155+ p.Complete()
156156+ return p
157157+}
158158+159159+// loadFunc creates a LoadFunc that can be used to create new build.Instances.
160160+func (l *loader) loadFunc(parentPath string) build.LoadFunc {
161161+162162+ return func(path string) *build.Instance {
163163+ cfg := l.cfg
164164+165165+ // TODO: HACK: for now we don't handle any imports that are not
166166+ // relative paths.
167167+ if !isLocalImport(path) {
168168+ return nil
169169+ }
170170+171171+ if strings.Contains(path, "@") {
172172+ i := cfg.newInstance(path)
173173+ report(i, l.errPkgf(nil,
174174+ "can only use path@version syntax with 'cue get'"))
175175+ return i
176176+ }
177177+178178+ return l.importPkg(path, parentPath)
179179+ }
180180+}
181181+182182+func updateDirs(c *Config, p *build.Instance, path, srcDir string, mode importMode) error {
183183+ ctxt := &c.fileSystem
184184+ // path := p.ImportPath
185185+ if path == "" {
186186+ return fmt.Errorf("import %q: invalid import path", path)
187187+ }
188188+189189+ if isLocalImport(path) {
190190+ if srcDir == "" {
191191+ return fmt.Errorf("import %q: import relative to unknown directory", path)
192192+ }
193193+ if !ctxt.isAbsPath(path) {
194194+ p.Dir = ctxt.joinPath(srcDir, path)
195195+ }
196196+ return nil
197197+ }
198198+199199+ if strings.HasPrefix(path, "/") {
200200+ return fmt.Errorf("import %q: cannot import absolute path", path)
201201+ }
202202+203203+ // TODO: Lookup the import in dir "pkg" at the module root.
204204+205205+ // package was not found
206206+ return fmt.Errorf("cannot find package %q", path)
207207+}
208208+209209+func normPrefix(root, path string, isLocal bool) string {
210210+ root = filepath.Clean(root)
211211+ prefix := ""
212212+ if isLocal {
213213+ prefix = "." + string(filepath.Separator)
214214+ }
215215+ if !strings.HasSuffix(root, string(filepath.Separator)) &&
216216+ strings.HasPrefix(path, root) {
217217+ path = prefix + path[len(root)+1:]
218218+ }
219219+ return path
220220+}
221221+222222+func rewriteFiles(p *build.Instance, root string, isLocal bool) {
223223+ p.Root = root
224224+ for i, path := range p.CUEFiles {
225225+ p.CUEFiles[i] = normPrefix(root, path, isLocal)
226226+ sortParentsFirst(p.CUEFiles)
227227+ }
228228+ for i, path := range p.TestCUEFiles {
229229+ p.TestCUEFiles[i] = normPrefix(root, path, isLocal)
230230+ sortParentsFirst(p.TestCUEFiles)
231231+ }
232232+ for i, path := range p.ToolCUEFiles {
233233+ p.ToolCUEFiles[i] = normPrefix(root, path, isLocal)
234234+ sortParentsFirst(p.ToolCUEFiles)
235235+ }
236236+ for i, path := range p.IgnoredCUEFiles {
237237+ if strings.HasPrefix(path, root) {
238238+ p.IgnoredCUEFiles[i] = normPrefix(root, path, isLocal)
239239+ }
240240+ }
241241+ for i, path := range p.InvalidCUEFiles {
242242+ p.InvalidCUEFiles[i] = normPrefix(root, path, isLocal)
243243+ sortParentsFirst(p.InvalidCUEFiles)
244244+ }
245245+}
246246+247247+func sortParentsFirst(s []string) {
248248+ sort.Slice(s, func(i, j int) bool {
249249+ return len(filepath.Dir(s[i])) < len(filepath.Dir(s[j]))
250250+ })
251251+}
252252+253253+type fileProcessor struct {
254254+ firstFile string
255255+ firstCommentFile string
256256+ imported map[string][]token.Position
257257+ allTags map[string]bool
258258+ allFiles bool
259259+ ignoreOther bool // ignore files from other packages
260260+261261+ c *Config
262262+ pkg *build.Instance
263263+264264+ err error
265265+}
266266+267267+func newFileProcessor(c *Config, p *build.Instance) *fileProcessor {
268268+ return &fileProcessor{
269269+ imported: make(map[string][]token.Position),
270270+ allTags: make(map[string]bool),
271271+ c: c,
272272+ pkg: p,
273273+ }
274274+}
275275+276276+func (fp *fileProcessor) finalize() error {
277277+ p := fp.pkg
278278+ if fp.err != nil {
279279+ return fp.err
280280+ }
281281+ if len(p.CUEFiles) == 0 && !fp.c.DataFiles {
282282+ return &noCUEError{Package: p, Dir: p.Dir, Ignored: len(p.IgnoredCUEFiles) > 0}
283283+ }
284284+285285+ for tag := range fp.allTags {
286286+ p.AllTags = append(p.AllTags, tag)
287287+ }
288288+ sort.Strings(p.AllTags)
289289+290290+ p.ImportPaths, _ = cleanImports(fp.imported)
291291+292292+ return nil
293293+}
294294+295295+func (fp *fileProcessor) add(root, path string, mode importMode) (added bool) {
296296+ fullPath := path
297297+ if !filepath.IsAbs(path) {
298298+ fullPath = filepath.Join(root, path)
299299+ }
300300+ name := filepath.Base(fullPath)
301301+ dir := filepath.Dir(fullPath)
302302+303303+ fset := token.NewFileSet()
304304+ ext := nameExt(name)
305305+ p := fp.pkg
306306+307307+ badFile := func(err error) bool {
308308+ if fp.err == nil {
309309+ fp.err = err
310310+ }
311311+ p.InvalidCUEFiles = append(p.InvalidCUEFiles, fullPath)
312312+ return true
313313+ }
314314+315315+ match, data, filename, err := matchFile(fp.c, dir, name, true, fp.allFiles, fp.allTags)
316316+ if err != nil {
317317+ return badFile(err)
318318+ }
319319+ if !match {
320320+ if ext == cueSuffix {
321321+ p.IgnoredCUEFiles = append(p.IgnoredCUEFiles, fullPath)
322322+ } else if encoding.MapExtension(ext) != nil {
323323+ p.DataFiles = append(p.DataFiles, fullPath)
324324+ }
325325+ return false // don't mark as added
326326+ }
327327+328328+ pf, err := parser.ParseFile(fset, filename, data, parser.ImportsOnly, parser.ParseComments)
329329+ if err != nil {
330330+ return badFile(err)
331331+ }
332332+333333+ pkg := ""
334334+ if pf.Name != nil {
335335+ pkg = pf.Name.Name
336336+ }
337337+ if pkg == "" && mode&allowAnonymous == 0 {
338338+ p.IgnoredCUEFiles = append(p.IgnoredCUEFiles, fullPath)
339339+ return false // don't mark as added
340340+ }
341341+342342+ if fp.c.Package != "" {
343343+ if pkg != fp.c.Package {
344344+ if fp.ignoreOther {
345345+ p.IgnoredCUEFiles = append(p.IgnoredCUEFiles, fullPath)
346346+ return false
347347+ }
348348+ // TODO: package does not conform with requested.
349349+ return badFile(fmt.Errorf("%s: found package %q; want %q", filename, pkg, fp.c.Package))
350350+ }
351351+ } else if fp.firstFile == "" {
352352+ p.PkgName = pkg
353353+ fp.firstFile = name
354354+ } else if pkg != p.PkgName {
355355+ if fp.ignoreOther {
356356+ p.IgnoredCUEFiles = append(p.IgnoredCUEFiles, fullPath)
357357+ return false
358358+ }
359359+ return badFile(&multiplePackageError{
360360+ Dir: p.Dir,
361361+ Packages: []string{p.PkgName, pkg},
362362+ Files: []string{fp.firstFile, name},
363363+ })
364364+ }
365365+366366+ isTest := strings.HasSuffix(name, "_test"+cueSuffix)
367367+ isTool := strings.HasSuffix(name, "_tool"+cueSuffix)
368368+369369+ if mode&importComment != 0 {
370370+ qcom, line := findimportComment(data)
371371+ if line != 0 {
372372+ com, err := strconv.Unquote(qcom)
373373+ if err != nil {
374374+ badFile(fmt.Errorf("%s:%d: cannot parse import comment", filename, line))
375375+ } else if p.ImportComment == "" {
376376+ p.ImportComment = com
377377+ fp.firstCommentFile = name
378378+ } else if p.ImportComment != com {
379379+ badFile(fmt.Errorf("found import comments %q (%s) and %q (%s) in %s", p.ImportComment, fp.firstCommentFile, com, name, p.Dir))
380380+ }
381381+ }
382382+ }
383383+384384+ for _, decl := range pf.Decls {
385385+ d, ok := decl.(*ast.ImportDecl)
386386+ if !ok {
387387+ continue
388388+ }
389389+ for _, spec := range d.Specs {
390390+ quoted := spec.Path.Value
391391+ path, err := strconv.Unquote(quoted)
392392+ if err != nil {
393393+ log.Panicf("%s: parser returned invalid quoted string: <%s>", filename, quoted)
394394+ }
395395+ if !isTest || fp.c.Tests {
396396+ fp.imported[path] = append(fp.imported[path], fset.Position(spec.Pos()))
397397+ }
398398+ }
399399+ }
400400+ switch {
401401+ case isTest:
402402+ p.TestCUEFiles = append(p.TestCUEFiles, fullPath)
403403+ case isTool:
404404+ p.ToolCUEFiles = append(p.TestCUEFiles, fullPath)
405405+ default:
406406+ p.CUEFiles = append(p.CUEFiles, fullPath)
407407+ }
408408+ return true
409409+}
410410+411411+func nameExt(name string) string {
412412+ i := strings.LastIndex(name, ".")
413413+ if i < 0 {
414414+ return ""
415415+ }
416416+ return name[i:]
417417+}
418418+419419+// hasCUEFiles reports whether dir contains any files with names ending in .go.
420420+// For a vendor check we must exclude directories that contain no .go files.
421421+// Otherwise it is not possible to vendor just a/b/c and still import the
422422+// non-vendored a/b. See golang.org/issue/13832.
423423+func hasCUEFiles(ctxt *fileSystem, dir string) bool {
424424+ ents, _ := ctxt.readDir(dir)
425425+ for _, ent := range ents {
426426+ if !ent.IsDir() && strings.HasSuffix(ent.Name(), cueSuffix) {
427427+ return true
428428+ }
429429+ }
430430+ return false
431431+}
432432+433433+func findimportComment(data []byte) (s string, line int) {
434434+ // expect keyword package
435435+ word, data := parseWord(data)
436436+ if string(word) != "package" {
437437+ return "", 0
438438+ }
439439+440440+ // expect package name
441441+ _, data = parseWord(data)
442442+443443+ // now ready for import comment, a // or /* */ comment
444444+ // beginning and ending on the current line.
445445+ for len(data) > 0 && (data[0] == ' ' || data[0] == '\t' || data[0] == '\r') {
446446+ data = data[1:]
447447+ }
448448+449449+ var comment []byte
450450+ switch {
451451+ case bytes.HasPrefix(data, slashSlash):
452452+ i := bytes.Index(data, newline)
453453+ if i < 0 {
454454+ i = len(data)
455455+ }
456456+ comment = data[2:i]
457457+ case bytes.HasPrefix(data, slashStar):
458458+ data = data[2:]
459459+ i := bytes.Index(data, starSlash)
460460+ if i < 0 {
461461+ // malformed comment
462462+ return "", 0
463463+ }
464464+ comment = data[:i]
465465+ if bytes.Contains(comment, newline) {
466466+ return "", 0
467467+ }
468468+ }
469469+ comment = bytes.TrimSpace(comment)
470470+471471+ // split comment into `import`, `"pkg"`
472472+ word, arg := parseWord(comment)
473473+ if string(word) != "import" {
474474+ return "", 0
475475+ }
476476+477477+ line = 1 + bytes.Count(data[:cap(data)-cap(arg)], newline)
478478+ return strings.TrimSpace(string(arg)), line
479479+}
480480+481481+var (
482482+ slashSlash = []byte("//")
483483+ slashStar = []byte("/*")
484484+ starSlash = []byte("*/")
485485+ newline = []byte("\n")
486486+)
487487+488488+// skipSpaceOrComment returns data with any leading spaces or comments removed.
489489+func skipSpaceOrComment(data []byte) []byte {
490490+ for len(data) > 0 {
491491+ switch data[0] {
492492+ case ' ', '\t', '\r', '\n':
493493+ data = data[1:]
494494+ continue
495495+ case '/':
496496+ if bytes.HasPrefix(data, slashSlash) {
497497+ i := bytes.Index(data, newline)
498498+ if i < 0 {
499499+ return nil
500500+ }
501501+ data = data[i+1:]
502502+ continue
503503+ }
504504+ if bytes.HasPrefix(data, slashStar) {
505505+ data = data[2:]
506506+ i := bytes.Index(data, starSlash)
507507+ if i < 0 {
508508+ return nil
509509+ }
510510+ data = data[i+2:]
511511+ continue
512512+ }
513513+ }
514514+ break
515515+ }
516516+ return data
517517+}
518518+519519+// parseWord skips any leading spaces or comments in data
520520+// and then parses the beginning of data as an identifier or keyword,
521521+// returning that word and what remains after the word.
522522+func parseWord(data []byte) (word, rest []byte) {
523523+ data = skipSpaceOrComment(data)
524524+525525+ // Parse past leading word characters.
526526+ rest = data
527527+ for {
528528+ r, size := utf8.DecodeRune(rest)
529529+ if unicode.IsLetter(r) || '0' <= r && r <= '9' || r == '_' {
530530+ rest = rest[size:]
531531+ continue
532532+ }
533533+ break
534534+ }
535535+536536+ word = data[:len(data)-len(rest)]
537537+ if len(word) == 0 {
538538+ return nil, nil
539539+ }
540540+541541+ return word, rest
542542+}
543543+544544+func cleanImports(m map[string][]token.Position) ([]string, map[string][]token.Position) {
545545+ all := make([]string, 0, len(m))
546546+ for path := range m {
547547+ all = append(all, path)
548548+ }
549549+ sort.Strings(all)
550550+ return all, m
551551+}
552552+553553+// // Import is shorthand for Default.Import.
554554+// func Import(path, srcDir string, mode ImportMode) (*Package, error) {
555555+// return Default.Import(path, srcDir, mode)
556556+// }
557557+558558+// // ImportDir is shorthand for Default.ImportDir.
559559+// func ImportDir(dir string, mode ImportMode) (*Package, error) {
560560+// return Default.ImportDir(dir, mode)
561561+// }
562562+563563+var slashslash = []byte("//")
564564+565565+// isLocalImport reports whether the import path is
566566+// a local import path, like ".", "..", "./foo", or "../foo".
567567+func isLocalImport(path string) bool {
568568+ return path == "." || path == ".." ||
569569+ strings.HasPrefix(path, "./") || strings.HasPrefix(path, "../")
570570+}
+121
cue/load/import_test.go
···11+// Copyright 2018 The CUE Authors
22+//
33+// Licensed under the Apache License, Version 2.0 (the "License");
44+// you may not use this file except in compliance with the License.
55+// You may obtain a copy of the License at
66+//
77+// http://www.apache.org/licenses/LICENSE-2.0
88+//
99+// Unless required by applicable law or agreed to in writing, software
1010+// distributed under the License is distributed on an "AS IS" BASIS,
1111+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212+// See the License for the specific language governing permissions and
1313+// limitations under the License.
1414+1515+package load
1616+1717+import (
1818+ "os"
1919+ "path/filepath"
2020+ "reflect"
2121+ "testing"
2222+2323+ build "cuelang.org/go/cue/build"
2424+)
2525+2626+const testdata = "./testdata/"
2727+2828+func getInst(pkg, cwd string) (*build.Instance, error) {
2929+ c, _ := (&Config{}).complete()
3030+ l := loader{cfg: c}
3131+ p := l.importPkg(pkg, cwd)
3232+ return p, p.Err
3333+}
3434+3535+func TestDotSlashImport(t *testing.T) {
3636+ c, _ := (&Config{}).complete()
3737+ l := loader{cfg: c}
3838+ p := l.importPkg(".", testdata+"other")
3939+ err := p.Err
4040+ if err != nil {
4141+ t.Fatal(err)
4242+ }
4343+ if len(p.ImportPaths) != 1 || p.ImportPaths[0] != "./file" {
4444+ t.Fatalf("testdata/other: Imports=%v, want [./file]", p.ImportPaths)
4545+ }
4646+4747+ p1, err := getInst("./file", testdata+"other")
4848+ if err != nil {
4949+ t.Fatal(err)
5050+ }
5151+ if p1.PkgName != "file" {
5252+ t.Fatalf("./file: Name=%q, want %q", p1.PkgName, "file")
5353+ }
5454+ dir := filepath.Clean(testdata + "other/file") // Clean to use \ on Windows
5555+ if p1.Dir != dir {
5656+ t.Fatalf("./file: Dir=%q, want %q", p1.PkgName, dir)
5757+ }
5858+}
5959+6060+func TestEmptyImport(t *testing.T) {
6161+ p, err := getInst("", "")
6262+ if err == nil {
6363+ t.Fatal(`Import("") returned nil error.`)
6464+ }
6565+ if p == nil {
6666+ t.Fatal(`Import("") returned nil package.`)
6767+ }
6868+ if p.DisplayPath != "" {
6969+ t.Fatalf("DisplayPath=%q, want %q.", p.DisplayPath, "")
7070+ }
7171+}
7272+7373+func TestEmptyFolderImport(t *testing.T) {
7474+ _, err := getInst(".", testdata+"empty")
7575+ if _, ok := err.(*noCUEError); !ok {
7676+ t.Fatal(`Import("testdata/empty") did not return NoCUEError.`)
7777+ }
7878+}
7979+8080+func TestIgnoredCUEFilesImport(t *testing.T) {
8181+ _, err := getInst(".", testdata+"ignored")
8282+ e, ok := err.(*noCUEError)
8383+ if !ok {
8484+ t.Fatal(`Import("testdata/ignored") did not return NoCUEError.`)
8585+ }
8686+ if !e.Ignored {
8787+ t.Fatal(`Import("testdata/ignored") should have ignored CUE files.`)
8888+ }
8989+}
9090+9191+func TestMultiplePackageImport(t *testing.T) {
9292+ _, err := getInst(".", testdata+"multi")
9393+ mpe, ok := err.(*multiplePackageError)
9494+ if !ok {
9595+ t.Fatal(`Import("testdata/multi") did not return MultiplePackageError.`)
9696+ }
9797+ want := &multiplePackageError{
9898+ Dir: filepath.FromSlash("testdata/multi"),
9999+ Packages: []string{"main", "test_package"},
100100+ Files: []string{"file.cue", "file_appengine.cue"},
101101+ }
102102+ if !reflect.DeepEqual(mpe, want) {
103103+ t.Errorf("got %#v; want %#v", mpe, want)
104104+ }
105105+}
106106+107107+func TestLocalDirectory(t *testing.T) {
108108+ cwd, err := os.Getwd()
109109+ if err != nil {
110110+ t.Fatal(err)
111111+ }
112112+113113+ p, err := getInst(".", cwd)
114114+ if err != nil {
115115+ t.Fatal(err)
116116+ }
117117+118118+ if p.DisplayPath != "." {
119119+ t.Fatalf("DisplayPath=%q, want %q", p.DisplayPath, ".")
120120+ }
121121+}
+261
cue/load/loader.go
···11+// Copyright 2018 The CUE Authors
22+//
33+// Licensed under the Apache License, Version 2.0 (the "License");
44+// you may not use this file except in compliance with the License.
55+// You may obtain a copy of the License at
66+//
77+// http://www.apache.org/licenses/LICENSE-2.0
88+//
99+// Unless required by applicable law or agreed to in writing, software
1010+// distributed under the License is distributed on an "AS IS" BASIS,
1111+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212+// See the License for the specific language governing permissions and
1313+// limitations under the License.
1414+1515+package load
1616+1717+// Files in package are to a large extent based on Go files from the following
1818+// Go packages:
1919+// - cmd/go/internal/load
2020+// - go/build
2121+2222+import (
2323+ "errors"
2424+ "fmt"
2525+ "os"
2626+ pathpkg "path"
2727+ "path/filepath"
2828+ "strings"
2929+ "unicode"
3030+3131+ build "cuelang.org/go/cue/build"
3232+ "cuelang.org/go/cue/token"
3333+)
3434+3535+// Instances returns the instances named by the command line arguments 'args'.
3636+// If errors occur trying to load an instance it is returned with Incomplete
3737+// set. Errors directly related to loading the instance are recorded in this
3838+// instance, but errors that occur loading dependencies are recorded in these
3939+// dependencies.
4040+func Instances(args []string, c *Config) []*build.Instance {
4141+ if c == nil {
4242+ c = &Config{}
4343+ }
4444+4545+ c, err := c.complete()
4646+ if err != nil {
4747+ return nil
4848+ }
4949+5050+ l := c.loader
5151+5252+ if len(args) > 0 && strings.HasSuffix(args[0], cueSuffix) {
5353+ return []*build.Instance{l.cueFilesPackage(args)}
5454+ }
5555+5656+ dummy := c.newInstance("user")
5757+ dummy.Local = true
5858+5959+ a := []*build.Instance{}
6060+ for _, m := range l.importPaths(args) {
6161+ if m.Err != nil {
6262+ inst := c.newErrInstance(m, "", m.Err)
6363+ a = append(a, inst)
6464+ continue
6565+ }
6666+ a = append(a, m.Pkgs...)
6767+ }
6868+ return a
6969+}
7070+7171+// Mode flags for loadImport and download (in get.go).
7272+const (
7373+ // resolveImport means that loadImport should do import path expansion.
7474+ // That is, resolveImport means that the import path came from
7575+ // a source file and has not been expanded yet to account for
7676+ // vendoring or possible module adjustment.
7777+ // Every import path should be loaded initially with resolveImport,
7878+ // and then the expanded version (for example with the /vendor/ in it)
7979+ // gets recorded as the canonical import path. At that point, future loads
8080+ // of that package must not pass resolveImport, because
8181+ // disallowVendor will reject direct use of paths containing /vendor/.
8282+ resolveImport = 1 << iota
8383+8484+ // resolveModule is for download (part of "go get") and indicates
8585+ // that the module adjustment should be done, but not vendor adjustment.
8686+ resolveModule
8787+8888+ // getTestDeps is for download (part of "go get") and indicates
8989+ // that test dependencies should be fetched too.
9090+ getTestDeps
9191+)
9292+9393+func firstPos(p []token.Position) token.Position {
9494+ if len(p) == 0 {
9595+ return token.Position{}
9696+ }
9797+ return p[0]
9898+}
9999+100100+type loader struct {
101101+ cfg *Config
102102+ stk importStack
103103+}
104104+105105+func (l *loader) abs(filename string) string {
106106+ if !isLocalImport(filename) {
107107+ return filename
108108+ }
109109+ return filepath.Join(l.cfg.Dir, filename)
110110+}
111111+112112+// cueFilesPackage creates a package for building a collection of CUE files
113113+// (typically named on the command line).
114114+func (l *loader) cueFilesPackage(files []string) *build.Instance {
115115+ cfg := l.cfg
116116+ // ModInit() // TODO: support modules
117117+ for _, f := range files {
118118+ if !strings.HasSuffix(f, ".cue") {
119119+ return cfg.newErrInstance(nil, f,
120120+ errors.New("named files must be .cue files"))
121121+ }
122122+ }
123123+124124+ pkg := l.cfg.Context.NewInstance(cfg.Dir, l.loadFunc(cfg.Dir))
125125+ // TODO: add fiels directly?
126126+ fp := newFileProcessor(cfg, pkg)
127127+ for _, file := range files {
128128+ path := file
129129+ if !filepath.IsAbs(file) {
130130+ path = filepath.Join(cfg.Dir, file)
131131+ }
132132+ fi, err := os.Stat(path)
133133+ if err != nil {
134134+ return cfg.newErrInstance(nil, path, err)
135135+ }
136136+ if fi.IsDir() {
137137+ return cfg.newErrInstance(nil, path,
138138+ fmt.Errorf("%s is a directory, should be a CUE file", file))
139139+ }
140140+ fp.add(cfg.Dir, file, allowAnonymous)
141141+ }
142142+143143+ // TODO: ModImportFromFiles(files)
144144+ _, err := filepath.Abs(cfg.Dir)
145145+ if err != nil {
146146+ return cfg.newErrInstance(nil, cfg.Dir, err)
147147+ }
148148+ pkg.Dir = cfg.Dir
149149+ rewriteFiles(pkg, pkg.Dir, true)
150150+ err = fp.finalize() // ImportDir(&ctxt, dir, 0)
151151+ // TODO: Support module importing.
152152+ // if ModDirImportPath != nil {
153153+ // // Use the effective import path of the directory
154154+ // // for deciding visibility during pkg.load.
155155+ // bp.ImportPath = ModDirImportPath(dir)
156156+ // }
157157+158158+ for _, f := range pkg.CUEFiles {
159159+ if !filepath.IsAbs(f) {
160160+ f = filepath.Join(cfg.Dir, f)
161161+ }
162162+ pkg.AddFile(f, nil)
163163+ }
164164+165165+ pkg.Local = true
166166+ l.stk.Push("user")
167167+ pkg.Complete()
168168+ l.stk.Pop()
169169+ pkg.Local = true
170170+ //pkg.LocalPrefix = dirToImportPath(dir)
171171+ pkg.DisplayPath = "command-line-arguments"
172172+ pkg.Match = files
173173+174174+ return pkg
175175+}
176176+177177+func cleanImport(path string) string {
178178+ orig := path
179179+ path = pathpkg.Clean(path)
180180+ if strings.HasPrefix(orig, "./") && path != ".." && !strings.HasPrefix(path, "../") {
181181+ path = "./" + path
182182+ }
183183+ return path
184184+}
185185+186186+// An importStack is a stack of import paths, possibly with the suffix " (test)" appended.
187187+// The import path of a test package is the import path of the corresponding
188188+// non-test package with the suffix "_test" added.
189189+type importStack []string
190190+191191+func (s *importStack) Push(p string) {
192192+ *s = append(*s, p)
193193+}
194194+195195+func (s *importStack) Pop() {
196196+ *s = (*s)[0 : len(*s)-1]
197197+}
198198+199199+func (s *importStack) Copy() []string {
200200+ return append([]string{}, *s...)
201201+}
202202+203203+// shorterThan reports whether sp is shorter than t.
204204+// We use this to record the shortest import sequences
205205+// that leads to a particular package.
206206+func (sp *importStack) shorterThan(t []string) bool {
207207+ s := *sp
208208+ if len(s) != len(t) {
209209+ return len(s) < len(t)
210210+ }
211211+ // If they are the same length, settle ties using string ordering.
212212+ for i := range s {
213213+ if s[i] != t[i] {
214214+ return s[i] < t[i]
215215+ }
216216+ }
217217+ return false // they are equal
218218+}
219219+220220+// reusePackage reuses package p to satisfy the import at the top
221221+// of the import stack stk. If this use causes an import loop,
222222+// reusePackage updates p's error information to record the loop.
223223+func (l *loader) reusePackage(p *build.Instance) *build.Instance {
224224+ // We use p.Internal.Imports==nil to detect a package that
225225+ // is in the midst of its own loadPackage call
226226+ // (all the recursion below happens before p.Internal.Imports gets set).
227227+ if p.ImportPaths == nil {
228228+ if err := lastError(p); err == nil {
229229+ err = l.errPkgf(nil, "import cycle not allowed")
230230+ err.IsImportCycle = true
231231+ report(p, err)
232232+ }
233233+ p.Incomplete = true
234234+ }
235235+ // Don't rewrite the import stack in the error if we have an import cycle.
236236+ // If we do, we'll lose the path that describes the cycle.
237237+ if err := lastError(p); err != nil && !err.IsImportCycle && l.stk.shorterThan(err.ImportStack) {
238238+ err.ImportStack = l.stk.Copy()
239239+ }
240240+ return p
241241+}
242242+243243+// dirToImportPath returns the pseudo-import path we use for a package
244244+// outside the CUE path. It begins with _/ and then contains the full path
245245+// to the directory. If the package lives in c:\home\gopher\my\pkg then
246246+// the pseudo-import path is _/c_/home/gopher/my/pkg.
247247+// Using a pseudo-import path like this makes the ./ imports no longer
248248+// a special case, so that all the code to deal with ordinary imports works
249249+// automatically.
250250+func dirToImportPath(dir string) string {
251251+ return pathpkg.Join("_", strings.Map(makeImportValid, filepath.ToSlash(dir)))
252252+}
253253+254254+func makeImportValid(r rune) rune {
255255+ // Should match Go spec, compilers, and ../../go/parser/parser.go:/isValidImport.
256256+ const illegalChars = `!"#$%&'()*,:;<=>?[\]^{|}` + "`\uFFFD"
257257+ if !unicode.IsGraphic(r) || unicode.IsSpace(r) || strings.ContainsRune(illegalChars, r) {
258258+ return '_'
259259+ }
260260+ return r
261261+}
+116
cue/load/loader_test.go
···11+// Copyright 2018 The CUE Authors
22+//
33+// Licensed under the Apache License, Version 2.0 (the "License");
44+// you may not use this file except in compliance with the License.
55+// You may obtain a copy of the License at
66+//
77+// http://www.apache.org/licenses/LICENSE-2.0
88+//
99+// Unless required by applicable law or agreed to in writing, software
1010+// distributed under the License is distributed on an "AS IS" BASIS,
1111+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212+// See the License for the specific language governing permissions and
1313+// limitations under the License.
1414+1515+package load
1616+1717+import (
1818+ "bytes"
1919+ "fmt"
2020+ "os"
2121+ "path/filepath"
2222+ "strconv"
2323+ "strings"
2424+ "testing"
2525+2626+ build "cuelang.org/go/cue/build"
2727+ "cuelang.org/go/internal/str"
2828+)
2929+3030+// TestLoad is an end-to-end test.
3131+func TestLoad(t *testing.T) {
3232+ cwd, err := os.Getwd()
3333+ if err != nil {
3434+ t.Fatal(err)
3535+ }
3636+ args := str.StringList
3737+ testCases := []struct {
3838+ args []string
3939+ want string
4040+ err string
4141+ }{{
4242+ args: nil,
4343+ want: "test: test.cue (1 files)",
4444+ }, {
4545+ args: args("."),
4646+ want: "test: test.cue (1 files)",
4747+ }, {
4848+ args: args("./other/..."),
4949+ want: `
5050+main: other/main.cue (1 files)
5151+ file: other/file/file.cue (1 files);main: other/main.cue (1 files)
5252+ file: other/file/file.cue (1 files)`,
5353+ }, {
5454+ args: args("./anon"),
5555+ want: ": (0 files)",
5656+ err: "build constraints exclude all CUE files",
5757+ }, {
5858+ args: args("./other"),
5959+ want: `
6060+main: other/main.cue (1 files)
6161+ file: other/file/file.cue (1 files)`,
6262+ }, {
6363+ args: args("./hello"),
6464+ want: "test: test.cue hello/test.cue (2 files)",
6565+ }, {
6666+ args: args("./anon.cue", "./other/anon.cue"),
6767+ want: ": ./anon.cue ./other/anon.cue (2 files)",
6868+ }, {
6969+ // Absolute file is normalized.
7070+ args: args(filepath.Join(cwd, "testdata", "anon.cue")),
7171+ want: ": ./anon.cue (1 files)",
7272+ }, {
7373+ args: args("non-existing"),
7474+ want: ": (0 files)",
7575+ err: `cannot find package "non-existing"`,
7676+ }, {
7777+ args: args("./empty"),
7878+ want: ": (0 files)",
7979+ err: `no CUE files in ./empty`,
8080+ }}
8181+ for i, tc := range testCases {
8282+ t.Run(strconv.Itoa(i)+"/"+strings.Join(tc.args, ":"), func(t *testing.T) {
8383+ c := &Config{Dir: filepath.Join(cwd, testdata)}
8484+ pkgs := Instances(tc.args, c)
8585+8686+ var errs, data []string
8787+ for _, p := range pkgs {
8888+ if p.Err != nil {
8989+ errs = append(errs, p.Err.Error())
9090+ }
9191+ got := strings.TrimSpace(pkgInfo(pkgs[0]))
9292+ data = append(data, got)
9393+ }
9494+9595+ if err := strings.Join(errs, ";"); err == "" != (tc.err == "") ||
9696+ err != "" && !strings.Contains(err, tc.err) {
9797+ t.Errorf("error:\n got: %v\nwant: %v", err, tc.err)
9898+ }
9999+ got := strings.Join(data, ";")
100100+ want := strings.TrimSpace(tc.want)
101101+ if got != want {
102102+ t.Errorf("got:\n%v\nwant:\n%v", got, want)
103103+ }
104104+ })
105105+ }
106106+}
107107+108108+func pkgInfo(p *build.Instance) string {
109109+ b := &bytes.Buffer{}
110110+ fmt.Fprintf(b, "%s: %s (%d files)\n",
111111+ p.PkgName, strings.Join(p.CUEFiles, " "), len(p.Files))
112112+ for _, p := range p.Imports {
113113+ fmt.Fprintf(b, "\t%s\n", pkgInfo(p))
114114+ }
115115+ return b.String()
116116+}
+213
cue/load/match.go
···11+// Copyright 2018 The CUE Authors
22+//
33+// Licensed under the Apache License, Version 2.0 (the "License");
44+// you may not use this file except in compliance with the License.
55+// You may obtain a copy of the License at
66+//
77+// http://www.apache.org/licenses/LICENSE-2.0
88+//
99+// Unless required by applicable law or agreed to in writing, software
1010+// distributed under the License is distributed on an "AS IS" BASIS,
1111+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212+// See the License for the specific language governing permissions and
1313+// limitations under the License.
1414+1515+package load
1616+1717+import (
1818+ "bytes"
1919+ "fmt"
2020+ "strings"
2121+ "unicode"
2222+)
2323+2424+// matchFileTest reports whether the file with the given name in the given directory
2525+// matches the context and would be included in a Package created by ImportDir
2626+// of that directory.
2727+//
2828+// matchFileTest considers the name of the file and may use cfg.Build.OpenFile to
2929+// read some or all of the file's content.
3030+func matchFileTest(cfg *Config, dir, name string) (match bool, err error) {
3131+ match, _, _, err = matchFile(cfg, dir, name, false, false, nil)
3232+ return
3333+}
3434+3535+// matchFile determines whether the file with the given name in the given directory
3636+// should be included in the package being constructed.
3737+// It returns the data read from the file.
3838+// If returnImports is true and name denotes a CUE file, matchFile reads
3939+// until the end of the imports (and returns that data) even though it only
4040+// considers text until the first non-comment.
4141+// If allTags is non-nil, matchFile records any encountered build tag
4242+// by setting allTags[tag] = true.
4343+func matchFile(cfg *Config, dir, name string, returnImports, allFiles bool, allTags map[string]bool) (match bool, data []byte, filename string, err error) {
4444+ if strings.HasPrefix(name, "_") ||
4545+ strings.HasPrefix(name, ".") {
4646+ return
4747+ }
4848+4949+ i := strings.LastIndex(name, ".")
5050+ if i < 0 {
5151+ i = len(name)
5252+ }
5353+ ext := name[i:]
5454+5555+ switch ext {
5656+ case cueSuffix:
5757+ // tentatively okay - read to make sure
5858+ default:
5959+ // skip
6060+ return
6161+ }
6262+6363+ filename = cfg.fileSystem.joinPath(dir, name)
6464+ f, err := cfg.fileSystem.openFile(filename)
6565+ if err != nil {
6666+ return
6767+ }
6868+6969+ if strings.HasSuffix(filename, cueSuffix) {
7070+ data, err = readImports(f, false, nil)
7171+ } else {
7272+ data, err = readComments(f)
7373+ }
7474+ f.Close()
7575+ if err != nil {
7676+ err = fmt.Errorf("read %s: %v", filename, err)
7777+ return
7878+ }
7979+8080+ // Look for +build comments to accept or reject the file.
8181+ if !shouldBuild(cfg, data, allTags) && !allFiles {
8282+ return
8383+ }
8484+8585+ match = true
8686+ return
8787+}
8888+8989+// shouldBuild reports whether it is okay to use this file,
9090+// The rule is that in the file's leading run of // comments
9191+// and blank lines, which must be followed by a blank line
9292+// (to avoid including a Go package clause doc comment),
9393+// lines beginning with '// +build' are taken as build directives.
9494+//
9595+// The file is accepted only if each such line lists something
9696+// matching the file. For example:
9797+//
9898+// // +build windows linux
9999+//
100100+// marks the file as applicable only on Windows and Linux.
101101+//
102102+// If shouldBuild finds a //go:binary-only-package comment in the file,
103103+// it sets *binaryOnly to true. Otherwise it does not change *binaryOnly.
104104+//
105105+func shouldBuild(cfg *Config, content []byte, allTags map[string]bool) bool {
106106+ // Pass 1. Identify leading run of // comments and blank lines,
107107+ // which must be followed by a blank line.
108108+ end := 0
109109+ p := content
110110+ for len(p) > 0 {
111111+ line := p
112112+ if i := bytes.IndexByte(line, '\n'); i >= 0 {
113113+ line, p = line[:i], p[i+1:]
114114+ } else {
115115+ p = p[len(p):]
116116+ }
117117+ line = bytes.TrimSpace(line)
118118+ if len(line) == 0 { // Blank line
119119+ end = len(content) - len(p)
120120+ continue
121121+ }
122122+ if !bytes.HasPrefix(line, slashslash) { // Not comment line
123123+ break
124124+ }
125125+ }
126126+ content = content[:end]
127127+128128+ // Pass 2. Process each line in the run.
129129+ p = content
130130+ allok := true
131131+ for len(p) > 0 {
132132+ line := p
133133+ if i := bytes.IndexByte(line, '\n'); i >= 0 {
134134+ line, p = line[:i], p[i+1:]
135135+ } else {
136136+ p = p[len(p):]
137137+ }
138138+ line = bytes.TrimSpace(line)
139139+ if bytes.HasPrefix(line, slashslash) {
140140+ line = bytes.TrimSpace(line[len(slashslash):])
141141+ if len(line) > 0 && line[0] == '+' {
142142+ // Looks like a comment +line.
143143+ f := strings.Fields(string(line))
144144+ if f[0] == "+build" {
145145+ ok := false
146146+ for _, tok := range f[1:] {
147147+ if doMatch(cfg, tok, allTags) {
148148+ ok = true
149149+ }
150150+ }
151151+ if !ok {
152152+ allok = false
153153+ }
154154+ }
155155+ }
156156+ }
157157+ }
158158+159159+ return allok
160160+}
161161+162162+// doMatch reports whether the name is one of:
163163+//
164164+// tag (if tag is listed in cfg.Build.BuildTags or cfg.Build.ReleaseTags)
165165+// !tag (if tag is not listed in cfg.Build.BuildTags or cfg.Build.ReleaseTags)
166166+// a comma-separated list of any of these
167167+//
168168+func doMatch(cfg *Config, name string, allTags map[string]bool) bool {
169169+ if name == "" {
170170+ if allTags != nil {
171171+ allTags[name] = true
172172+ }
173173+ return false
174174+ }
175175+ if i := strings.Index(name, ","); i >= 0 {
176176+ // comma-separated list
177177+ ok1 := doMatch(cfg, name[:i], allTags)
178178+ ok2 := doMatch(cfg, name[i+1:], allTags)
179179+ return ok1 && ok2
180180+ }
181181+ if strings.HasPrefix(name, "!!") { // bad syntax, reject always
182182+ return false
183183+ }
184184+ if strings.HasPrefix(name, "!") { // negation
185185+ return len(name) > 1 && !doMatch(cfg, name[1:], allTags)
186186+ }
187187+188188+ if allTags != nil {
189189+ allTags[name] = true
190190+ }
191191+192192+ // Tags must be letters, digits, underscores or dots.
193193+ // Unlike in CUE identifiers, all digits are fine (e.g., "386").
194194+ for _, c := range name {
195195+ if !unicode.IsLetter(c) && !unicode.IsDigit(c) && c != '_' && c != '.' {
196196+ return false
197197+ }
198198+ }
199199+200200+ // other tags
201201+ for _, tag := range cfg.BuildTags {
202202+ if tag == name {
203203+ return true
204204+ }
205205+ }
206206+ for _, tag := range cfg.releaseTags {
207207+ if tag == name {
208208+ return true
209209+ }
210210+ }
211211+212212+ return false
213213+}
+144
cue/load/match_test.go
···11+// Copyright 2018 The CUE Authors
22+//
33+// Licensed under the Apache License, Version 2.0 (the "License");
44+// you may not use this file except in compliance with the License.
55+// You may obtain a copy of the License at
66+//
77+// http://www.apache.org/licenses/LICENSE-2.0
88+//
99+// Unless required by applicable law or agreed to in writing, software
1010+// distributed under the License is distributed on an "AS IS" BASIS,
1111+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212+// See the License for the specific language governing permissions and
1313+// limitations under the License.
1414+1515+package load
1616+1717+import (
1818+ "io"
1919+ "reflect"
2020+ "strings"
2121+ "testing"
2222+)
2323+2424+func TestMatch(t *testing.T) {
2525+ c := &Config{}
2626+ what := "default"
2727+ matchFn := func(tag string, want map[string]bool) {
2828+ t.Helper()
2929+ m := make(map[string]bool)
3030+ if !doMatch(c, tag, m) {
3131+ t.Errorf("%s context should match %s, does not", what, tag)
3232+ }
3333+ if !reflect.DeepEqual(m, want) {
3434+ t.Errorf("%s tags = %v, want %v", tag, m, want)
3535+ }
3636+ }
3737+ noMatch := func(tag string, want map[string]bool) {
3838+ m := make(map[string]bool)
3939+ if doMatch(c, tag, m) {
4040+ t.Errorf("%s context should NOT match %s, does", what, tag)
4141+ }
4242+ if !reflect.DeepEqual(m, want) {
4343+ t.Errorf("%s tags = %v, want %v", tag, m, want)
4444+ }
4545+ }
4646+4747+ c.BuildTags = []string{"foo"}
4848+ matchFn("foo", map[string]bool{"foo": true})
4949+ noMatch("!foo", map[string]bool{"foo": true})
5050+ matchFn("foo,!bar", map[string]bool{"foo": true, "bar": true})
5151+ noMatch("!", map[string]bool{})
5252+}
5353+5454+func TestShouldBuild(t *testing.T) {
5555+ const file1 = "// +build tag1\n\n" +
5656+ "package main\n"
5757+ want1 := map[string]bool{"tag1": true}
5858+5959+ const file2 = "// +build cgo\n\n" +
6060+ "// This package implements parsing of tags like\n" +
6161+ "// +build tag1\n" +
6262+ "package load"
6363+ want2 := map[string]bool{"cgo": true}
6464+6565+ const file3 = "// Copyright The CUE Authors.\n\n" +
6666+ "package load\n\n" +
6767+ "// shouldBuild checks tags given by lines of the form\n" +
6868+ "// +build tag\n" +
6969+ "func shouldBuild(content []byte)\n"
7070+ want3 := map[string]bool{}
7171+7272+ c := &Config{BuildTags: []string{"tag1"}}
7373+ m := map[string]bool{}
7474+ if !shouldBuild(c, []byte(file1), m) {
7575+ t.Errorf("shouldBuild(file1) = false, want true")
7676+ }
7777+ if !reflect.DeepEqual(m, want1) {
7878+ t.Errorf("shouldBuild(file1) tags = %v, want %v", m, want1)
7979+ }
8080+8181+ m = map[string]bool{}
8282+ if shouldBuild(c, []byte(file2), m) {
8383+ t.Errorf("shouldBuild(file2) = true, want false")
8484+ }
8585+ if !reflect.DeepEqual(m, want2) {
8686+ t.Errorf("shouldBuild(file2) tags = %v, want %v", m, want2)
8787+ }
8888+8989+ m = map[string]bool{}
9090+ c = &Config{BuildTags: nil}
9191+ if !shouldBuild(c, []byte(file3), m) {
9292+ t.Errorf("shouldBuild(file3) = false, want true")
9393+ }
9494+ if !reflect.DeepEqual(m, want3) {
9595+ t.Errorf("shouldBuild(file3) tags = %v, want %v", m, want3)
9696+ }
9797+}
9898+9999+type readNopCloser struct {
100100+ io.Reader
101101+}
102102+103103+func (r readNopCloser) Close() error {
104104+ return nil
105105+}
106106+107107+var (
108108+ cfg = &Config{BuildTags: []string{"enable"}}
109109+ defCfg = &Config{}
110110+)
111111+112112+var matchFileTests = []struct {
113113+ cfg *Config
114114+ name string
115115+ data string
116116+ match bool
117117+}{
118118+ {defCfg, "foo.cue", "", true},
119119+ {defCfg, "foo.cue", "// +build enable\n\npackage foo\n", false},
120120+ {defCfg, "foo.cue", "// +build !enable\n\npackage foo\n", true},
121121+ {defCfg, "foo1.cue", "// +build linux\n\npackage foo\n", false},
122122+ {defCfg, "foo.badsuffix", "", false},
123123+ {cfg, "foo.cue", "// +build enable\n\npackage foo\n", true},
124124+ {cfg, "foo.cue", "// +build !enable\n\npackage foo\n", false},
125125+}
126126+127127+func TestMatchFile(t *testing.T) {
128128+ for _, tt := range matchFileTests {
129129+ ctxt := &tt.cfg.fileSystem
130130+ ctxt.OpenFile = func(path string) (r io.ReadCloser, err error) {
131131+ if path != "x+"+tt.name {
132132+ t.Fatalf("OpenFile asked for %q, expected %q", path, "x+"+tt.name)
133133+ }
134134+ return &readNopCloser{strings.NewReader(tt.data)}, nil
135135+ }
136136+ ctxt.JoinPath = func(elem ...string) string {
137137+ return strings.Join(elem, "+")
138138+ }
139139+ match, err := matchFileTest(tt.cfg, "x", tt.name)
140140+ if match != tt.match || err != nil {
141141+ t.Fatalf("MatchFile(%q) = %v, %v, want %v, nil", tt.name, match, err, tt.match)
142142+ }
143143+ }
144144+}
+68
cue/load/package.go
···11+// Copyright 2018 The CUE Authors
22+//
33+// Licensed under the Apache License, Version 2.0 (the "License");
44+// you may not use this file except in compliance with the License.
55+// You may obtain a copy of the License at
66+//
77+// http://www.apache.org/licenses/LICENSE-2.0
88+//
99+// Unless required by applicable law or agreed to in writing, software
1010+// distributed under the License is distributed on an "AS IS" BASIS,
1111+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212+// See the License for the specific language governing permissions and
1313+// limitations under the License.
1414+1515+package load
1616+1717+import (
1818+ "unicode/utf8"
1919+2020+ "cuelang.org/go/cue/build"
2121+ "cuelang.org/go/internal/str"
2222+)
2323+2424+// Package rules:
2525+//
2626+// - the package clause defines a namespace.
2727+// - a cue file without a package clause is a standalone file.
2828+// - all files with the same package name within a directory and its
2929+// ancestor directories up to the package root belong to the same package.
3030+// - The package root is either the top of the file hierarchy or the first
3131+// directory in which a cue.mod file is defined.
3232+//
3333+// The contents of a namespace depends on the directory that is selected as the
3434+// starting point to load a package. An instance defines a package-directory
3535+// pair.
3636+3737+// allFiles returns the names of all the files considered for the package.
3838+// This is used for sanity and security checks, so we include all files,
3939+// even IgnoredGoFiles, because some subcommands consider them.
4040+func allFiles(p *build.Instance) []string {
4141+ return str.StringList(
4242+ p.CUEFiles,
4343+ p.ToolCUEFiles,
4444+ p.TestCUEFiles,
4545+ p.IgnoredCUEFiles,
4646+ p.InvalidCUEFiles,
4747+ p.DataFiles,
4848+ )
4949+}
5050+5151+var foldPath = make(map[string]string)
5252+5353+// safeArg reports whether arg is a "safe" command-line argument,
5454+// meaning that when it appears in a command-line, it probably
5555+// doesn't have some special meaning other than its own name.
5656+// Obviously args beginning with - are not safe (they look like flags).
5757+// Less obviously, args beginning with @ are not safe (they look like
5858+// GNU binutils flagfile specifiers, sometimes called "response files").
5959+// To be conservative, we reject almost any arg beginning with non-alphanumeric ASCII.
6060+// We accept leading . _ and / as likely in file system paths.
6161+// There is a copy of this function in cmd/compile/internal/gc/noder.go.
6262+func safeArg(name string) bool {
6363+ if name == "" {
6464+ return false
6565+ }
6666+ c := name[0]
6767+ return '0' <= c && c <= '9' || 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || c == '.' || c == '_' || c == '/' || c >= utf8.RuneSelf
6868+}
+257
cue/load/read.go
···11+// Copyright 2018 The CUE Authors
22+//
33+// Licensed under the Apache License, Version 2.0 (the "License");
44+// you may not use this file except in compliance with the License.
55+// You may obtain a copy of the License at
66+//
77+// http://www.apache.org/licenses/LICENSE-2.0
88+//
99+// Unless required by applicable law or agreed to in writing, software
1010+// distributed under the License is distributed on an "AS IS" BASIS,
1111+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212+// See the License for the specific language governing permissions and
1313+// limitations under the License.
1414+1515+package load
1616+1717+import (
1818+ "bufio"
1919+ "errors"
2020+ "io"
2121+ "unicode/utf8"
2222+)
2323+2424+type importReader struct {
2525+ b *bufio.Reader
2626+ buf []byte
2727+ peek byte
2828+ err error
2929+ eof bool
3030+ nerr int
3131+}
3232+3333+func isIdent(c byte) bool {
3434+ return 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || '0' <= c && c <= '9' || c == '_' || c >= utf8.RuneSelf
3535+}
3636+3737+var (
3838+ errSyntax = errors.New("syntax error")
3939+ errNUL = errors.New("unexpected NUL in input")
4040+)
4141+4242+// syntaxError records a syntax error, but only if an I/O error has not already been recorded.
4343+func (r *importReader) syntaxError() {
4444+ if r.err == nil {
4545+ r.err = errSyntax
4646+ }
4747+}
4848+4949+// readByte reads the next byte from the input, saves it in buf, and returns it.
5050+// If an error occurs, readByte records the error in r.err and returns 0.
5151+func (r *importReader) readByte() byte {
5252+ c, err := r.b.ReadByte()
5353+ if err == nil {
5454+ r.buf = append(r.buf, c)
5555+ if c == 0 {
5656+ err = errNUL
5757+ }
5858+ }
5959+ if err != nil {
6060+ if err == io.EOF {
6161+ r.eof = true
6262+ } else if r.err == nil {
6363+ r.err = err
6464+ }
6565+ c = 0
6666+ }
6767+ return c
6868+}
6969+7070+// peekByte returns the next byte from the input reader but does not advance beyond it.
7171+// If skipSpace is set, peekByte skips leading spaces and comments.
7272+func (r *importReader) peekByte(skipSpace bool) byte {
7373+ if r.err != nil {
7474+ if r.nerr++; r.nerr > 10000 {
7575+ panic("go/build: import reader looping")
7676+ }
7777+ return 0
7878+ }
7979+8080+ // Use r.peek as first input byte.
8181+ // Don't just return r.peek here: it might have been left by peekByte(false)
8282+ // and this might be peekByte(true).
8383+ c := r.peek
8484+ if c == 0 {
8585+ c = r.readByte()
8686+ }
8787+ for r.err == nil && !r.eof {
8888+ if skipSpace {
8989+ // For the purposes of this reader, semicolons are never necessary to
9090+ // understand the input and are treated as spaces.
9191+ switch c {
9292+ case ' ', '\f', '\t', '\r', '\n', ';':
9393+ c = r.readByte()
9494+ continue
9595+9696+ case '/':
9797+ c = r.readByte()
9898+ if c == '/' {
9999+ for c != '\n' && r.err == nil && !r.eof {
100100+ c = r.readByte()
101101+ }
102102+ } else if c == '*' {
103103+ var c1 byte
104104+ for (c != '*' || c1 != '/') && r.err == nil {
105105+ if r.eof {
106106+ r.syntaxError()
107107+ }
108108+ c, c1 = c1, r.readByte()
109109+ }
110110+ } else {
111111+ r.syntaxError()
112112+ }
113113+ c = r.readByte()
114114+ continue
115115+ }
116116+ }
117117+ break
118118+ }
119119+ r.peek = c
120120+ return r.peek
121121+}
122122+123123+// nextByte is like peekByte but advances beyond the returned byte.
124124+func (r *importReader) nextByte(skipSpace bool) byte {
125125+ c := r.peekByte(skipSpace)
126126+ r.peek = 0
127127+ return c
128128+}
129129+130130+// readKeyword reads the given keyword from the input.
131131+// If the keyword is not present, readKeyword records a syntax error.
132132+func (r *importReader) readKeyword(kw string) {
133133+ r.peekByte(true)
134134+ for i := 0; i < len(kw); i++ {
135135+ if r.nextByte(false) != kw[i] {
136136+ r.syntaxError()
137137+ return
138138+ }
139139+ }
140140+ if isIdent(r.peekByte(false)) {
141141+ r.syntaxError()
142142+ }
143143+}
144144+145145+// readIdent reads an identifier from the input.
146146+// If an identifier is not present, readIdent records a syntax error.
147147+func (r *importReader) readIdent() {
148148+ c := r.peekByte(true)
149149+ if !isIdent(c) {
150150+ r.syntaxError()
151151+ return
152152+ }
153153+ for isIdent(r.peekByte(false)) {
154154+ r.peek = 0
155155+ }
156156+}
157157+158158+// readString reads a quoted string literal from the input.
159159+// If an identifier is not present, readString records a syntax error.
160160+func (r *importReader) readString(save *[]string) {
161161+ switch r.nextByte(true) {
162162+ case '`':
163163+ start := len(r.buf) - 1
164164+ for r.err == nil {
165165+ if r.nextByte(false) == '`' {
166166+ if save != nil {
167167+ *save = append(*save, string(r.buf[start:]))
168168+ }
169169+ break
170170+ }
171171+ if r.eof {
172172+ r.syntaxError()
173173+ }
174174+ }
175175+ case '"':
176176+ start := len(r.buf) - 1
177177+ for r.err == nil {
178178+ c := r.nextByte(false)
179179+ if c == '"' {
180180+ if save != nil {
181181+ *save = append(*save, string(r.buf[start:]))
182182+ }
183183+ break
184184+ }
185185+ if r.eof || c == '\n' {
186186+ r.syntaxError()
187187+ }
188188+ if c == '\\' {
189189+ r.nextByte(false)
190190+ }
191191+ }
192192+ default:
193193+ r.syntaxError()
194194+ }
195195+}
196196+197197+// readImport reads an import clause - optional identifier followed by quoted string -
198198+// from the input.
199199+func (r *importReader) readImport(imports *[]string) {
200200+ c := r.peekByte(true)
201201+ if c == '.' {
202202+ r.peek = 0
203203+ } else if isIdent(c) {
204204+ r.readIdent()
205205+ }
206206+ r.readString(imports)
207207+}
208208+209209+// readComments is like ioutil.ReadAll, except that it only reads the leading
210210+// block of comments in the file.
211211+func readComments(f io.Reader) ([]byte, error) {
212212+ r := &importReader{b: bufio.NewReader(f)}
213213+ r.peekByte(true)
214214+ if r.err == nil && !r.eof {
215215+ // Didn't reach EOF, so must have found a non-space byte. Remove it.
216216+ r.buf = r.buf[:len(r.buf)-1]
217217+ }
218218+ return r.buf, r.err
219219+}
220220+221221+// readImports is like ioutil.ReadAll, except that it expects a Go file as input
222222+// and stops reading the input once the imports have completed.
223223+func readImports(f io.Reader, reportSyntaxError bool, imports *[]string) ([]byte, error) {
224224+ r := &importReader{b: bufio.NewReader(f)}
225225+226226+ r.readKeyword("package")
227227+ r.readIdent()
228228+ for r.peekByte(true) == 'i' {
229229+ r.readKeyword("import")
230230+ if r.peekByte(true) == '(' {
231231+ r.nextByte(false)
232232+ for r.peekByte(true) != ')' && r.err == nil {
233233+ r.readImport(imports)
234234+ }
235235+ r.nextByte(false)
236236+ } else {
237237+ r.readImport(imports)
238238+ }
239239+ }
240240+241241+ // If we stopped successfully before EOF, we read a byte that told us we were done.
242242+ // Return all but that last byte, which would cause a syntax error if we let it through.
243243+ if r.err == nil && !r.eof {
244244+ return r.buf[:len(r.buf)-1], nil
245245+ }
246246+247247+ // If we stopped for a syntax error, consume the whole file so that
248248+ // we are sure we don't change the errors that go/parser returns.
249249+ if r.err == errSyntax && !reportSyntaxError {
250250+ r.err = nil
251251+ for r.err == nil && !r.eof {
252252+ r.readByte()
253253+ }
254254+ }
255255+256256+ return r.buf, r.err
257257+}
+236
cue/load/read_test.go
···11+// Copyright 2018 The CUE Authors
22+//
33+// Licensed under the Apache License, Version 2.0 (the "License");
44+// you may not use this file except in compliance with the License.
55+// You may obtain a copy of the License at
66+//
77+// http://www.apache.org/licenses/LICENSE-2.0
88+//
99+// Unless required by applicable law or agreed to in writing, software
1010+// distributed under the License is distributed on an "AS IS" BASIS,
1111+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212+// See the License for the specific language governing permissions and
1313+// limitations under the License.
1414+1515+package load
1616+1717+import (
1818+ "io"
1919+ "strings"
2020+ "testing"
2121+)
2222+2323+const quote = "`"
2424+2525+type readTest struct {
2626+ // Test input contains ℙ where readImports should stop.
2727+ in string
2828+ err string
2929+}
3030+3131+var readImportsTests = []readTest{
3232+ {
3333+ `package p`,
3434+ "",
3535+ },
3636+ {
3737+ `package p; import "x"`,
3838+ "",
3939+ },
4040+ {
4141+ `package p; import . "x"`,
4242+ "",
4343+ },
4444+ {
4545+ `package p; import "x";ℙvar x = 1`,
4646+ "",
4747+ },
4848+ {
4949+ `package p
5050+5151+ // comment
5252+5353+ import "x"
5454+ import _ "x"
5555+ import a "x"
5656+5757+ /* comment */
5858+5959+ import (
6060+ "x" /* comment */
6161+ _ "x"
6262+ a "x" // comment
6363+ ` + quote + `x` + quote + `
6464+ _ /*comment*/ ` + quote + `x` + quote + `
6565+ a ` + quote + `x` + quote + `
6666+ )
6767+ import (
6868+ )
6969+ import ()
7070+ import()import()import()
7171+ import();import();import()
7272+7373+ ℙvar x = 1
7474+ `,
7575+ "",
7676+ },
7777+}
7878+7979+var readCommentsTests = []readTest{
8080+ {
8181+ `ℙpackage p`,
8282+ "",
8383+ },
8484+ {
8585+ `ℙpackage p; import "x"`,
8686+ "",
8787+ },
8888+ {
8989+ `ℙpackage p; import . "x"`,
9090+ "",
9191+ },
9292+ {
9393+ `// foo
9494+9595+ /* bar */
9696+9797+ /* quux */ // baz
9898+9999+ /*/ zot */
100100+101101+ // asdf
102102+ ℙHello, world`,
103103+ "",
104104+ },
105105+}
106106+107107+func testRead(t *testing.T, tests []readTest, read func(io.Reader) ([]byte, error)) {
108108+ for i, tt := range tests {
109109+ var in, testOut string
110110+ j := strings.Index(tt.in, "ℙ")
111111+ if j < 0 {
112112+ in = tt.in
113113+ testOut = tt.in
114114+ } else {
115115+ in = tt.in[:j] + tt.in[j+len("ℙ"):]
116116+ testOut = tt.in[:j]
117117+ }
118118+ r := strings.NewReader(in)
119119+ buf, err := read(r)
120120+ if err != nil {
121121+ if tt.err == "" {
122122+ t.Errorf("#%d: err=%q, expected success (%q)", i, err, string(buf))
123123+ continue
124124+ }
125125+ if !strings.Contains(err.Error(), tt.err) {
126126+ t.Errorf("#%d: err=%q, expected %q", i, err, tt.err)
127127+ continue
128128+ }
129129+ continue
130130+ }
131131+ if err == nil && tt.err != "" {
132132+ t.Errorf("#%d: success, expected %q", i, tt.err)
133133+ continue
134134+ }
135135+136136+ out := string(buf)
137137+ if out != testOut {
138138+ t.Errorf("#%d: wrong output:\nhave %q\nwant %q\n", i, out, testOut)
139139+ }
140140+ }
141141+}
142142+143143+func TestReadImports(t *testing.T) {
144144+ testRead(t, readImportsTests, func(r io.Reader) ([]byte, error) { return readImports(r, true, nil) })
145145+}
146146+147147+func TestReadComments(t *testing.T) {
148148+ testRead(t, readCommentsTests, readComments)
149149+}
150150+151151+var readFailuresTests = []readTest{
152152+ {
153153+ `package`,
154154+ "syntax error",
155155+ },
156156+ {
157157+ "package p\n\x00\nimport `math`\n",
158158+ "unexpected NUL in input",
159159+ },
160160+ {
161161+ `package p; import`,
162162+ "syntax error",
163163+ },
164164+ {
165165+ `package p; import "`,
166166+ "syntax error",
167167+ },
168168+ {
169169+ "package p; import ` \n\n",
170170+ "syntax error",
171171+ },
172172+ {
173173+ `package p; import "x`,
174174+ "syntax error",
175175+ },
176176+ {
177177+ `package p; import _`,
178178+ "syntax error",
179179+ },
180180+ {
181181+ `package p; import _ "`,
182182+ "syntax error",
183183+ },
184184+ {
185185+ `package p; import _ "x`,
186186+ "syntax error",
187187+ },
188188+ {
189189+ `package p; import .`,
190190+ "syntax error",
191191+ },
192192+ {
193193+ `package p; import . "`,
194194+ "syntax error",
195195+ },
196196+ {
197197+ `package p; import . "x`,
198198+ "syntax error",
199199+ },
200200+ {
201201+ `package p; import (`,
202202+ "syntax error",
203203+ },
204204+ {
205205+ `package p; import ("`,
206206+ "syntax error",
207207+ },
208208+ {
209209+ `package p; import ("x`,
210210+ "syntax error",
211211+ },
212212+ {
213213+ `package p; import ("x"`,
214214+ "syntax error",
215215+ },
216216+}
217217+218218+func TestReadFailures(t *testing.T) {
219219+ // Errors should be reported (true arg to readImports).
220220+ testRead(t, readFailuresTests, func(r io.Reader) ([]byte, error) { return readImports(r, true, nil) })
221221+}
222222+223223+func TestReadFailuresIgnored(t *testing.T) {
224224+ // Syntax errors should not be reported (false arg to readImports).
225225+ // Instead, entire file should be the output and no error.
226226+ // Convert tests not to return syntax errors.
227227+ tests := make([]readTest, len(readFailuresTests))
228228+ copy(tests, readFailuresTests)
229229+ for i := range tests {
230230+ tt := &tests[i]
231231+ if !strings.Contains(tt.err, "NUL") {
232232+ tt.err = ""
233233+ }
234234+ }
235235+ testRead(t, tests, func(r io.Reader) ([]byte, error) { return readImports(r, false, nil) })
236236+}
+497
cue/load/search.go
···11+// Copyright 2018 The CUE Authors
22+//
33+// Licensed under the Apache License, Version 2.0 (the "License");
44+// you may not use this file except in compliance with the License.
55+// You may obtain a copy of the License at
66+//
77+// http://www.apache.org/licenses/LICENSE-2.0
88+//
99+// Unless required by applicable law or agreed to in writing, software
1010+// distributed under the License is distributed on an "AS IS" BASIS,
1111+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212+// See the License for the specific language governing permissions and
1313+// limitations under the License.
1414+1515+package load
1616+1717+import (
1818+ "fmt" // TODO: remove this usage
1919+ "log"
2020+ "os"
2121+ "path"
2222+ "path/filepath"
2323+ "regexp"
2424+ "strings"
2525+2626+ build "cuelang.org/go/cue/build"
2727+)
2828+2929+// A match represents the result of matching a single package pattern.
3030+type match struct {
3131+ Pattern string // the pattern itself
3232+ Literal bool // whether it is a literal (no wildcards)
3333+ Pkgs []*build.Instance
3434+ Err error
3535+}
3636+3737+// TODO: should be matched from module file only.
3838+// The pattern is either "all" (all packages), "std" (standard packages),
3939+// "cmd" (standard commands), or a path including "...".
4040+func (l *loader) matchPackages(pattern string) *match {
4141+ // cfg := l.cfg
4242+ m := &match{
4343+ Pattern: pattern,
4444+ Literal: false,
4545+ }
4646+ // match := func(string) bool { return true }
4747+ // treeCanMatch := func(string) bool { return true }
4848+ // if !isMetaPackage(pattern) {
4949+ // match = matchPattern(pattern)
5050+ // treeCanMatch = treeCanMatchPattern(pattern)
5151+ // }
5252+5353+ // have := map[string]bool{
5454+ // "builtin": true, // ignore pseudo-package that exists only for documentation
5555+ // }
5656+5757+ // for _, src := range cfg.srcDirs() {
5858+ // if pattern == "std" || pattern == "cmd" {
5959+ // continue
6060+ // }
6161+ // src = filepath.Clean(src) + string(filepath.Separator)
6262+ // root := src
6363+ // if pattern == "cmd" {
6464+ // root += "cmd" + string(filepath.Separator)
6565+ // }
6666+ // filepath.Walk(root, func(path string, fi os.FileInfo, err error) error {
6767+ // if err != nil || path == src {
6868+ // return nil
6969+ // }
7070+7171+ // want := true
7272+ // // Avoid .foo, _foo, and testdata directory trees.
7373+ // _, elem := filepath.Split(path)
7474+ // if strings.HasPrefix(elem, ".") || strings.HasPrefix(elem, "_") || elem == "testdata" {
7575+ // want = false
7676+ // }
7777+7878+ // name := filepath.ToSlash(path[len(src):])
7979+ // if pattern == "std" && (!isStandardImportPath(name) || name == "cmd") {
8080+ // // The name "std" is only the standard library.
8181+ // // If the name is cmd, it's the root of the command tree.
8282+ // want = false
8383+ // }
8484+ // if !treeCanMatch(name) {
8585+ // want = false
8686+ // }
8787+8888+ // if !fi.IsDir() {
8989+ // if fi.Mode()&os.ModeSymlink != 0 && want {
9090+ // if target, err := os.Stat(path); err == nil && target.IsDir() {
9191+ // fmt.Fprintf(os.Stderr, "warning: ignoring symlink %s\n", path)
9292+ // }
9393+ // }
9494+ // return nil
9595+ // }
9696+ // if !want {
9797+ // return filepath.SkipDir
9898+ // }
9999+100100+ // if have[name] {
101101+ // return nil
102102+ // }
103103+ // have[name] = true
104104+ // if !match(name) {
105105+ // return nil
106106+ // }
107107+ // pkg := l.importPkg(".", path)
108108+ // if err := pkg.Error; err != nil {
109109+ // if _, noGo := err.(*noCUEError); noGo {
110110+ // return nil
111111+ // }
112112+ // }
113113+114114+ // // If we are expanding "cmd", skip main
115115+ // // packages under cmd/vendor. At least as of
116116+ // // March, 2017, there is one there for the
117117+ // // vendored pprof tool.
118118+ // if pattern == "cmd" && strings.HasPrefix(pkg.DisplayPath, "cmd/vendor") && pkg.PkgName == "main" {
119119+ // return nil
120120+ // }
121121+122122+ // m.Pkgs = append(m.Pkgs, pkg)
123123+ // return nil
124124+ // })
125125+ // }
126126+ return m
127127+}
128128+129129+// matchPackagesInFS is like allPackages but is passed a pattern
130130+// beginning ./ or ../, meaning it should scan the tree rooted
131131+// at the given directory. There are ... in the pattern too.
132132+// (See go help packages for pattern syntax.)
133133+func (l *loader) matchPackagesInFS(pattern string) *match {
134134+ c := l.cfg
135135+ m := &match{
136136+ Pattern: pattern,
137137+ Literal: false,
138138+ }
139139+140140+ // Find directory to begin the scan.
141141+ // Could be smarter but this one optimization
142142+ // is enough for now, since ... is usually at the
143143+ // end of a path.
144144+ i := strings.Index(pattern, "...")
145145+ dir, _ := path.Split(pattern[:i])
146146+147147+ root := l.abs(dir)
148148+149149+ if c.modRoot != "" {
150150+ if !hasFilepathPrefix(root, c.modRoot) {
151151+ m.Err = fmt.Errorf(
152152+ "cue: pattern %s refers to dir %s, outside module root %s",
153153+ pattern, root, c.modRoot)
154154+ return m
155155+ }
156156+ }
157157+158158+ filepath.Walk(root, func(path string, fi os.FileInfo, err error) error {
159159+ if err != nil || !fi.IsDir() {
160160+ return nil
161161+ }
162162+163163+ top := path == root
164164+165165+ // Avoid .foo, _foo, and testdata directory trees, but do not avoid "." or "..".
166166+ _, elem := filepath.Split(path)
167167+ dot := strings.HasPrefix(elem, ".") && elem != "." && elem != ".."
168168+ if dot || strings.HasPrefix(elem, "_") || (elem == "testdata" && !top) {
169169+ return filepath.SkipDir
170170+ }
171171+172172+ if !top {
173173+ // Ignore other modules found in subdirectories.
174174+ if _, err := os.Stat(filepath.Join(path, modFile)); err == nil {
175175+ return filepath.SkipDir
176176+ }
177177+ }
178178+179179+ // name := prefix + filepath.ToSlash(path)
180180+ // if !match(name) {
181181+ // return nil
182182+ // }
183183+184184+ // We keep the directory if we can import it, or if we can't import it
185185+ // due to invalid CUE source files. This means that directories
186186+ // containing parse errors will be built (and fail) instead of being
187187+ // silently skipped as not matching the pattern.
188188+ p := l.importPkg("."+path[len(root):], root)
189189+ if err := p.Err; err != nil && (p == nil || len(p.InvalidCUEFiles) == 0) {
190190+ switch err.(type) {
191191+ case nil:
192192+ break
193193+ case *noCUEError:
194194+ if c.DataFiles && len(p.DataFiles) > 0 {
195195+ break
196196+ }
197197+ return nil
198198+ default:
199199+ log.Print(err)
200200+ return nil
201201+ }
202202+ }
203203+204204+ m.Pkgs = append(m.Pkgs, p)
205205+ return nil
206206+ })
207207+ return m
208208+}
209209+210210+// treeCanMatchPattern(pattern)(name) reports whether
211211+// name or children of name can possibly match pattern.
212212+// Pattern is the same limited glob accepted by matchPattern.
213213+func treeCanMatchPattern(pattern string) func(name string) bool {
214214+ wildCard := false
215215+ if i := strings.Index(pattern, "..."); i >= 0 {
216216+ wildCard = true
217217+ pattern = pattern[:i]
218218+ }
219219+ return func(name string) bool {
220220+ return len(name) <= len(pattern) && hasPathPrefix(pattern, name) ||
221221+ wildCard && strings.HasPrefix(name, pattern)
222222+ }
223223+}
224224+225225+// matchPattern(pattern)(name) reports whether
226226+// name matches pattern. Pattern is a limited glob
227227+// pattern in which '...' means 'any string' and there
228228+// is no other special syntax.
229229+// Unfortunately, there are two special cases. Quoting "go help packages":
230230+//
231231+// First, /... at the end of the pattern can match an empty string,
232232+// so that net/... matches both net and packages in its subdirectories, like net/http.
233233+// Second, any slash-separted pattern element containing a wildcard never
234234+// participates in a match of the "vendor" element in the path of a vendored
235235+// package, so that ./... does not match packages in subdirectories of
236236+// ./vendor or ./mycode/vendor, but ./vendor/... and ./mycode/vendor/... do.
237237+// Note, however, that a directory named vendor that itself contains code
238238+// is not a vendored package: cmd/vendor would be a command named vendor,
239239+// and the pattern cmd/... matches it.
240240+func matchPattern(pattern string) func(name string) bool {
241241+ // Convert pattern to regular expression.
242242+ // The strategy for the trailing /... is to nest it in an explicit ? expression.
243243+ // The strategy for the vendor exclusion is to change the unmatchable
244244+ // vendor strings to a disallowed code point (vendorChar) and to use
245245+ // "(anything but that codepoint)*" as the implementation of the ... wildcard.
246246+ // This is a bit complicated but the obvious alternative,
247247+ // namely a hand-written search like in most shell glob matchers,
248248+ // is too easy to make accidentally exponential.
249249+ // Using package regexp guarantees linear-time matching.
250250+251251+ const vendorChar = "\x00"
252252+253253+ if strings.Contains(pattern, vendorChar) {
254254+ return func(name string) bool { return false }
255255+ }
256256+257257+ re := regexp.QuoteMeta(pattern)
258258+ re = replaceVendor(re, vendorChar)
259259+ switch {
260260+ case strings.HasSuffix(re, `/`+vendorChar+`/\.\.\.`):
261261+ re = strings.TrimSuffix(re, `/`+vendorChar+`/\.\.\.`) + `(/vendor|/` + vendorChar + `/\.\.\.)`
262262+ case re == vendorChar+`/\.\.\.`:
263263+ re = `(/vendor|/` + vendorChar + `/\.\.\.)`
264264+ case strings.HasSuffix(re, `/\.\.\.`):
265265+ re = strings.TrimSuffix(re, `/\.\.\.`) + `(/\.\.\.)?`
266266+ }
267267+ re = strings.Replace(re, `\.\.\.`, `[^`+vendorChar+`]*`, -1)
268268+269269+ reg := regexp.MustCompile(`^` + re + `$`)
270270+271271+ return func(name string) bool {
272272+ if strings.Contains(name, vendorChar) {
273273+ return false
274274+ }
275275+ return reg.MatchString(replaceVendor(name, vendorChar))
276276+ }
277277+}
278278+279279+// replaceVendor returns the result of replacing
280280+// non-trailing vendor path elements in x with repl.
281281+func replaceVendor(x, repl string) string {
282282+ if !strings.Contains(x, "vendor") {
283283+ return x
284284+ }
285285+ elem := strings.Split(x, "/")
286286+ for i := 0; i < len(elem)-1; i++ {
287287+ if elem[i] == "vendor" {
288288+ elem[i] = repl
289289+ }
290290+ }
291291+ return strings.Join(elem, "/")
292292+}
293293+294294+// warnUnmatched warns about patterns that didn't match any packages.
295295+func warnUnmatched(matches []*match) {
296296+ for _, m := range matches {
297297+ if len(m.Pkgs) == 0 {
298298+ m.Err =
299299+ fmt.Errorf("cue: %q matched no packages\n", m.Pattern)
300300+ }
301301+ }
302302+}
303303+304304+// importPaths returns the matching paths to use for the given command line.
305305+// It calls ImportPathsQuiet and then WarnUnmatched.
306306+func (l *loader) importPaths(patterns []string) []*match {
307307+ matches := l.importPathsQuiet(patterns)
308308+ warnUnmatched(matches)
309309+ return matches
310310+}
311311+312312+// importPathsQuiet is like ImportPaths but does not warn about patterns with no matches.
313313+func (l *loader) importPathsQuiet(patterns []string) []*match {
314314+ var out []*match
315315+ for _, a := range cleanPatterns(patterns) {
316316+ if isMetaPackage(a) {
317317+ out = append(out, l.matchPackages(a))
318318+ continue
319319+ }
320320+ if strings.Contains(a, "...") {
321321+ if isLocalImport(a) {
322322+ out = append(out, l.matchPackagesInFS(a))
323323+ } else {
324324+ out = append(out, l.matchPackages(a))
325325+ }
326326+ continue
327327+ }
328328+329329+ pkg := l.importPkg(a, l.cfg.Dir)
330330+ out = append(out, &match{Pattern: a, Literal: true, Pkgs: []*build.Instance{pkg}})
331331+ }
332332+ return out
333333+}
334334+335335+// cleanPatterns returns the patterns to use for the given
336336+// command line. It canonicalizes the patterns but does not
337337+// evaluate any matches.
338338+func cleanPatterns(patterns []string) []string {
339339+ if len(patterns) == 0 {
340340+ return []string{"."}
341341+ }
342342+ var out []string
343343+ for _, a := range patterns {
344344+ // Arguments are supposed to be import paths, but
345345+ // as a courtesy to Windows developers, rewrite \ to /
346346+ // in command-line arguments. Handles .\... and so on.
347347+ if filepath.Separator == '\\' {
348348+ a = strings.Replace(a, `\`, `/`, -1)
349349+ }
350350+351351+ // Put argument in canonical form, but preserve leading ./.
352352+ if strings.HasPrefix(a, "./") {
353353+ a = "./" + path.Clean(a)
354354+ if a == "./." {
355355+ a = "."
356356+ }
357357+ } else {
358358+ a = path.Clean(a)
359359+ }
360360+ out = append(out, a)
361361+ }
362362+ return out
363363+}
364364+365365+// isMetaPackage checks if name is a reserved package name that expands to multiple packages.
366366+func isMetaPackage(name string) bool {
367367+ return name == "std" || name == "cmd" || name == "all"
368368+}
369369+370370+// hasPathPrefix reports whether the path s begins with the
371371+// elements in prefix.
372372+func hasPathPrefix(s, prefix string) bool {
373373+ switch {
374374+ default:
375375+ return false
376376+ case len(s) == len(prefix):
377377+ return s == prefix
378378+ case len(s) > len(prefix):
379379+ if prefix != "" && prefix[len(prefix)-1] == '/' {
380380+ return strings.HasPrefix(s, prefix)
381381+ }
382382+ return s[len(prefix)] == '/' && s[:len(prefix)] == prefix
383383+ }
384384+}
385385+386386+// hasFilepathPrefix reports whether the path s begins with the
387387+// elements in prefix.
388388+func hasFilepathPrefix(s, prefix string) bool {
389389+ switch {
390390+ default:
391391+ return false
392392+ case len(s) == len(prefix):
393393+ return s == prefix
394394+ case len(s) > len(prefix):
395395+ if prefix != "" && prefix[len(prefix)-1] == filepath.Separator {
396396+ return strings.HasPrefix(s, prefix)
397397+ }
398398+ return s[len(prefix)] == filepath.Separator && s[:len(prefix)] == prefix
399399+ }
400400+}
401401+402402+// isStandardImportPath reports whether $GOROOT/src/path should be considered
403403+// part of the standard distribution. For historical reasons we allow people to add
404404+// their own code to $GOROOT instead of using $GOPATH, but we assume that
405405+// code will start with a domain name (dot in the first element).
406406+//
407407+// Note that this function is meant to evaluate whether a directory found in GOROOT
408408+// should be treated as part of the standard library. It should not be used to decide
409409+// that a directory found in GOPATH should be rejected: directories in GOPATH
410410+// need not have dots in the first element, and they just take their chances
411411+// with future collisions in the standard library.
412412+func isStandardImportPath(path string) bool {
413413+ i := strings.Index(path, "/")
414414+ if i < 0 {
415415+ i = len(path)
416416+ }
417417+ elem := path[:i]
418418+ return !strings.Contains(elem, ".")
419419+}
420420+421421+// isRelativePath reports whether pattern should be interpreted as a directory
422422+// path relative to the current directory, as opposed to a pattern matching
423423+// import paths.
424424+func isRelativePath(pattern string) bool {
425425+ return strings.HasPrefix(pattern, "./") || strings.HasPrefix(pattern, "../") || pattern == "." || pattern == ".."
426426+}
427427+428428+// inDir checks whether path is in the file tree rooted at dir.
429429+// If so, inDir returns an equivalent path relative to dir.
430430+// If not, inDir returns an empty string.
431431+// inDir makes some effort to succeed even in the presence of symbolic links.
432432+// TODO(rsc): Replace internal/test.inDir with a call to this function for Go 1.12.
433433+func inDir(path, dir string) string {
434434+ if rel := inDirLex(path, dir); rel != "" {
435435+ return rel
436436+ }
437437+ xpath, err := filepath.EvalSymlinks(path)
438438+ if err != nil || xpath == path {
439439+ xpath = ""
440440+ } else {
441441+ if rel := inDirLex(xpath, dir); rel != "" {
442442+ return rel
443443+ }
444444+ }
445445+446446+ xdir, err := filepath.EvalSymlinks(dir)
447447+ if err == nil && xdir != dir {
448448+ if rel := inDirLex(path, xdir); rel != "" {
449449+ return rel
450450+ }
451451+ if xpath != "" {
452452+ if rel := inDirLex(xpath, xdir); rel != "" {
453453+ return rel
454454+ }
455455+ }
456456+ }
457457+ return ""
458458+}
459459+460460+// inDirLex is like inDir but only checks the lexical form of the file names.
461461+// It does not consider symbolic links.
462462+// TODO(rsc): This is a copy of str.HasFilePathPrefix, modified to
463463+// return the suffix. Most uses of str.HasFilePathPrefix should probably
464464+// be calling InDir instead.
465465+func inDirLex(path, dir string) string {
466466+ pv := strings.ToUpper(filepath.VolumeName(path))
467467+ dv := strings.ToUpper(filepath.VolumeName(dir))
468468+ path = path[len(pv):]
469469+ dir = dir[len(dv):]
470470+ switch {
471471+ default:
472472+ return ""
473473+ case pv != dv:
474474+ return ""
475475+ case len(path) == len(dir):
476476+ if path == dir {
477477+ return "."
478478+ }
479479+ return ""
480480+ case dir == "":
481481+ return path
482482+ case len(path) > len(dir):
483483+ if dir[len(dir)-1] == filepath.Separator {
484484+ if path[:len(dir)] == dir {
485485+ return path[len(dir):]
486486+ }
487487+ return ""
488488+ }
489489+ if path[len(dir)] == filepath.Separator && path[:len(dir)] == dir {
490490+ if len(path) == len(dir)+1 {
491491+ return "."
492492+ }
493493+ return path[len(dir)+1:]
494494+ }
495495+ return ""
496496+ }
497497+}
+175
cue/load/search_test.go
···11+// Copyright 2018 The CUE Authors
22+//
33+// Licensed under the Apache License, Version 2.0 (the "License");
44+// you may not use this file except in compliance with the License.
55+// You may obtain a copy of the License at
66+//
77+// http://www.apache.org/licenses/LICENSE-2.0
88+//
99+// Unless required by applicable law or agreed to in writing, software
1010+// distributed under the License is distributed on an "AS IS" BASIS,
1111+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212+// See the License for the specific language governing permissions and
1313+// limitations under the License.
1414+1515+package load
1616+1717+import (
1818+ "strings"
1919+ "testing"
2020+)
2121+2222+var matchPatternTests = `
2323+ pattern ...
2424+ match foo
2525+2626+ pattern net
2727+ match net
2828+ not net/http
2929+3030+ pattern net/http
3131+ match net/http
3232+ not net
3333+3434+ pattern net...
3535+ match net net/http netchan
3636+ not not/http not/net/http
3737+3838+ # Special cases. Quoting docs:
3939+4040+ # First, /... at the end of the pattern can match an empty string,
4141+ # so that net/... matches both net and packages in its subdirectories, like net/http.
4242+ pattern net/...
4343+ match net net/http
4444+ not not/http not/net/http netchan
4545+4646+ # Second, any slash-separted pattern element containing a wildcard never
4747+ # participates in a match of the "vendor" element in the path of a vendored
4848+ # package, so that ./... does not match packages in subdirectories of
4949+ # ./vendor or ./mycode/vendor, but ./vendor/... and ./mycode/vendor/... do.
5050+ # Note, however, that a directory named vendor that itself contains code
5151+ # is not a vendored package: cmd/vendor would be a command named vendor,
5252+ # and the pattern cmd/... matches it.
5353+ pattern ./...
5454+ match ./vendor ./mycode/vendor
5555+ not ./vendor/foo ./mycode/vendor/foo
5656+5757+ pattern ./vendor/...
5858+ match ./vendor/foo ./vendor/foo/vendor
5959+ not ./vendor/foo/vendor/bar
6060+6161+ pattern mycode/vendor/...
6262+ match mycode/vendor mycode/vendor/foo mycode/vendor/foo/vendor
6363+ not mycode/vendor/foo/vendor/bar
6464+6565+ pattern x/vendor/y
6666+ match x/vendor/y
6767+ not x/vendor
6868+6969+ pattern x/vendor/y/...
7070+ match x/vendor/y x/vendor/y/z x/vendor/y/vendor x/vendor/y/z/vendor
7171+ not x/vendor/y/vendor/z
7272+7373+ pattern .../vendor/...
7474+ match x/vendor/y x/vendor/y/z x/vendor/y/vendor x/vendor/y/z/vendor
7575+`
7676+7777+func TestMatchPattern(t *testing.T) {
7878+ testPatterns(t, "MatchPattern", matchPatternTests, func(pattern, name string) bool {
7979+ return matchPattern(pattern)(name)
8080+ })
8181+}
8282+8383+var treeCanMatchPatternTests = `
8484+ pattern ...
8585+ match foo
8686+8787+ pattern net
8888+ match net
8989+ not net/http
9090+9191+ pattern net/http
9292+ match net net/http
9393+9494+ pattern net...
9595+ match net netchan net/http
9696+ not not/http not/net/http
9797+9898+ pattern net/...
9999+ match net net/http
100100+ not not/http netchan
101101+102102+ pattern abc.../def
103103+ match abcxyz
104104+ not xyzabc
105105+106106+ pattern x/y/z/...
107107+ match x x/y x/y/z x/y/z/w
108108+109109+ pattern x/y/z
110110+ match x x/y x/y/z
111111+ not x/y/z/w
112112+113113+ pattern x/.../y/z
114114+ match x/a/b/c
115115+ not y/x/a/b/c
116116+`
117117+118118+func TestTreeCanMatchPattern(t *testing.T) {
119119+ testPatterns(t, "TreeCanMatchPattern", treeCanMatchPatternTests, func(pattern, name string) bool {
120120+ return treeCanMatchPattern(pattern)(name)
121121+ })
122122+}
123123+124124+var hasPathPrefixTests = []stringPairTest{
125125+ {"abc", "a", false},
126126+ {"a/bc", "a", true},
127127+ {"a", "a", true},
128128+ {"a/bc", "a/", true},
129129+}
130130+131131+func TestHasPathPrefix(t *testing.T) {
132132+ testStringPairs(t, "hasPathPrefix", hasPathPrefixTests, hasPathPrefix)
133133+}
134134+135135+type stringPairTest struct {
136136+ in1 string
137137+ in2 string
138138+ out bool
139139+}
140140+141141+func testStringPairs(t *testing.T, name string, tests []stringPairTest, f func(string, string) bool) {
142142+ for _, tt := range tests {
143143+ if out := f(tt.in1, tt.in2); out != tt.out {
144144+ t.Errorf("%s(%q, %q) = %v, want %v", name, tt.in1, tt.in2, out, tt.out)
145145+ }
146146+ }
147147+}
148148+149149+func testPatterns(t *testing.T, name, tests string, fn func(string, string) bool) {
150150+ var patterns []string
151151+ for _, line := range strings.Split(tests, "\n") {
152152+ if i := strings.Index(line, "#"); i >= 0 {
153153+ line = line[:i]
154154+ }
155155+ f := strings.Fields(line)
156156+ if len(f) == 0 {
157157+ continue
158158+ }
159159+ switch f[0] {
160160+ default:
161161+ t.Fatalf("unknown directive %q", f[0])
162162+ case "pattern":
163163+ patterns = f[1:]
164164+ case "match", "not":
165165+ want := f[0] == "match"
166166+ for _, pattern := range patterns {
167167+ for _, in := range f[1:] {
168168+ if fn(pattern, in) != want {
169169+ t.Errorf("%s(%q, %q) = %v, want %v", name, pattern, in, !want, want)
170170+ }
171171+ }
172172+ }
173173+ }
174174+ }
175175+}
···11+// Copyright 2018 The CUE Authors
22+//
33+// Licensed under the Apache License, Version 2.0 (the "License");
44+// you may not use this file except in compliance with the License.
55+// You may obtain a copy of the License at
66+//
77+// http://www.apache.org/licenses/LICENSE-2.0
88+//
99+// Unless required by applicable law or agreed to in writing, software
1010+// distributed under the License is distributed on an "AS IS" BASIS,
1111+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212+// See the License for the specific language governing permissions and
1313+// limitations under the License.
1414+