this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

cue/load: add package

Change-Id: Ie71f233333ad0a8294cf3cebf4f33d3e19c9200f

+3244
+194
cue/load/config.go
··· 1 + // Copyright 2018 The CUE Authors 2 + // 3 + // Licensed under the Apache License, Version 2.0 (the "License"); 4 + // you may not use this file except in compliance with the License. 5 + // You may obtain a copy of the License at 6 + // 7 + // http://www.apache.org/licenses/LICENSE-2.0 8 + // 9 + // Unless required by applicable law or agreed to in writing, software 10 + // distributed under the License is distributed on an "AS IS" BASIS, 11 + // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 + // See the License for the specific language governing permissions and 13 + // limitations under the License. 14 + 15 + package load 16 + 17 + import ( 18 + "os" 19 + "path/filepath" 20 + "runtime" 21 + 22 + "cuelang.org/go/cue/build" 23 + ) 24 + 25 + const ( 26 + cueSuffix = ".cue" 27 + defaultDir = "cue" 28 + modFile = "cue.mod" 29 + ) 30 + 31 + // FromArgsUsage is a partial usage message that applications calling 32 + // FromArgs may wish to include in their -help output. 33 + // 34 + // Some of the aspects of this documentation, like flags and handling '--' need 35 + // to be implemented by the tools. 36 + const FromArgsUsage = ` 37 + <args> is a list of arguments denoting a set of instances. 38 + It may take one of two forms: 39 + 40 + 1. A list of *.cue source files. 41 + 42 + All of the specified files are loaded, parsed and type-checked 43 + as a single instance. 44 + 45 + 2. A list of relative directories to denote a package instance. 46 + 47 + Each directory matching the pattern is loaded as a separate instance. 48 + The instance contains all files in this directory and ancestor directories, 49 + up to the module root, with the same package name. The package name must 50 + be either uniquely determined by the files in the given directory, or 51 + explicitly defined using the '-p' flag. 52 + 53 + Files without a package clause are ignored. 54 + 55 + Files ending in *_test.cue files are only loaded when testing. 56 + 57 + 3. A list of import paths, each denoting a package. 58 + 59 + The package's directory is loaded from the package cache. The version of the 60 + package is defined in the modules cue.mod file. 61 + 62 + A '--' argument terminates the list of packages. 63 + ` 64 + 65 + // A Config configures load behavior. 66 + type Config struct { 67 + // Context specifies the context for the load operation. 68 + // If the context is cancelled, the loader may stop early 69 + // and return an ErrCancelled error. 70 + // If Context is nil, the load cannot be cancelled. 71 + Context *build.Context 72 + 73 + loader *loader 74 + 75 + modRoot string // module root for package paths ("" if unknown) 76 + 77 + // cache specifies the package cache in which to look for packages. 78 + cache string 79 + 80 + // Package defines the name of the package to be loaded. In this is not set, 81 + // the package must be uniquely defined from its context. 82 + Package string 83 + 84 + // Dir is the directory in which to run the build system's query tool 85 + // that provides information about the packages. 86 + // If Dir is empty, the tool is run in the current directory. 87 + Dir string 88 + 89 + // The build and release tags specify build constraints that should be 90 + // considered satisfied when processing +build lines. Clients creating a new 91 + // context may customize BuildTags, which defaults to empty, but it is 92 + // usually an error to customize ReleaseTags, which defaults to the list of 93 + // CUE releases the current release is compatible with. 94 + BuildTags []string 95 + releaseTags []string 96 + 97 + // If Tests is set, the loader includes not just the packages 98 + // matching a particular pattern but also any related test packages. 99 + Tests bool 100 + 101 + // If Tools is set, the loader includes tool files associated with 102 + // a package. 103 + Tools bool 104 + 105 + // If DataFiles is set, the loader includes entries for directories that 106 + // have no CUE files, but have recognized data files that could be converted 107 + // to CUE. 108 + DataFiles bool 109 + 110 + fileSystem 111 + } 112 + 113 + func (c Config) newInstance(path string) *build.Instance { 114 + i := c.Context.NewInstance(path, nil) 115 + i.DisplayPath = path 116 + return i 117 + } 118 + 119 + func (c Config) newErrInstance(m *match, path string, err error) *build.Instance { 120 + i := c.Context.NewInstance(path, nil) 121 + i.DisplayPath = path 122 + i.ReportError(err) 123 + return i 124 + } 125 + 126 + func (c Config) complete() (cfg *Config, err error) { 127 + // Each major CUE release should add a tag here. 128 + // Old tags should not be removed. That is, the cue1.x tag is present 129 + // in all releases >= CUE 1.x. Code that requires CUE 1.x or later should 130 + // say "+build cue1.x", and code that should only be built before CUE 1.x 131 + // (perhaps it is the stub to use in that case) should say "+build !cue1.x". 132 + c.releaseTags = []string{"cue0.1"} 133 + 134 + if c.Dir == "" { 135 + c.Dir, err = os.Getwd() 136 + if err != nil { 137 + return nil, err 138 + } 139 + } 140 + 141 + c.loader = &loader{cfg: &c} 142 + 143 + if c.Context == nil { 144 + c.Context = build.NewContext(build.Loader(c.loader.loadFunc(c.Dir))) 145 + } 146 + 147 + if c.cache == "" { 148 + c.cache = filepath.Join(home(), defaultDir) 149 + // os.MkdirAll(c.Cache, 0755) // TODO: tools task 150 + } 151 + 152 + // TODO: determine root on a package basis. Maybe we even need a 153 + // pkgname.cue.mod 154 + // Look to see if there is a cue.mod. 155 + if c.modRoot == "" { 156 + abs, err := findRoot(c.Dir) 157 + if err != nil { 158 + // Not using modules: only consider the current directory. 159 + c.modRoot = c.Dir 160 + } else { 161 + c.modRoot = abs 162 + } 163 + } 164 + return &c, nil 165 + } 166 + 167 + func findRoot(dir string) (string, error) { 168 + abs, err := filepath.Abs(dir) 169 + if err != nil { 170 + return "", err 171 + } 172 + for { 173 + info, err := os.Stat(filepath.Join(abs, modFile)) 174 + if err == nil && !info.IsDir() { 175 + break 176 + } 177 + d := filepath.Dir(abs) 178 + if len(d) >= len(abs) { 179 + return "", err // reached top of file system, no cue.mod 180 + } 181 + abs = d 182 + } 183 + return abs, nil 184 + } 185 + 186 + func home() string { 187 + env := "HOME" 188 + if runtime.GOOS == "windows" { 189 + env = "USERPROFILE" 190 + } else if runtime.GOOS == "plan9" { 191 + env = "home" 192 + } 193 + return os.Getenv(env) 194 + }
+16
cue/load/doc.go
··· 1 + // Copyright 2018 The CUE Authors 2 + // 3 + // Licensed under the Apache License, Version 2.0 (the "License"); 4 + // you may not use this file except in compliance with the License. 5 + // You may obtain a copy of the License at 6 + // 7 + // http://www.apache.org/licenses/LICENSE-2.0 8 + // 9 + // Unless required by applicable law or agreed to in writing, software 10 + // distributed under the License is distributed on an "AS IS" BASIS, 11 + // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 + // See the License for the specific language governing permissions and 13 + // limitations under the License. 14 + 15 + // Package load loads CUE instances. 16 + package load // import "cuelang.org/go/cue/load"
+150
cue/load/errors.go
··· 1 + // Copyright 2018 The CUE Authors 2 + // 3 + // Licensed under the Apache License, Version 2.0 (the "License"); 4 + // you may not use this file except in compliance with the License. 5 + // You may obtain a copy of the License at 6 + // 7 + // http://www.apache.org/licenses/LICENSE-2.0 8 + // 9 + // Unless required by applicable law or agreed to in writing, software 10 + // distributed under the License is distributed on an "AS IS" BASIS, 11 + // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 + // See the License for the specific language governing permissions and 13 + // limitations under the License. 14 + 15 + package load 16 + 17 + import ( 18 + "fmt" 19 + "path/filepath" 20 + "strings" 21 + 22 + build "cuelang.org/go/cue/build" 23 + "cuelang.org/go/cue/token" 24 + ) 25 + 26 + func lastError(p *build.Instance) *packageError { 27 + if p == nil { 28 + return nil 29 + } 30 + switch v := p.Err.(type) { 31 + case *packageError: 32 + return v 33 + } 34 + return nil 35 + } 36 + 37 + func report(p *build.Instance, err *packageError) { 38 + if err != nil { 39 + p.ReportError(err) 40 + } 41 + } 42 + 43 + // shortPath returns an absolute or relative name for path, whatever is shorter. 44 + func shortPath(cwd, path string) string { 45 + if cwd == "" { 46 + return path 47 + } 48 + if rel, err := filepath.Rel(cwd, path); err == nil && len(rel) < len(path) { 49 + return rel 50 + } 51 + return path 52 + } 53 + 54 + // A packageError describes an error loading information about a package. 55 + type packageError struct { 56 + ImportStack []string // shortest path from package named on command line to this one 57 + Pos string // position of error 58 + Err string // the error itself 59 + IsImportCycle bool `json:"-"` // the error is an import cycle 60 + Hard bool `json:"-"` // whether the error is soft or hard; soft errors are ignored in some places 61 + } 62 + 63 + func (l *loader) errPkgf(importPos []token.Position, format string, args ...interface{}) *packageError { 64 + err := &packageError{ 65 + ImportStack: l.stk.Copy(), 66 + Err: fmt.Sprintf(format, args...), 67 + } 68 + err.fillPos(l.cfg.Dir, importPos) 69 + return err 70 + } 71 + 72 + func (p *packageError) fillPos(cwd string, positions []token.Position) { 73 + if len(positions) > 0 && p.Pos == "" { 74 + pos := positions[0] 75 + pos.Filename = shortPath(cwd, pos.Filename) 76 + p.Pos = pos.String() 77 + } 78 + } 79 + 80 + func (p *packageError) Error() string { 81 + // Import cycles deserve special treatment. 82 + if p.IsImportCycle { 83 + return fmt.Sprintf("%s\npackage %s\n", p.Err, strings.Join(p.ImportStack, "\n\timports ")) 84 + } 85 + if p.Pos != "" { 86 + // Omit import stack. The full path to the file where the error 87 + // is the most important thing. 88 + return p.Pos + ": " + p.Err 89 + } 90 + if len(p.ImportStack) == 0 { 91 + return p.Err 92 + } 93 + return "package " + strings.Join(p.ImportStack, "\n\timports ") + ": " + p.Err 94 + } 95 + 96 + // noCUEError is the error used by Import to describe a directory 97 + // containing no buildable Go source files. (It may still contain 98 + // test files, files hidden by build tags, and so on.) 99 + type noCUEError struct { 100 + Package *build.Instance 101 + 102 + Dir string 103 + Ignored bool // whether any Go files were ignored due to build tags 104 + } 105 + 106 + // func (e *noCUEError) Error() string { 107 + // msg := "no buildable CUE config files in " + e.Dir 108 + // if e.Ignored { 109 + // msg += " (.cue files ignored due to build tags)" 110 + // } 111 + // return msg 112 + // } 113 + 114 + func (e *noCUEError) Error() string { 115 + // Count files beginning with _ and ., which we will pretend don't exist at all. 116 + dummy := 0 117 + for _, name := range e.Package.IgnoredCUEFiles { 118 + if strings.HasPrefix(name, "_") || strings.HasPrefix(name, ".") { 119 + dummy++ 120 + } 121 + } 122 + 123 + // path := shortPath(e.Package.Root, e.Package.Dir) 124 + path := e.Package.DisplayPath 125 + 126 + if len(e.Package.IgnoredCUEFiles) > dummy { 127 + // CUE files exist, but they were ignored due to build constraints. 128 + return "build constraints exclude all CUE files in " + path 129 + } 130 + // if len(e.Package.TestCUEFiles) > 0 { 131 + // // Test CUE files exist, but we're not interested in them. 132 + // // The double-negative is unfortunate but we want e.Package.Dir 133 + // // to appear at the end of error message. 134 + // return "no non-test CUE files in " + e.Package.Dir 135 + // } 136 + return "no CUE files in " + path 137 + } 138 + 139 + // multiplePackageError describes a directory containing 140 + // multiple buildable Go source files for multiple packages. 141 + type multiplePackageError struct { 142 + Dir string // directory containing files 143 + Packages []string // package names found 144 + Files []string // corresponding files: Files[i] declares package Packages[i] 145 + } 146 + 147 + func (e *multiplePackageError) Error() string { 148 + // Error string limited to two entries for compatibility. 149 + 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) 150 + }
+173
cue/load/fs.go
··· 1 + // Copyright 2018 The CUE Authors 2 + // 3 + // Licensed under the Apache License, Version 2.0 (the "License"); 4 + // you may not use this file except in compliance with the License. 5 + // You may obtain a copy of the License at 6 + // 7 + // http://www.apache.org/licenses/LICENSE-2.0 8 + // 9 + // Unless required by applicable law or agreed to in writing, software 10 + // distributed under the License is distributed on an "AS IS" BASIS, 11 + // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 + // See the License for the specific language governing permissions and 13 + // limitations under the License. 14 + 15 + package load 16 + 17 + import ( 18 + "io" 19 + "io/ioutil" 20 + "os" 21 + "path/filepath" 22 + "strings" 23 + ) 24 + 25 + // TODO: remove this file if we know we don't need it. 26 + 27 + // A fileSystem specifies the supporting context for a build. 28 + type fileSystem struct { 29 + // By default, Import uses the operating system's file system calls 30 + // to read directories and files. To read from other sources, 31 + // callers can set the following functions. They all have default 32 + // behaviors that use the local file system, so clients need only set 33 + // the functions whose behaviors they wish to change. 34 + 35 + // JoinPath joins the sequence of path fragments into a single path. 36 + // If JoinPath is nil, Import uses filepath.Join. 37 + JoinPath func(elem ...string) string 38 + 39 + // SplitPathList splits the path list into a slice of individual paths. 40 + // If SplitPathList is nil, Import uses filepath.SplitList. 41 + SplitPathList func(list string) []string 42 + 43 + // IsAbsPath reports whether path is an absolute path. 44 + // If IsAbsPath is nil, Import uses filepath.IsAbs. 45 + IsAbsPath func(path string) bool 46 + 47 + // IsDir reports whether the path names a directory. 48 + // If IsDir is nil, Import calls os.Stat and uses the result's IsDir method. 49 + IsDir func(path string) bool 50 + 51 + // HasSubdir reports whether dir is a subdirectory of 52 + // (perhaps multiple levels below) root. 53 + // If so, HasSubdir sets rel to a slash-separated path that 54 + // can be joined to root to produce a path equivalent to dir. 55 + // If HasSubdir is nil, Import uses an implementation built on 56 + // filepath.EvalSymlinks. 57 + HasSubdir func(root, dir string) (rel string, ok bool) 58 + 59 + // ReadDir returns a slice of os.FileInfo, sorted by Name, 60 + // describing the content of the named directory. 61 + // If ReadDir is nil, Import uses ioutil.ReadDir. 62 + ReadDir func(dir string) ([]os.FileInfo, error) 63 + 64 + // OpenFile opens a file (not a directory) for reading. 65 + // If OpenFile is nil, Import uses os.Open. 66 + OpenFile func(path string) (io.ReadCloser, error) 67 + } 68 + 69 + // JoinPath calls ctxt.JoinPath (if not nil) or else filepath.Join. 70 + func (ctxt *fileSystem) joinPath(elem ...string) string { 71 + if f := ctxt.JoinPath; f != nil { 72 + return f(elem...) 73 + } 74 + return filepath.Join(elem...) 75 + } 76 + 77 + // splitPathList calls ctxt.SplitPathList (if not nil) or else filepath.SplitList. 78 + func (ctxt *fileSystem) splitPathList(s string) []string { 79 + if f := ctxt.SplitPathList; f != nil { 80 + return f(s) 81 + } 82 + return filepath.SplitList(s) 83 + } 84 + 85 + // isAbsPath calls ctxt.IsAbsPath (if not nil) or else filepath.IsAbs. 86 + func (ctxt *fileSystem) isAbsPath(path string) bool { 87 + if f := ctxt.IsAbsPath; f != nil { 88 + return f(path) 89 + } 90 + return filepath.IsAbs(path) 91 + } 92 + 93 + // isDir calls ctxt.IsDir (if not nil) or else uses os.Stat. 94 + func (ctxt *fileSystem) isDir(path string) bool { 95 + if f := ctxt.IsDir; f != nil { 96 + return f(path) 97 + } 98 + fi, err := os.Stat(path) 99 + return err == nil && fi.IsDir() 100 + } 101 + 102 + // hasSubdir calls ctxt.HasSubdir (if not nil) or else uses 103 + // the local file system to answer the question. 104 + func (ctxt *fileSystem) hasSubdir(root, dir string) (rel string, ok bool) { 105 + if f := ctxt.HasSubdir; f != nil { 106 + return f(root, dir) 107 + } 108 + 109 + // Try using paths we received. 110 + if rel, ok = hasSubdir(root, dir); ok { 111 + return 112 + } 113 + 114 + // Try expanding symlinks and comparing 115 + // expanded against unexpanded and 116 + // expanded against expanded. 117 + rootSym, _ := filepath.EvalSymlinks(root) 118 + dirSym, _ := filepath.EvalSymlinks(dir) 119 + 120 + if rel, ok = hasSubdir(rootSym, dir); ok { 121 + return 122 + } 123 + if rel, ok = hasSubdir(root, dirSym); ok { 124 + return 125 + } 126 + return hasSubdir(rootSym, dirSym) 127 + } 128 + 129 + func hasSubdir(root, dir string) (rel string, ok bool) { 130 + const sep = string(filepath.Separator) 131 + root = filepath.Clean(root) 132 + if !strings.HasSuffix(root, sep) { 133 + root += sep 134 + } 135 + dir = filepath.Clean(dir) 136 + if !strings.HasPrefix(dir, root) { 137 + return "", false 138 + } 139 + return filepath.ToSlash(dir[len(root):]), true 140 + } 141 + 142 + // ReadDir calls ctxt.ReadDir (if not nil) or else ioutil.ReadDir. 143 + func (ctxt *fileSystem) readDir(path string) ([]os.FileInfo, error) { 144 + if f := ctxt.ReadDir; f != nil { 145 + return f(path) 146 + } 147 + return ioutil.ReadDir(path) 148 + } 149 + 150 + // openFile calls ctxt.OpenFile (if not nil) or else os.Open. 151 + func (ctxt *fileSystem) openFile(path string) (io.ReadCloser, error) { 152 + if fn := ctxt.OpenFile; fn != nil { 153 + return fn(path) 154 + } 155 + 156 + f, err := os.Open(path) 157 + if err != nil { 158 + return nil, err // nil interface 159 + } 160 + return f, nil 161 + } 162 + 163 + // isFile determines whether path is a file by trying to open it. 164 + // It reuses openFile instead of adding another function to the 165 + // list in Context. 166 + func (ctxt *fileSystem) isFile(path string) bool { 167 + f, err := ctxt.openFile(path) 168 + if err != nil { 169 + return false 170 + } 171 + f.Close() 172 + return true 173 + }
+570
cue/load/import.go
··· 1 + // Copyright 2018 The CUE Authors 2 + // 3 + // Licensed under the Apache License, Version 2.0 (the "License"); 4 + // you may not use this file except in compliance with the License. 5 + // You may obtain a copy of the License at 6 + // 7 + // http://www.apache.org/licenses/LICENSE-2.0 8 + // 9 + // Unless required by applicable law or agreed to in writing, software 10 + // distributed under the License is distributed on an "AS IS" BASIS, 11 + // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 + // See the License for the specific language governing permissions and 13 + // limitations under the License. 14 + 15 + package load 16 + 17 + import ( 18 + "bytes" 19 + "fmt" 20 + "log" 21 + pathpkg "path" 22 + "path/filepath" 23 + "sort" 24 + "strconv" 25 + "strings" 26 + "unicode" 27 + "unicode/utf8" 28 + 29 + "cuelang.org/go/cue/ast" 30 + build "cuelang.org/go/cue/build" 31 + "cuelang.org/go/cue/encoding" 32 + "cuelang.org/go/cue/parser" 33 + "cuelang.org/go/cue/token" 34 + ) 35 + 36 + // An importMode controls the behavior of the Import method. 37 + type importMode uint 38 + 39 + const ( 40 + // If findOnly is set, Import stops after locating the directory 41 + // that should contain the sources for a package. It does not 42 + // read any files in the directory. 43 + findOnly importMode = 1 << iota 44 + 45 + // If importComment is set, parse import comments on package statements. 46 + // Import returns an error if it finds a comment it cannot understand 47 + // or finds conflicting comments in multiple source files. 48 + // See golang.org/s/go14customimport for more information. 49 + importComment 50 + 51 + allowAnonymous 52 + ) 53 + 54 + // importPkg returns details about the CUE package named by the import path, 55 + // interpreting local import paths relative to the srcDir directory. 56 + // If the path is a local import path naming a package that can be imported 57 + // using a standard import path, the returned package will set p.ImportPath 58 + // to that path. 59 + // 60 + // In the directory and ancestor directories up to including one with a 61 + // cue.mod file, all .cue files are considered part of the package except for: 62 + // 63 + // - files starting with _ or . (likely editor temporary files) 64 + // - files with build constraints not satisfied by the context 65 + // 66 + // If an error occurs, importPkg sets the error in the returned instance, 67 + // which then may contain partial information. 68 + // 69 + func (l *loader) importPkg(path, srcDir string) *build.Instance { 70 + l.stk.Push(path) 71 + defer l.stk.Pop() 72 + 73 + cfg := l.cfg 74 + ctxt := &cfg.fileSystem 75 + 76 + parentPath := path 77 + if isLocalImport(path) { 78 + parentPath = filepath.Join(srcDir, path) 79 + } 80 + p := cfg.Context.NewInstance(path, l.loadFunc(parentPath)) 81 + p.DisplayPath = path 82 + 83 + isLocal := isLocalImport(path) 84 + var modDir string 85 + // var modErr error 86 + if !isLocal { 87 + // TODO(mpvl): support module lookup 88 + } 89 + 90 + p.Local = isLocal 91 + 92 + if err := updateDirs(cfg, p, path, srcDir, 0); err != nil { 93 + p.ReportError(err) 94 + return p 95 + } 96 + 97 + if modDir == "" && path != cleanImport(path) { 98 + report(p, l.errPkgf(nil, 99 + "non-canonical import path: %q should be %q", path, pathpkg.Clean(path))) 100 + p.Incomplete = true 101 + return p 102 + } 103 + 104 + fp := newFileProcessor(cfg, p) 105 + 106 + root := p.Dir 107 + 108 + for dir := p.Dir; ctxt.isDir(dir); { 109 + files, err := ctxt.readDir(dir) 110 + if err != nil { 111 + p.ReportError(err) 112 + return p 113 + } 114 + rootFound := false 115 + for _, f := range files { 116 + if f.IsDir() { 117 + continue 118 + } 119 + if fp.add(dir, f.Name(), importComment) { 120 + root = dir 121 + } 122 + if f.Name() == "cue.mod" { 123 + root = dir 124 + rootFound = true 125 + } 126 + } 127 + 128 + if rootFound || dir == p.Root || fp.pkg.PkgName == "" { 129 + break 130 + } 131 + 132 + // From now on we just ignore files that do not belong to the same 133 + // package. 134 + fp.ignoreOther = true 135 + 136 + parent, _ := filepath.Split(filepath.Clean(dir)) 137 + if parent == dir { 138 + break 139 + } 140 + dir = parent 141 + } 142 + 143 + rewriteFiles(p, root, false) 144 + if err := fp.finalize(); err != nil { 145 + p.ReportError(err) 146 + return p 147 + } 148 + 149 + for _, f := range p.CUEFiles { 150 + if !filepath.IsAbs(f) { 151 + f = filepath.Join(root, f) 152 + } 153 + p.AddFile(f, nil) 154 + } 155 + p.Complete() 156 + return p 157 + } 158 + 159 + // loadFunc creates a LoadFunc that can be used to create new build.Instances. 160 + func (l *loader) loadFunc(parentPath string) build.LoadFunc { 161 + 162 + return func(path string) *build.Instance { 163 + cfg := l.cfg 164 + 165 + // TODO: HACK: for now we don't handle any imports that are not 166 + // relative paths. 167 + if !isLocalImport(path) { 168 + return nil 169 + } 170 + 171 + if strings.Contains(path, "@") { 172 + i := cfg.newInstance(path) 173 + report(i, l.errPkgf(nil, 174 + "can only use path@version syntax with 'cue get'")) 175 + return i 176 + } 177 + 178 + return l.importPkg(path, parentPath) 179 + } 180 + } 181 + 182 + func updateDirs(c *Config, p *build.Instance, path, srcDir string, mode importMode) error { 183 + ctxt := &c.fileSystem 184 + // path := p.ImportPath 185 + if path == "" { 186 + return fmt.Errorf("import %q: invalid import path", path) 187 + } 188 + 189 + if isLocalImport(path) { 190 + if srcDir == "" { 191 + return fmt.Errorf("import %q: import relative to unknown directory", path) 192 + } 193 + if !ctxt.isAbsPath(path) { 194 + p.Dir = ctxt.joinPath(srcDir, path) 195 + } 196 + return nil 197 + } 198 + 199 + if strings.HasPrefix(path, "/") { 200 + return fmt.Errorf("import %q: cannot import absolute path", path) 201 + } 202 + 203 + // TODO: Lookup the import in dir "pkg" at the module root. 204 + 205 + // package was not found 206 + return fmt.Errorf("cannot find package %q", path) 207 + } 208 + 209 + func normPrefix(root, path string, isLocal bool) string { 210 + root = filepath.Clean(root) 211 + prefix := "" 212 + if isLocal { 213 + prefix = "." + string(filepath.Separator) 214 + } 215 + if !strings.HasSuffix(root, string(filepath.Separator)) && 216 + strings.HasPrefix(path, root) { 217 + path = prefix + path[len(root)+1:] 218 + } 219 + return path 220 + } 221 + 222 + func rewriteFiles(p *build.Instance, root string, isLocal bool) { 223 + p.Root = root 224 + for i, path := range p.CUEFiles { 225 + p.CUEFiles[i] = normPrefix(root, path, isLocal) 226 + sortParentsFirst(p.CUEFiles) 227 + } 228 + for i, path := range p.TestCUEFiles { 229 + p.TestCUEFiles[i] = normPrefix(root, path, isLocal) 230 + sortParentsFirst(p.TestCUEFiles) 231 + } 232 + for i, path := range p.ToolCUEFiles { 233 + p.ToolCUEFiles[i] = normPrefix(root, path, isLocal) 234 + sortParentsFirst(p.ToolCUEFiles) 235 + } 236 + for i, path := range p.IgnoredCUEFiles { 237 + if strings.HasPrefix(path, root) { 238 + p.IgnoredCUEFiles[i] = normPrefix(root, path, isLocal) 239 + } 240 + } 241 + for i, path := range p.InvalidCUEFiles { 242 + p.InvalidCUEFiles[i] = normPrefix(root, path, isLocal) 243 + sortParentsFirst(p.InvalidCUEFiles) 244 + } 245 + } 246 + 247 + func sortParentsFirst(s []string) { 248 + sort.Slice(s, func(i, j int) bool { 249 + return len(filepath.Dir(s[i])) < len(filepath.Dir(s[j])) 250 + }) 251 + } 252 + 253 + type fileProcessor struct { 254 + firstFile string 255 + firstCommentFile string 256 + imported map[string][]token.Position 257 + allTags map[string]bool 258 + allFiles bool 259 + ignoreOther bool // ignore files from other packages 260 + 261 + c *Config 262 + pkg *build.Instance 263 + 264 + err error 265 + } 266 + 267 + func newFileProcessor(c *Config, p *build.Instance) *fileProcessor { 268 + return &fileProcessor{ 269 + imported: make(map[string][]token.Position), 270 + allTags: make(map[string]bool), 271 + c: c, 272 + pkg: p, 273 + } 274 + } 275 + 276 + func (fp *fileProcessor) finalize() error { 277 + p := fp.pkg 278 + if fp.err != nil { 279 + return fp.err 280 + } 281 + if len(p.CUEFiles) == 0 && !fp.c.DataFiles { 282 + return &noCUEError{Package: p, Dir: p.Dir, Ignored: len(p.IgnoredCUEFiles) > 0} 283 + } 284 + 285 + for tag := range fp.allTags { 286 + p.AllTags = append(p.AllTags, tag) 287 + } 288 + sort.Strings(p.AllTags) 289 + 290 + p.ImportPaths, _ = cleanImports(fp.imported) 291 + 292 + return nil 293 + } 294 + 295 + func (fp *fileProcessor) add(root, path string, mode importMode) (added bool) { 296 + fullPath := path 297 + if !filepath.IsAbs(path) { 298 + fullPath = filepath.Join(root, path) 299 + } 300 + name := filepath.Base(fullPath) 301 + dir := filepath.Dir(fullPath) 302 + 303 + fset := token.NewFileSet() 304 + ext := nameExt(name) 305 + p := fp.pkg 306 + 307 + badFile := func(err error) bool { 308 + if fp.err == nil { 309 + fp.err = err 310 + } 311 + p.InvalidCUEFiles = append(p.InvalidCUEFiles, fullPath) 312 + return true 313 + } 314 + 315 + match, data, filename, err := matchFile(fp.c, dir, name, true, fp.allFiles, fp.allTags) 316 + if err != nil { 317 + return badFile(err) 318 + } 319 + if !match { 320 + if ext == cueSuffix { 321 + p.IgnoredCUEFiles = append(p.IgnoredCUEFiles, fullPath) 322 + } else if encoding.MapExtension(ext) != nil { 323 + p.DataFiles = append(p.DataFiles, fullPath) 324 + } 325 + return false // don't mark as added 326 + } 327 + 328 + pf, err := parser.ParseFile(fset, filename, data, parser.ImportsOnly, parser.ParseComments) 329 + if err != nil { 330 + return badFile(err) 331 + } 332 + 333 + pkg := "" 334 + if pf.Name != nil { 335 + pkg = pf.Name.Name 336 + } 337 + if pkg == "" && mode&allowAnonymous == 0 { 338 + p.IgnoredCUEFiles = append(p.IgnoredCUEFiles, fullPath) 339 + return false // don't mark as added 340 + } 341 + 342 + if fp.c.Package != "" { 343 + if pkg != fp.c.Package { 344 + if fp.ignoreOther { 345 + p.IgnoredCUEFiles = append(p.IgnoredCUEFiles, fullPath) 346 + return false 347 + } 348 + // TODO: package does not conform with requested. 349 + return badFile(fmt.Errorf("%s: found package %q; want %q", filename, pkg, fp.c.Package)) 350 + } 351 + } else if fp.firstFile == "" { 352 + p.PkgName = pkg 353 + fp.firstFile = name 354 + } else if pkg != p.PkgName { 355 + if fp.ignoreOther { 356 + p.IgnoredCUEFiles = append(p.IgnoredCUEFiles, fullPath) 357 + return false 358 + } 359 + return badFile(&multiplePackageError{ 360 + Dir: p.Dir, 361 + Packages: []string{p.PkgName, pkg}, 362 + Files: []string{fp.firstFile, name}, 363 + }) 364 + } 365 + 366 + isTest := strings.HasSuffix(name, "_test"+cueSuffix) 367 + isTool := strings.HasSuffix(name, "_tool"+cueSuffix) 368 + 369 + if mode&importComment != 0 { 370 + qcom, line := findimportComment(data) 371 + if line != 0 { 372 + com, err := strconv.Unquote(qcom) 373 + if err != nil { 374 + badFile(fmt.Errorf("%s:%d: cannot parse import comment", filename, line)) 375 + } else if p.ImportComment == "" { 376 + p.ImportComment = com 377 + fp.firstCommentFile = name 378 + } else if p.ImportComment != com { 379 + badFile(fmt.Errorf("found import comments %q (%s) and %q (%s) in %s", p.ImportComment, fp.firstCommentFile, com, name, p.Dir)) 380 + } 381 + } 382 + } 383 + 384 + for _, decl := range pf.Decls { 385 + d, ok := decl.(*ast.ImportDecl) 386 + if !ok { 387 + continue 388 + } 389 + for _, spec := range d.Specs { 390 + quoted := spec.Path.Value 391 + path, err := strconv.Unquote(quoted) 392 + if err != nil { 393 + log.Panicf("%s: parser returned invalid quoted string: <%s>", filename, quoted) 394 + } 395 + if !isTest || fp.c.Tests { 396 + fp.imported[path] = append(fp.imported[path], fset.Position(spec.Pos())) 397 + } 398 + } 399 + } 400 + switch { 401 + case isTest: 402 + p.TestCUEFiles = append(p.TestCUEFiles, fullPath) 403 + case isTool: 404 + p.ToolCUEFiles = append(p.TestCUEFiles, fullPath) 405 + default: 406 + p.CUEFiles = append(p.CUEFiles, fullPath) 407 + } 408 + return true 409 + } 410 + 411 + func nameExt(name string) string { 412 + i := strings.LastIndex(name, ".") 413 + if i < 0 { 414 + return "" 415 + } 416 + return name[i:] 417 + } 418 + 419 + // hasCUEFiles reports whether dir contains any files with names ending in .go. 420 + // For a vendor check we must exclude directories that contain no .go files. 421 + // Otherwise it is not possible to vendor just a/b/c and still import the 422 + // non-vendored a/b. See golang.org/issue/13832. 423 + func hasCUEFiles(ctxt *fileSystem, dir string) bool { 424 + ents, _ := ctxt.readDir(dir) 425 + for _, ent := range ents { 426 + if !ent.IsDir() && strings.HasSuffix(ent.Name(), cueSuffix) { 427 + return true 428 + } 429 + } 430 + return false 431 + } 432 + 433 + func findimportComment(data []byte) (s string, line int) { 434 + // expect keyword package 435 + word, data := parseWord(data) 436 + if string(word) != "package" { 437 + return "", 0 438 + } 439 + 440 + // expect package name 441 + _, data = parseWord(data) 442 + 443 + // now ready for import comment, a // or /* */ comment 444 + // beginning and ending on the current line. 445 + for len(data) > 0 && (data[0] == ' ' || data[0] == '\t' || data[0] == '\r') { 446 + data = data[1:] 447 + } 448 + 449 + var comment []byte 450 + switch { 451 + case bytes.HasPrefix(data, slashSlash): 452 + i := bytes.Index(data, newline) 453 + if i < 0 { 454 + i = len(data) 455 + } 456 + comment = data[2:i] 457 + case bytes.HasPrefix(data, slashStar): 458 + data = data[2:] 459 + i := bytes.Index(data, starSlash) 460 + if i < 0 { 461 + // malformed comment 462 + return "", 0 463 + } 464 + comment = data[:i] 465 + if bytes.Contains(comment, newline) { 466 + return "", 0 467 + } 468 + } 469 + comment = bytes.TrimSpace(comment) 470 + 471 + // split comment into `import`, `"pkg"` 472 + word, arg := parseWord(comment) 473 + if string(word) != "import" { 474 + return "", 0 475 + } 476 + 477 + line = 1 + bytes.Count(data[:cap(data)-cap(arg)], newline) 478 + return strings.TrimSpace(string(arg)), line 479 + } 480 + 481 + var ( 482 + slashSlash = []byte("//") 483 + slashStar = []byte("/*") 484 + starSlash = []byte("*/") 485 + newline = []byte("\n") 486 + ) 487 + 488 + // skipSpaceOrComment returns data with any leading spaces or comments removed. 489 + func skipSpaceOrComment(data []byte) []byte { 490 + for len(data) > 0 { 491 + switch data[0] { 492 + case ' ', '\t', '\r', '\n': 493 + data = data[1:] 494 + continue 495 + case '/': 496 + if bytes.HasPrefix(data, slashSlash) { 497 + i := bytes.Index(data, newline) 498 + if i < 0 { 499 + return nil 500 + } 501 + data = data[i+1:] 502 + continue 503 + } 504 + if bytes.HasPrefix(data, slashStar) { 505 + data = data[2:] 506 + i := bytes.Index(data, starSlash) 507 + if i < 0 { 508 + return nil 509 + } 510 + data = data[i+2:] 511 + continue 512 + } 513 + } 514 + break 515 + } 516 + return data 517 + } 518 + 519 + // parseWord skips any leading spaces or comments in data 520 + // and then parses the beginning of data as an identifier or keyword, 521 + // returning that word and what remains after the word. 522 + func parseWord(data []byte) (word, rest []byte) { 523 + data = skipSpaceOrComment(data) 524 + 525 + // Parse past leading word characters. 526 + rest = data 527 + for { 528 + r, size := utf8.DecodeRune(rest) 529 + if unicode.IsLetter(r) || '0' <= r && r <= '9' || r == '_' { 530 + rest = rest[size:] 531 + continue 532 + } 533 + break 534 + } 535 + 536 + word = data[:len(data)-len(rest)] 537 + if len(word) == 0 { 538 + return nil, nil 539 + } 540 + 541 + return word, rest 542 + } 543 + 544 + func cleanImports(m map[string][]token.Position) ([]string, map[string][]token.Position) { 545 + all := make([]string, 0, len(m)) 546 + for path := range m { 547 + all = append(all, path) 548 + } 549 + sort.Strings(all) 550 + return all, m 551 + } 552 + 553 + // // Import is shorthand for Default.Import. 554 + // func Import(path, srcDir string, mode ImportMode) (*Package, error) { 555 + // return Default.Import(path, srcDir, mode) 556 + // } 557 + 558 + // // ImportDir is shorthand for Default.ImportDir. 559 + // func ImportDir(dir string, mode ImportMode) (*Package, error) { 560 + // return Default.ImportDir(dir, mode) 561 + // } 562 + 563 + var slashslash = []byte("//") 564 + 565 + // isLocalImport reports whether the import path is 566 + // a local import path, like ".", "..", "./foo", or "../foo". 567 + func isLocalImport(path string) bool { 568 + return path == "." || path == ".." || 569 + strings.HasPrefix(path, "./") || strings.HasPrefix(path, "../") 570 + }
+121
cue/load/import_test.go
··· 1 + // Copyright 2018 The CUE Authors 2 + // 3 + // Licensed under the Apache License, Version 2.0 (the "License"); 4 + // you may not use this file except in compliance with the License. 5 + // You may obtain a copy of the License at 6 + // 7 + // http://www.apache.org/licenses/LICENSE-2.0 8 + // 9 + // Unless required by applicable law or agreed to in writing, software 10 + // distributed under the License is distributed on an "AS IS" BASIS, 11 + // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 + // See the License for the specific language governing permissions and 13 + // limitations under the License. 14 + 15 + package load 16 + 17 + import ( 18 + "os" 19 + "path/filepath" 20 + "reflect" 21 + "testing" 22 + 23 + build "cuelang.org/go/cue/build" 24 + ) 25 + 26 + const testdata = "./testdata/" 27 + 28 + func getInst(pkg, cwd string) (*build.Instance, error) { 29 + c, _ := (&Config{}).complete() 30 + l := loader{cfg: c} 31 + p := l.importPkg(pkg, cwd) 32 + return p, p.Err 33 + } 34 + 35 + func TestDotSlashImport(t *testing.T) { 36 + c, _ := (&Config{}).complete() 37 + l := loader{cfg: c} 38 + p := l.importPkg(".", testdata+"other") 39 + err := p.Err 40 + if err != nil { 41 + t.Fatal(err) 42 + } 43 + if len(p.ImportPaths) != 1 || p.ImportPaths[0] != "./file" { 44 + t.Fatalf("testdata/other: Imports=%v, want [./file]", p.ImportPaths) 45 + } 46 + 47 + p1, err := getInst("./file", testdata+"other") 48 + if err != nil { 49 + t.Fatal(err) 50 + } 51 + if p1.PkgName != "file" { 52 + t.Fatalf("./file: Name=%q, want %q", p1.PkgName, "file") 53 + } 54 + dir := filepath.Clean(testdata + "other/file") // Clean to use \ on Windows 55 + if p1.Dir != dir { 56 + t.Fatalf("./file: Dir=%q, want %q", p1.PkgName, dir) 57 + } 58 + } 59 + 60 + func TestEmptyImport(t *testing.T) { 61 + p, err := getInst("", "") 62 + if err == nil { 63 + t.Fatal(`Import("") returned nil error.`) 64 + } 65 + if p == nil { 66 + t.Fatal(`Import("") returned nil package.`) 67 + } 68 + if p.DisplayPath != "" { 69 + t.Fatalf("DisplayPath=%q, want %q.", p.DisplayPath, "") 70 + } 71 + } 72 + 73 + func TestEmptyFolderImport(t *testing.T) { 74 + _, err := getInst(".", testdata+"empty") 75 + if _, ok := err.(*noCUEError); !ok { 76 + t.Fatal(`Import("testdata/empty") did not return NoCUEError.`) 77 + } 78 + } 79 + 80 + func TestIgnoredCUEFilesImport(t *testing.T) { 81 + _, err := getInst(".", testdata+"ignored") 82 + e, ok := err.(*noCUEError) 83 + if !ok { 84 + t.Fatal(`Import("testdata/ignored") did not return NoCUEError.`) 85 + } 86 + if !e.Ignored { 87 + t.Fatal(`Import("testdata/ignored") should have ignored CUE files.`) 88 + } 89 + } 90 + 91 + func TestMultiplePackageImport(t *testing.T) { 92 + _, err := getInst(".", testdata+"multi") 93 + mpe, ok := err.(*multiplePackageError) 94 + if !ok { 95 + t.Fatal(`Import("testdata/multi") did not return MultiplePackageError.`) 96 + } 97 + want := &multiplePackageError{ 98 + Dir: filepath.FromSlash("testdata/multi"), 99 + Packages: []string{"main", "test_package"}, 100 + Files: []string{"file.cue", "file_appengine.cue"}, 101 + } 102 + if !reflect.DeepEqual(mpe, want) { 103 + t.Errorf("got %#v; want %#v", mpe, want) 104 + } 105 + } 106 + 107 + func TestLocalDirectory(t *testing.T) { 108 + cwd, err := os.Getwd() 109 + if err != nil { 110 + t.Fatal(err) 111 + } 112 + 113 + p, err := getInst(".", cwd) 114 + if err != nil { 115 + t.Fatal(err) 116 + } 117 + 118 + if p.DisplayPath != "." { 119 + t.Fatalf("DisplayPath=%q, want %q", p.DisplayPath, ".") 120 + } 121 + }
+261
cue/load/loader.go
··· 1 + // Copyright 2018 The CUE Authors 2 + // 3 + // Licensed under the Apache License, Version 2.0 (the "License"); 4 + // you may not use this file except in compliance with the License. 5 + // You may obtain a copy of the License at 6 + // 7 + // http://www.apache.org/licenses/LICENSE-2.0 8 + // 9 + // Unless required by applicable law or agreed to in writing, software 10 + // distributed under the License is distributed on an "AS IS" BASIS, 11 + // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 + // See the License for the specific language governing permissions and 13 + // limitations under the License. 14 + 15 + package load 16 + 17 + // Files in package are to a large extent based on Go files from the following 18 + // Go packages: 19 + // - cmd/go/internal/load 20 + // - go/build 21 + 22 + import ( 23 + "errors" 24 + "fmt" 25 + "os" 26 + pathpkg "path" 27 + "path/filepath" 28 + "strings" 29 + "unicode" 30 + 31 + build "cuelang.org/go/cue/build" 32 + "cuelang.org/go/cue/token" 33 + ) 34 + 35 + // Instances returns the instances named by the command line arguments 'args'. 36 + // If errors occur trying to load an instance it is returned with Incomplete 37 + // set. Errors directly related to loading the instance are recorded in this 38 + // instance, but errors that occur loading dependencies are recorded in these 39 + // dependencies. 40 + func Instances(args []string, c *Config) []*build.Instance { 41 + if c == nil { 42 + c = &Config{} 43 + } 44 + 45 + c, err := c.complete() 46 + if err != nil { 47 + return nil 48 + } 49 + 50 + l := c.loader 51 + 52 + if len(args) > 0 && strings.HasSuffix(args[0], cueSuffix) { 53 + return []*build.Instance{l.cueFilesPackage(args)} 54 + } 55 + 56 + dummy := c.newInstance("user") 57 + dummy.Local = true 58 + 59 + a := []*build.Instance{} 60 + for _, m := range l.importPaths(args) { 61 + if m.Err != nil { 62 + inst := c.newErrInstance(m, "", m.Err) 63 + a = append(a, inst) 64 + continue 65 + } 66 + a = append(a, m.Pkgs...) 67 + } 68 + return a 69 + } 70 + 71 + // Mode flags for loadImport and download (in get.go). 72 + const ( 73 + // resolveImport means that loadImport should do import path expansion. 74 + // That is, resolveImport means that the import path came from 75 + // a source file and has not been expanded yet to account for 76 + // vendoring or possible module adjustment. 77 + // Every import path should be loaded initially with resolveImport, 78 + // and then the expanded version (for example with the /vendor/ in it) 79 + // gets recorded as the canonical import path. At that point, future loads 80 + // of that package must not pass resolveImport, because 81 + // disallowVendor will reject direct use of paths containing /vendor/. 82 + resolveImport = 1 << iota 83 + 84 + // resolveModule is for download (part of "go get") and indicates 85 + // that the module adjustment should be done, but not vendor adjustment. 86 + resolveModule 87 + 88 + // getTestDeps is for download (part of "go get") and indicates 89 + // that test dependencies should be fetched too. 90 + getTestDeps 91 + ) 92 + 93 + func firstPos(p []token.Position) token.Position { 94 + if len(p) == 0 { 95 + return token.Position{} 96 + } 97 + return p[0] 98 + } 99 + 100 + type loader struct { 101 + cfg *Config 102 + stk importStack 103 + } 104 + 105 + func (l *loader) abs(filename string) string { 106 + if !isLocalImport(filename) { 107 + return filename 108 + } 109 + return filepath.Join(l.cfg.Dir, filename) 110 + } 111 + 112 + // cueFilesPackage creates a package for building a collection of CUE files 113 + // (typically named on the command line). 114 + func (l *loader) cueFilesPackage(files []string) *build.Instance { 115 + cfg := l.cfg 116 + // ModInit() // TODO: support modules 117 + for _, f := range files { 118 + if !strings.HasSuffix(f, ".cue") { 119 + return cfg.newErrInstance(nil, f, 120 + errors.New("named files must be .cue files")) 121 + } 122 + } 123 + 124 + pkg := l.cfg.Context.NewInstance(cfg.Dir, l.loadFunc(cfg.Dir)) 125 + // TODO: add fiels directly? 126 + fp := newFileProcessor(cfg, pkg) 127 + for _, file := range files { 128 + path := file 129 + if !filepath.IsAbs(file) { 130 + path = filepath.Join(cfg.Dir, file) 131 + } 132 + fi, err := os.Stat(path) 133 + if err != nil { 134 + return cfg.newErrInstance(nil, path, err) 135 + } 136 + if fi.IsDir() { 137 + return cfg.newErrInstance(nil, path, 138 + fmt.Errorf("%s is a directory, should be a CUE file", file)) 139 + } 140 + fp.add(cfg.Dir, file, allowAnonymous) 141 + } 142 + 143 + // TODO: ModImportFromFiles(files) 144 + _, err := filepath.Abs(cfg.Dir) 145 + if err != nil { 146 + return cfg.newErrInstance(nil, cfg.Dir, err) 147 + } 148 + pkg.Dir = cfg.Dir 149 + rewriteFiles(pkg, pkg.Dir, true) 150 + err = fp.finalize() // ImportDir(&ctxt, dir, 0) 151 + // TODO: Support module importing. 152 + // if ModDirImportPath != nil { 153 + // // Use the effective import path of the directory 154 + // // for deciding visibility during pkg.load. 155 + // bp.ImportPath = ModDirImportPath(dir) 156 + // } 157 + 158 + for _, f := range pkg.CUEFiles { 159 + if !filepath.IsAbs(f) { 160 + f = filepath.Join(cfg.Dir, f) 161 + } 162 + pkg.AddFile(f, nil) 163 + } 164 + 165 + pkg.Local = true 166 + l.stk.Push("user") 167 + pkg.Complete() 168 + l.stk.Pop() 169 + pkg.Local = true 170 + //pkg.LocalPrefix = dirToImportPath(dir) 171 + pkg.DisplayPath = "command-line-arguments" 172 + pkg.Match = files 173 + 174 + return pkg 175 + } 176 + 177 + func cleanImport(path string) string { 178 + orig := path 179 + path = pathpkg.Clean(path) 180 + if strings.HasPrefix(orig, "./") && path != ".." && !strings.HasPrefix(path, "../") { 181 + path = "./" + path 182 + } 183 + return path 184 + } 185 + 186 + // An importStack is a stack of import paths, possibly with the suffix " (test)" appended. 187 + // The import path of a test package is the import path of the corresponding 188 + // non-test package with the suffix "_test" added. 189 + type importStack []string 190 + 191 + func (s *importStack) Push(p string) { 192 + *s = append(*s, p) 193 + } 194 + 195 + func (s *importStack) Pop() { 196 + *s = (*s)[0 : len(*s)-1] 197 + } 198 + 199 + func (s *importStack) Copy() []string { 200 + return append([]string{}, *s...) 201 + } 202 + 203 + // shorterThan reports whether sp is shorter than t. 204 + // We use this to record the shortest import sequences 205 + // that leads to a particular package. 206 + func (sp *importStack) shorterThan(t []string) bool { 207 + s := *sp 208 + if len(s) != len(t) { 209 + return len(s) < len(t) 210 + } 211 + // If they are the same length, settle ties using string ordering. 212 + for i := range s { 213 + if s[i] != t[i] { 214 + return s[i] < t[i] 215 + } 216 + } 217 + return false // they are equal 218 + } 219 + 220 + // reusePackage reuses package p to satisfy the import at the top 221 + // of the import stack stk. If this use causes an import loop, 222 + // reusePackage updates p's error information to record the loop. 223 + func (l *loader) reusePackage(p *build.Instance) *build.Instance { 224 + // We use p.Internal.Imports==nil to detect a package that 225 + // is in the midst of its own loadPackage call 226 + // (all the recursion below happens before p.Internal.Imports gets set). 227 + if p.ImportPaths == nil { 228 + if err := lastError(p); err == nil { 229 + err = l.errPkgf(nil, "import cycle not allowed") 230 + err.IsImportCycle = true 231 + report(p, err) 232 + } 233 + p.Incomplete = true 234 + } 235 + // Don't rewrite the import stack in the error if we have an import cycle. 236 + // If we do, we'll lose the path that describes the cycle. 237 + if err := lastError(p); err != nil && !err.IsImportCycle && l.stk.shorterThan(err.ImportStack) { 238 + err.ImportStack = l.stk.Copy() 239 + } 240 + return p 241 + } 242 + 243 + // dirToImportPath returns the pseudo-import path we use for a package 244 + // outside the CUE path. It begins with _/ and then contains the full path 245 + // to the directory. If the package lives in c:\home\gopher\my\pkg then 246 + // the pseudo-import path is _/c_/home/gopher/my/pkg. 247 + // Using a pseudo-import path like this makes the ./ imports no longer 248 + // a special case, so that all the code to deal with ordinary imports works 249 + // automatically. 250 + func dirToImportPath(dir string) string { 251 + return pathpkg.Join("_", strings.Map(makeImportValid, filepath.ToSlash(dir))) 252 + } 253 + 254 + func makeImportValid(r rune) rune { 255 + // Should match Go spec, compilers, and ../../go/parser/parser.go:/isValidImport. 256 + const illegalChars = `!"#$%&'()*,:;<=>?[\]^{|}` + "`\uFFFD" 257 + if !unicode.IsGraphic(r) || unicode.IsSpace(r) || strings.ContainsRune(illegalChars, r) { 258 + return '_' 259 + } 260 + return r 261 + }
+116
cue/load/loader_test.go
··· 1 + // Copyright 2018 The CUE Authors 2 + // 3 + // Licensed under the Apache License, Version 2.0 (the "License"); 4 + // you may not use this file except in compliance with the License. 5 + // You may obtain a copy of the License at 6 + // 7 + // http://www.apache.org/licenses/LICENSE-2.0 8 + // 9 + // Unless required by applicable law or agreed to in writing, software 10 + // distributed under the License is distributed on an "AS IS" BASIS, 11 + // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 + // See the License for the specific language governing permissions and 13 + // limitations under the License. 14 + 15 + package load 16 + 17 + import ( 18 + "bytes" 19 + "fmt" 20 + "os" 21 + "path/filepath" 22 + "strconv" 23 + "strings" 24 + "testing" 25 + 26 + build "cuelang.org/go/cue/build" 27 + "cuelang.org/go/internal/str" 28 + ) 29 + 30 + // TestLoad is an end-to-end test. 31 + func TestLoad(t *testing.T) { 32 + cwd, err := os.Getwd() 33 + if err != nil { 34 + t.Fatal(err) 35 + } 36 + args := str.StringList 37 + testCases := []struct { 38 + args []string 39 + want string 40 + err string 41 + }{{ 42 + args: nil, 43 + want: "test: test.cue (1 files)", 44 + }, { 45 + args: args("."), 46 + want: "test: test.cue (1 files)", 47 + }, { 48 + args: args("./other/..."), 49 + want: ` 50 + main: other/main.cue (1 files) 51 + file: other/file/file.cue (1 files);main: other/main.cue (1 files) 52 + file: other/file/file.cue (1 files)`, 53 + }, { 54 + args: args("./anon"), 55 + want: ": (0 files)", 56 + err: "build constraints exclude all CUE files", 57 + }, { 58 + args: args("./other"), 59 + want: ` 60 + main: other/main.cue (1 files) 61 + file: other/file/file.cue (1 files)`, 62 + }, { 63 + args: args("./hello"), 64 + want: "test: test.cue hello/test.cue (2 files)", 65 + }, { 66 + args: args("./anon.cue", "./other/anon.cue"), 67 + want: ": ./anon.cue ./other/anon.cue (2 files)", 68 + }, { 69 + // Absolute file is normalized. 70 + args: args(filepath.Join(cwd, "testdata", "anon.cue")), 71 + want: ": ./anon.cue (1 files)", 72 + }, { 73 + args: args("non-existing"), 74 + want: ": (0 files)", 75 + err: `cannot find package "non-existing"`, 76 + }, { 77 + args: args("./empty"), 78 + want: ": (0 files)", 79 + err: `no CUE files in ./empty`, 80 + }} 81 + for i, tc := range testCases { 82 + t.Run(strconv.Itoa(i)+"/"+strings.Join(tc.args, ":"), func(t *testing.T) { 83 + c := &Config{Dir: filepath.Join(cwd, testdata)} 84 + pkgs := Instances(tc.args, c) 85 + 86 + var errs, data []string 87 + for _, p := range pkgs { 88 + if p.Err != nil { 89 + errs = append(errs, p.Err.Error()) 90 + } 91 + got := strings.TrimSpace(pkgInfo(pkgs[0])) 92 + data = append(data, got) 93 + } 94 + 95 + if err := strings.Join(errs, ";"); err == "" != (tc.err == "") || 96 + err != "" && !strings.Contains(err, tc.err) { 97 + t.Errorf("error:\n got: %v\nwant: %v", err, tc.err) 98 + } 99 + got := strings.Join(data, ";") 100 + want := strings.TrimSpace(tc.want) 101 + if got != want { 102 + t.Errorf("got:\n%v\nwant:\n%v", got, want) 103 + } 104 + }) 105 + } 106 + } 107 + 108 + func pkgInfo(p *build.Instance) string { 109 + b := &bytes.Buffer{} 110 + fmt.Fprintf(b, "%s: %s (%d files)\n", 111 + p.PkgName, strings.Join(p.CUEFiles, " "), len(p.Files)) 112 + for _, p := range p.Imports { 113 + fmt.Fprintf(b, "\t%s\n", pkgInfo(p)) 114 + } 115 + return b.String() 116 + }
+213
cue/load/match.go
··· 1 + // Copyright 2018 The CUE Authors 2 + // 3 + // Licensed under the Apache License, Version 2.0 (the "License"); 4 + // you may not use this file except in compliance with the License. 5 + // You may obtain a copy of the License at 6 + // 7 + // http://www.apache.org/licenses/LICENSE-2.0 8 + // 9 + // Unless required by applicable law or agreed to in writing, software 10 + // distributed under the License is distributed on an "AS IS" BASIS, 11 + // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 + // See the License for the specific language governing permissions and 13 + // limitations under the License. 14 + 15 + package load 16 + 17 + import ( 18 + "bytes" 19 + "fmt" 20 + "strings" 21 + "unicode" 22 + ) 23 + 24 + // matchFileTest reports whether the file with the given name in the given directory 25 + // matches the context and would be included in a Package created by ImportDir 26 + // of that directory. 27 + // 28 + // matchFileTest considers the name of the file and may use cfg.Build.OpenFile to 29 + // read some or all of the file's content. 30 + func matchFileTest(cfg *Config, dir, name string) (match bool, err error) { 31 + match, _, _, err = matchFile(cfg, dir, name, false, false, nil) 32 + return 33 + } 34 + 35 + // matchFile determines whether the file with the given name in the given directory 36 + // should be included in the package being constructed. 37 + // It returns the data read from the file. 38 + // If returnImports is true and name denotes a CUE file, matchFile reads 39 + // until the end of the imports (and returns that data) even though it only 40 + // considers text until the first non-comment. 41 + // If allTags is non-nil, matchFile records any encountered build tag 42 + // by setting allTags[tag] = true. 43 + func matchFile(cfg *Config, dir, name string, returnImports, allFiles bool, allTags map[string]bool) (match bool, data []byte, filename string, err error) { 44 + if strings.HasPrefix(name, "_") || 45 + strings.HasPrefix(name, ".") { 46 + return 47 + } 48 + 49 + i := strings.LastIndex(name, ".") 50 + if i < 0 { 51 + i = len(name) 52 + } 53 + ext := name[i:] 54 + 55 + switch ext { 56 + case cueSuffix: 57 + // tentatively okay - read to make sure 58 + default: 59 + // skip 60 + return 61 + } 62 + 63 + filename = cfg.fileSystem.joinPath(dir, name) 64 + f, err := cfg.fileSystem.openFile(filename) 65 + if err != nil { 66 + return 67 + } 68 + 69 + if strings.HasSuffix(filename, cueSuffix) { 70 + data, err = readImports(f, false, nil) 71 + } else { 72 + data, err = readComments(f) 73 + } 74 + f.Close() 75 + if err != nil { 76 + err = fmt.Errorf("read %s: %v", filename, err) 77 + return 78 + } 79 + 80 + // Look for +build comments to accept or reject the file. 81 + if !shouldBuild(cfg, data, allTags) && !allFiles { 82 + return 83 + } 84 + 85 + match = true 86 + return 87 + } 88 + 89 + // shouldBuild reports whether it is okay to use this file, 90 + // The rule is that in the file's leading run of // comments 91 + // and blank lines, which must be followed by a blank line 92 + // (to avoid including a Go package clause doc comment), 93 + // lines beginning with '// +build' are taken as build directives. 94 + // 95 + // The file is accepted only if each such line lists something 96 + // matching the file. For example: 97 + // 98 + // // +build windows linux 99 + // 100 + // marks the file as applicable only on Windows and Linux. 101 + // 102 + // If shouldBuild finds a //go:binary-only-package comment in the file, 103 + // it sets *binaryOnly to true. Otherwise it does not change *binaryOnly. 104 + // 105 + func shouldBuild(cfg *Config, content []byte, allTags map[string]bool) bool { 106 + // Pass 1. Identify leading run of // comments and blank lines, 107 + // which must be followed by a blank line. 108 + end := 0 109 + p := content 110 + for len(p) > 0 { 111 + line := p 112 + if i := bytes.IndexByte(line, '\n'); i >= 0 { 113 + line, p = line[:i], p[i+1:] 114 + } else { 115 + p = p[len(p):] 116 + } 117 + line = bytes.TrimSpace(line) 118 + if len(line) == 0 { // Blank line 119 + end = len(content) - len(p) 120 + continue 121 + } 122 + if !bytes.HasPrefix(line, slashslash) { // Not comment line 123 + break 124 + } 125 + } 126 + content = content[:end] 127 + 128 + // Pass 2. Process each line in the run. 129 + p = content 130 + allok := true 131 + for len(p) > 0 { 132 + line := p 133 + if i := bytes.IndexByte(line, '\n'); i >= 0 { 134 + line, p = line[:i], p[i+1:] 135 + } else { 136 + p = p[len(p):] 137 + } 138 + line = bytes.TrimSpace(line) 139 + if bytes.HasPrefix(line, slashslash) { 140 + line = bytes.TrimSpace(line[len(slashslash):]) 141 + if len(line) > 0 && line[0] == '+' { 142 + // Looks like a comment +line. 143 + f := strings.Fields(string(line)) 144 + if f[0] == "+build" { 145 + ok := false 146 + for _, tok := range f[1:] { 147 + if doMatch(cfg, tok, allTags) { 148 + ok = true 149 + } 150 + } 151 + if !ok { 152 + allok = false 153 + } 154 + } 155 + } 156 + } 157 + } 158 + 159 + return allok 160 + } 161 + 162 + // doMatch reports whether the name is one of: 163 + // 164 + // tag (if tag is listed in cfg.Build.BuildTags or cfg.Build.ReleaseTags) 165 + // !tag (if tag is not listed in cfg.Build.BuildTags or cfg.Build.ReleaseTags) 166 + // a comma-separated list of any of these 167 + // 168 + func doMatch(cfg *Config, name string, allTags map[string]bool) bool { 169 + if name == "" { 170 + if allTags != nil { 171 + allTags[name] = true 172 + } 173 + return false 174 + } 175 + if i := strings.Index(name, ","); i >= 0 { 176 + // comma-separated list 177 + ok1 := doMatch(cfg, name[:i], allTags) 178 + ok2 := doMatch(cfg, name[i+1:], allTags) 179 + return ok1 && ok2 180 + } 181 + if strings.HasPrefix(name, "!!") { // bad syntax, reject always 182 + return false 183 + } 184 + if strings.HasPrefix(name, "!") { // negation 185 + return len(name) > 1 && !doMatch(cfg, name[1:], allTags) 186 + } 187 + 188 + if allTags != nil { 189 + allTags[name] = true 190 + } 191 + 192 + // Tags must be letters, digits, underscores or dots. 193 + // Unlike in CUE identifiers, all digits are fine (e.g., "386"). 194 + for _, c := range name { 195 + if !unicode.IsLetter(c) && !unicode.IsDigit(c) && c != '_' && c != '.' { 196 + return false 197 + } 198 + } 199 + 200 + // other tags 201 + for _, tag := range cfg.BuildTags { 202 + if tag == name { 203 + return true 204 + } 205 + } 206 + for _, tag := range cfg.releaseTags { 207 + if tag == name { 208 + return true 209 + } 210 + } 211 + 212 + return false 213 + }
+144
cue/load/match_test.go
··· 1 + // Copyright 2018 The CUE Authors 2 + // 3 + // Licensed under the Apache License, Version 2.0 (the "License"); 4 + // you may not use this file except in compliance with the License. 5 + // You may obtain a copy of the License at 6 + // 7 + // http://www.apache.org/licenses/LICENSE-2.0 8 + // 9 + // Unless required by applicable law or agreed to in writing, software 10 + // distributed under the License is distributed on an "AS IS" BASIS, 11 + // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 + // See the License for the specific language governing permissions and 13 + // limitations under the License. 14 + 15 + package load 16 + 17 + import ( 18 + "io" 19 + "reflect" 20 + "strings" 21 + "testing" 22 + ) 23 + 24 + func TestMatch(t *testing.T) { 25 + c := &Config{} 26 + what := "default" 27 + matchFn := func(tag string, want map[string]bool) { 28 + t.Helper() 29 + m := make(map[string]bool) 30 + if !doMatch(c, tag, m) { 31 + t.Errorf("%s context should match %s, does not", what, tag) 32 + } 33 + if !reflect.DeepEqual(m, want) { 34 + t.Errorf("%s tags = %v, want %v", tag, m, want) 35 + } 36 + } 37 + noMatch := func(tag string, want map[string]bool) { 38 + m := make(map[string]bool) 39 + if doMatch(c, tag, m) { 40 + t.Errorf("%s context should NOT match %s, does", what, tag) 41 + } 42 + if !reflect.DeepEqual(m, want) { 43 + t.Errorf("%s tags = %v, want %v", tag, m, want) 44 + } 45 + } 46 + 47 + c.BuildTags = []string{"foo"} 48 + matchFn("foo", map[string]bool{"foo": true}) 49 + noMatch("!foo", map[string]bool{"foo": true}) 50 + matchFn("foo,!bar", map[string]bool{"foo": true, "bar": true}) 51 + noMatch("!", map[string]bool{}) 52 + } 53 + 54 + func TestShouldBuild(t *testing.T) { 55 + const file1 = "// +build tag1\n\n" + 56 + "package main\n" 57 + want1 := map[string]bool{"tag1": true} 58 + 59 + const file2 = "// +build cgo\n\n" + 60 + "// This package implements parsing of tags like\n" + 61 + "// +build tag1\n" + 62 + "package load" 63 + want2 := map[string]bool{"cgo": true} 64 + 65 + const file3 = "// Copyright The CUE Authors.\n\n" + 66 + "package load\n\n" + 67 + "// shouldBuild checks tags given by lines of the form\n" + 68 + "// +build tag\n" + 69 + "func shouldBuild(content []byte)\n" 70 + want3 := map[string]bool{} 71 + 72 + c := &Config{BuildTags: []string{"tag1"}} 73 + m := map[string]bool{} 74 + if !shouldBuild(c, []byte(file1), m) { 75 + t.Errorf("shouldBuild(file1) = false, want true") 76 + } 77 + if !reflect.DeepEqual(m, want1) { 78 + t.Errorf("shouldBuild(file1) tags = %v, want %v", m, want1) 79 + } 80 + 81 + m = map[string]bool{} 82 + if shouldBuild(c, []byte(file2), m) { 83 + t.Errorf("shouldBuild(file2) = true, want false") 84 + } 85 + if !reflect.DeepEqual(m, want2) { 86 + t.Errorf("shouldBuild(file2) tags = %v, want %v", m, want2) 87 + } 88 + 89 + m = map[string]bool{} 90 + c = &Config{BuildTags: nil} 91 + if !shouldBuild(c, []byte(file3), m) { 92 + t.Errorf("shouldBuild(file3) = false, want true") 93 + } 94 + if !reflect.DeepEqual(m, want3) { 95 + t.Errorf("shouldBuild(file3) tags = %v, want %v", m, want3) 96 + } 97 + } 98 + 99 + type readNopCloser struct { 100 + io.Reader 101 + } 102 + 103 + func (r readNopCloser) Close() error { 104 + return nil 105 + } 106 + 107 + var ( 108 + cfg = &Config{BuildTags: []string{"enable"}} 109 + defCfg = &Config{} 110 + ) 111 + 112 + var matchFileTests = []struct { 113 + cfg *Config 114 + name string 115 + data string 116 + match bool 117 + }{ 118 + {defCfg, "foo.cue", "", true}, 119 + {defCfg, "foo.cue", "// +build enable\n\npackage foo\n", false}, 120 + {defCfg, "foo.cue", "// +build !enable\n\npackage foo\n", true}, 121 + {defCfg, "foo1.cue", "// +build linux\n\npackage foo\n", false}, 122 + {defCfg, "foo.badsuffix", "", false}, 123 + {cfg, "foo.cue", "// +build enable\n\npackage foo\n", true}, 124 + {cfg, "foo.cue", "// +build !enable\n\npackage foo\n", false}, 125 + } 126 + 127 + func TestMatchFile(t *testing.T) { 128 + for _, tt := range matchFileTests { 129 + ctxt := &tt.cfg.fileSystem 130 + ctxt.OpenFile = func(path string) (r io.ReadCloser, err error) { 131 + if path != "x+"+tt.name { 132 + t.Fatalf("OpenFile asked for %q, expected %q", path, "x+"+tt.name) 133 + } 134 + return &readNopCloser{strings.NewReader(tt.data)}, nil 135 + } 136 + ctxt.JoinPath = func(elem ...string) string { 137 + return strings.Join(elem, "+") 138 + } 139 + match, err := matchFileTest(tt.cfg, "x", tt.name) 140 + if match != tt.match || err != nil { 141 + t.Fatalf("MatchFile(%q) = %v, %v, want %v, nil", tt.name, match, err, tt.match) 142 + } 143 + } 144 + }
+68
cue/load/package.go
··· 1 + // Copyright 2018 The CUE Authors 2 + // 3 + // Licensed under the Apache License, Version 2.0 (the "License"); 4 + // you may not use this file except in compliance with the License. 5 + // You may obtain a copy of the License at 6 + // 7 + // http://www.apache.org/licenses/LICENSE-2.0 8 + // 9 + // Unless required by applicable law or agreed to in writing, software 10 + // distributed under the License is distributed on an "AS IS" BASIS, 11 + // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 + // See the License for the specific language governing permissions and 13 + // limitations under the License. 14 + 15 + package load 16 + 17 + import ( 18 + "unicode/utf8" 19 + 20 + "cuelang.org/go/cue/build" 21 + "cuelang.org/go/internal/str" 22 + ) 23 + 24 + // Package rules: 25 + // 26 + // - the package clause defines a namespace. 27 + // - a cue file without a package clause is a standalone file. 28 + // - all files with the same package name within a directory and its 29 + // ancestor directories up to the package root belong to the same package. 30 + // - The package root is either the top of the file hierarchy or the first 31 + // directory in which a cue.mod file is defined. 32 + // 33 + // The contents of a namespace depends on the directory that is selected as the 34 + // starting point to load a package. An instance defines a package-directory 35 + // pair. 36 + 37 + // allFiles returns the names of all the files considered for the package. 38 + // This is used for sanity and security checks, so we include all files, 39 + // even IgnoredGoFiles, because some subcommands consider them. 40 + func allFiles(p *build.Instance) []string { 41 + return str.StringList( 42 + p.CUEFiles, 43 + p.ToolCUEFiles, 44 + p.TestCUEFiles, 45 + p.IgnoredCUEFiles, 46 + p.InvalidCUEFiles, 47 + p.DataFiles, 48 + ) 49 + } 50 + 51 + var foldPath = make(map[string]string) 52 + 53 + // safeArg reports whether arg is a "safe" command-line argument, 54 + // meaning that when it appears in a command-line, it probably 55 + // doesn't have some special meaning other than its own name. 56 + // Obviously args beginning with - are not safe (they look like flags). 57 + // Less obviously, args beginning with @ are not safe (they look like 58 + // GNU binutils flagfile specifiers, sometimes called "response files"). 59 + // To be conservative, we reject almost any arg beginning with non-alphanumeric ASCII. 60 + // We accept leading . _ and / as likely in file system paths. 61 + // There is a copy of this function in cmd/compile/internal/gc/noder.go. 62 + func safeArg(name string) bool { 63 + if name == "" { 64 + return false 65 + } 66 + c := name[0] 67 + return '0' <= c && c <= '9' || 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || c == '.' || c == '_' || c == '/' || c >= utf8.RuneSelf 68 + }
+257
cue/load/read.go
··· 1 + // Copyright 2018 The CUE Authors 2 + // 3 + // Licensed under the Apache License, Version 2.0 (the "License"); 4 + // you may not use this file except in compliance with the License. 5 + // You may obtain a copy of the License at 6 + // 7 + // http://www.apache.org/licenses/LICENSE-2.0 8 + // 9 + // Unless required by applicable law or agreed to in writing, software 10 + // distributed under the License is distributed on an "AS IS" BASIS, 11 + // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 + // See the License for the specific language governing permissions and 13 + // limitations under the License. 14 + 15 + package load 16 + 17 + import ( 18 + "bufio" 19 + "errors" 20 + "io" 21 + "unicode/utf8" 22 + ) 23 + 24 + type importReader struct { 25 + b *bufio.Reader 26 + buf []byte 27 + peek byte 28 + err error 29 + eof bool 30 + nerr int 31 + } 32 + 33 + func isIdent(c byte) bool { 34 + return 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || '0' <= c && c <= '9' || c == '_' || c >= utf8.RuneSelf 35 + } 36 + 37 + var ( 38 + errSyntax = errors.New("syntax error") 39 + errNUL = errors.New("unexpected NUL in input") 40 + ) 41 + 42 + // syntaxError records a syntax error, but only if an I/O error has not already been recorded. 43 + func (r *importReader) syntaxError() { 44 + if r.err == nil { 45 + r.err = errSyntax 46 + } 47 + } 48 + 49 + // readByte reads the next byte from the input, saves it in buf, and returns it. 50 + // If an error occurs, readByte records the error in r.err and returns 0. 51 + func (r *importReader) readByte() byte { 52 + c, err := r.b.ReadByte() 53 + if err == nil { 54 + r.buf = append(r.buf, c) 55 + if c == 0 { 56 + err = errNUL 57 + } 58 + } 59 + if err != nil { 60 + if err == io.EOF { 61 + r.eof = true 62 + } else if r.err == nil { 63 + r.err = err 64 + } 65 + c = 0 66 + } 67 + return c 68 + } 69 + 70 + // peekByte returns the next byte from the input reader but does not advance beyond it. 71 + // If skipSpace is set, peekByte skips leading spaces and comments. 72 + func (r *importReader) peekByte(skipSpace bool) byte { 73 + if r.err != nil { 74 + if r.nerr++; r.nerr > 10000 { 75 + panic("go/build: import reader looping") 76 + } 77 + return 0 78 + } 79 + 80 + // Use r.peek as first input byte. 81 + // Don't just return r.peek here: it might have been left by peekByte(false) 82 + // and this might be peekByte(true). 83 + c := r.peek 84 + if c == 0 { 85 + c = r.readByte() 86 + } 87 + for r.err == nil && !r.eof { 88 + if skipSpace { 89 + // For the purposes of this reader, semicolons are never necessary to 90 + // understand the input and are treated as spaces. 91 + switch c { 92 + case ' ', '\f', '\t', '\r', '\n', ';': 93 + c = r.readByte() 94 + continue 95 + 96 + case '/': 97 + c = r.readByte() 98 + if c == '/' { 99 + for c != '\n' && r.err == nil && !r.eof { 100 + c = r.readByte() 101 + } 102 + } else if c == '*' { 103 + var c1 byte 104 + for (c != '*' || c1 != '/') && r.err == nil { 105 + if r.eof { 106 + r.syntaxError() 107 + } 108 + c, c1 = c1, r.readByte() 109 + } 110 + } else { 111 + r.syntaxError() 112 + } 113 + c = r.readByte() 114 + continue 115 + } 116 + } 117 + break 118 + } 119 + r.peek = c 120 + return r.peek 121 + } 122 + 123 + // nextByte is like peekByte but advances beyond the returned byte. 124 + func (r *importReader) nextByte(skipSpace bool) byte { 125 + c := r.peekByte(skipSpace) 126 + r.peek = 0 127 + return c 128 + } 129 + 130 + // readKeyword reads the given keyword from the input. 131 + // If the keyword is not present, readKeyword records a syntax error. 132 + func (r *importReader) readKeyword(kw string) { 133 + r.peekByte(true) 134 + for i := 0; i < len(kw); i++ { 135 + if r.nextByte(false) != kw[i] { 136 + r.syntaxError() 137 + return 138 + } 139 + } 140 + if isIdent(r.peekByte(false)) { 141 + r.syntaxError() 142 + } 143 + } 144 + 145 + // readIdent reads an identifier from the input. 146 + // If an identifier is not present, readIdent records a syntax error. 147 + func (r *importReader) readIdent() { 148 + c := r.peekByte(true) 149 + if !isIdent(c) { 150 + r.syntaxError() 151 + return 152 + } 153 + for isIdent(r.peekByte(false)) { 154 + r.peek = 0 155 + } 156 + } 157 + 158 + // readString reads a quoted string literal from the input. 159 + // If an identifier is not present, readString records a syntax error. 160 + func (r *importReader) readString(save *[]string) { 161 + switch r.nextByte(true) { 162 + case '`': 163 + start := len(r.buf) - 1 164 + for r.err == nil { 165 + if r.nextByte(false) == '`' { 166 + if save != nil { 167 + *save = append(*save, string(r.buf[start:])) 168 + } 169 + break 170 + } 171 + if r.eof { 172 + r.syntaxError() 173 + } 174 + } 175 + case '"': 176 + start := len(r.buf) - 1 177 + for r.err == nil { 178 + c := r.nextByte(false) 179 + if c == '"' { 180 + if save != nil { 181 + *save = append(*save, string(r.buf[start:])) 182 + } 183 + break 184 + } 185 + if r.eof || c == '\n' { 186 + r.syntaxError() 187 + } 188 + if c == '\\' { 189 + r.nextByte(false) 190 + } 191 + } 192 + default: 193 + r.syntaxError() 194 + } 195 + } 196 + 197 + // readImport reads an import clause - optional identifier followed by quoted string - 198 + // from the input. 199 + func (r *importReader) readImport(imports *[]string) { 200 + c := r.peekByte(true) 201 + if c == '.' { 202 + r.peek = 0 203 + } else if isIdent(c) { 204 + r.readIdent() 205 + } 206 + r.readString(imports) 207 + } 208 + 209 + // readComments is like ioutil.ReadAll, except that it only reads the leading 210 + // block of comments in the file. 211 + func readComments(f io.Reader) ([]byte, error) { 212 + r := &importReader{b: bufio.NewReader(f)} 213 + r.peekByte(true) 214 + if r.err == nil && !r.eof { 215 + // Didn't reach EOF, so must have found a non-space byte. Remove it. 216 + r.buf = r.buf[:len(r.buf)-1] 217 + } 218 + return r.buf, r.err 219 + } 220 + 221 + // readImports is like ioutil.ReadAll, except that it expects a Go file as input 222 + // and stops reading the input once the imports have completed. 223 + func readImports(f io.Reader, reportSyntaxError bool, imports *[]string) ([]byte, error) { 224 + r := &importReader{b: bufio.NewReader(f)} 225 + 226 + r.readKeyword("package") 227 + r.readIdent() 228 + for r.peekByte(true) == 'i' { 229 + r.readKeyword("import") 230 + if r.peekByte(true) == '(' { 231 + r.nextByte(false) 232 + for r.peekByte(true) != ')' && r.err == nil { 233 + r.readImport(imports) 234 + } 235 + r.nextByte(false) 236 + } else { 237 + r.readImport(imports) 238 + } 239 + } 240 + 241 + // If we stopped successfully before EOF, we read a byte that told us we were done. 242 + // Return all but that last byte, which would cause a syntax error if we let it through. 243 + if r.err == nil && !r.eof { 244 + return r.buf[:len(r.buf)-1], nil 245 + } 246 + 247 + // If we stopped for a syntax error, consume the whole file so that 248 + // we are sure we don't change the errors that go/parser returns. 249 + if r.err == errSyntax && !reportSyntaxError { 250 + r.err = nil 251 + for r.err == nil && !r.eof { 252 + r.readByte() 253 + } 254 + } 255 + 256 + return r.buf, r.err 257 + }
+236
cue/load/read_test.go
··· 1 + // Copyright 2018 The CUE Authors 2 + // 3 + // Licensed under the Apache License, Version 2.0 (the "License"); 4 + // you may not use this file except in compliance with the License. 5 + // You may obtain a copy of the License at 6 + // 7 + // http://www.apache.org/licenses/LICENSE-2.0 8 + // 9 + // Unless required by applicable law or agreed to in writing, software 10 + // distributed under the License is distributed on an "AS IS" BASIS, 11 + // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 + // See the License for the specific language governing permissions and 13 + // limitations under the License. 14 + 15 + package load 16 + 17 + import ( 18 + "io" 19 + "strings" 20 + "testing" 21 + ) 22 + 23 + const quote = "`" 24 + 25 + type readTest struct { 26 + // Test input contains ℙ where readImports should stop. 27 + in string 28 + err string 29 + } 30 + 31 + var readImportsTests = []readTest{ 32 + { 33 + `package p`, 34 + "", 35 + }, 36 + { 37 + `package p; import "x"`, 38 + "", 39 + }, 40 + { 41 + `package p; import . "x"`, 42 + "", 43 + }, 44 + { 45 + `package p; import "x";ℙvar x = 1`, 46 + "", 47 + }, 48 + { 49 + `package p 50 + 51 + // comment 52 + 53 + import "x" 54 + import _ "x" 55 + import a "x" 56 + 57 + /* comment */ 58 + 59 + import ( 60 + "x" /* comment */ 61 + _ "x" 62 + a "x" // comment 63 + ` + quote + `x` + quote + ` 64 + _ /*comment*/ ` + quote + `x` + quote + ` 65 + a ` + quote + `x` + quote + ` 66 + ) 67 + import ( 68 + ) 69 + import () 70 + import()import()import() 71 + import();import();import() 72 + 73 + ℙvar x = 1 74 + `, 75 + "", 76 + }, 77 + } 78 + 79 + var readCommentsTests = []readTest{ 80 + { 81 + `ℙpackage p`, 82 + "", 83 + }, 84 + { 85 + `ℙpackage p; import "x"`, 86 + "", 87 + }, 88 + { 89 + `ℙpackage p; import . "x"`, 90 + "", 91 + }, 92 + { 93 + `// foo 94 + 95 + /* bar */ 96 + 97 + /* quux */ // baz 98 + 99 + /*/ zot */ 100 + 101 + // asdf 102 + ℙHello, world`, 103 + "", 104 + }, 105 + } 106 + 107 + func testRead(t *testing.T, tests []readTest, read func(io.Reader) ([]byte, error)) { 108 + for i, tt := range tests { 109 + var in, testOut string 110 + j := strings.Index(tt.in, "ℙ") 111 + if j < 0 { 112 + in = tt.in 113 + testOut = tt.in 114 + } else { 115 + in = tt.in[:j] + tt.in[j+len("ℙ"):] 116 + testOut = tt.in[:j] 117 + } 118 + r := strings.NewReader(in) 119 + buf, err := read(r) 120 + if err != nil { 121 + if tt.err == "" { 122 + t.Errorf("#%d: err=%q, expected success (%q)", i, err, string(buf)) 123 + continue 124 + } 125 + if !strings.Contains(err.Error(), tt.err) { 126 + t.Errorf("#%d: err=%q, expected %q", i, err, tt.err) 127 + continue 128 + } 129 + continue 130 + } 131 + if err == nil && tt.err != "" { 132 + t.Errorf("#%d: success, expected %q", i, tt.err) 133 + continue 134 + } 135 + 136 + out := string(buf) 137 + if out != testOut { 138 + t.Errorf("#%d: wrong output:\nhave %q\nwant %q\n", i, out, testOut) 139 + } 140 + } 141 + } 142 + 143 + func TestReadImports(t *testing.T) { 144 + testRead(t, readImportsTests, func(r io.Reader) ([]byte, error) { return readImports(r, true, nil) }) 145 + } 146 + 147 + func TestReadComments(t *testing.T) { 148 + testRead(t, readCommentsTests, readComments) 149 + } 150 + 151 + var readFailuresTests = []readTest{ 152 + { 153 + `package`, 154 + "syntax error", 155 + }, 156 + { 157 + "package p\n\x00\nimport `math`\n", 158 + "unexpected NUL in input", 159 + }, 160 + { 161 + `package p; import`, 162 + "syntax error", 163 + }, 164 + { 165 + `package p; import "`, 166 + "syntax error", 167 + }, 168 + { 169 + "package p; import ` \n\n", 170 + "syntax error", 171 + }, 172 + { 173 + `package p; import "x`, 174 + "syntax error", 175 + }, 176 + { 177 + `package p; import _`, 178 + "syntax error", 179 + }, 180 + { 181 + `package p; import _ "`, 182 + "syntax error", 183 + }, 184 + { 185 + `package p; import _ "x`, 186 + "syntax error", 187 + }, 188 + { 189 + `package p; import .`, 190 + "syntax error", 191 + }, 192 + { 193 + `package p; import . "`, 194 + "syntax error", 195 + }, 196 + { 197 + `package p; import . "x`, 198 + "syntax error", 199 + }, 200 + { 201 + `package p; import (`, 202 + "syntax error", 203 + }, 204 + { 205 + `package p; import ("`, 206 + "syntax error", 207 + }, 208 + { 209 + `package p; import ("x`, 210 + "syntax error", 211 + }, 212 + { 213 + `package p; import ("x"`, 214 + "syntax error", 215 + }, 216 + } 217 + 218 + func TestReadFailures(t *testing.T) { 219 + // Errors should be reported (true arg to readImports). 220 + testRead(t, readFailuresTests, func(r io.Reader) ([]byte, error) { return readImports(r, true, nil) }) 221 + } 222 + 223 + func TestReadFailuresIgnored(t *testing.T) { 224 + // Syntax errors should not be reported (false arg to readImports). 225 + // Instead, entire file should be the output and no error. 226 + // Convert tests not to return syntax errors. 227 + tests := make([]readTest, len(readFailuresTests)) 228 + copy(tests, readFailuresTests) 229 + for i := range tests { 230 + tt := &tests[i] 231 + if !strings.Contains(tt.err, "NUL") { 232 + tt.err = "" 233 + } 234 + } 235 + testRead(t, tests, func(r io.Reader) ([]byte, error) { return readImports(r, false, nil) }) 236 + }
+497
cue/load/search.go
··· 1 + // Copyright 2018 The CUE Authors 2 + // 3 + // Licensed under the Apache License, Version 2.0 (the "License"); 4 + // you may not use this file except in compliance with the License. 5 + // You may obtain a copy of the License at 6 + // 7 + // http://www.apache.org/licenses/LICENSE-2.0 8 + // 9 + // Unless required by applicable law or agreed to in writing, software 10 + // distributed under the License is distributed on an "AS IS" BASIS, 11 + // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 + // See the License for the specific language governing permissions and 13 + // limitations under the License. 14 + 15 + package load 16 + 17 + import ( 18 + "fmt" // TODO: remove this usage 19 + "log" 20 + "os" 21 + "path" 22 + "path/filepath" 23 + "regexp" 24 + "strings" 25 + 26 + build "cuelang.org/go/cue/build" 27 + ) 28 + 29 + // A match represents the result of matching a single package pattern. 30 + type match struct { 31 + Pattern string // the pattern itself 32 + Literal bool // whether it is a literal (no wildcards) 33 + Pkgs []*build.Instance 34 + Err error 35 + } 36 + 37 + // TODO: should be matched from module file only. 38 + // The pattern is either "all" (all packages), "std" (standard packages), 39 + // "cmd" (standard commands), or a path including "...". 40 + func (l *loader) matchPackages(pattern string) *match { 41 + // cfg := l.cfg 42 + m := &match{ 43 + Pattern: pattern, 44 + Literal: false, 45 + } 46 + // match := func(string) bool { return true } 47 + // treeCanMatch := func(string) bool { return true } 48 + // if !isMetaPackage(pattern) { 49 + // match = matchPattern(pattern) 50 + // treeCanMatch = treeCanMatchPattern(pattern) 51 + // } 52 + 53 + // have := map[string]bool{ 54 + // "builtin": true, // ignore pseudo-package that exists only for documentation 55 + // } 56 + 57 + // for _, src := range cfg.srcDirs() { 58 + // if pattern == "std" || pattern == "cmd" { 59 + // continue 60 + // } 61 + // src = filepath.Clean(src) + string(filepath.Separator) 62 + // root := src 63 + // if pattern == "cmd" { 64 + // root += "cmd" + string(filepath.Separator) 65 + // } 66 + // filepath.Walk(root, func(path string, fi os.FileInfo, err error) error { 67 + // if err != nil || path == src { 68 + // return nil 69 + // } 70 + 71 + // want := true 72 + // // Avoid .foo, _foo, and testdata directory trees. 73 + // _, elem := filepath.Split(path) 74 + // if strings.HasPrefix(elem, ".") || strings.HasPrefix(elem, "_") || elem == "testdata" { 75 + // want = false 76 + // } 77 + 78 + // name := filepath.ToSlash(path[len(src):]) 79 + // if pattern == "std" && (!isStandardImportPath(name) || name == "cmd") { 80 + // // The name "std" is only the standard library. 81 + // // If the name is cmd, it's the root of the command tree. 82 + // want = false 83 + // } 84 + // if !treeCanMatch(name) { 85 + // want = false 86 + // } 87 + 88 + // if !fi.IsDir() { 89 + // if fi.Mode()&os.ModeSymlink != 0 && want { 90 + // if target, err := os.Stat(path); err == nil && target.IsDir() { 91 + // fmt.Fprintf(os.Stderr, "warning: ignoring symlink %s\n", path) 92 + // } 93 + // } 94 + // return nil 95 + // } 96 + // if !want { 97 + // return filepath.SkipDir 98 + // } 99 + 100 + // if have[name] { 101 + // return nil 102 + // } 103 + // have[name] = true 104 + // if !match(name) { 105 + // return nil 106 + // } 107 + // pkg := l.importPkg(".", path) 108 + // if err := pkg.Error; err != nil { 109 + // if _, noGo := err.(*noCUEError); noGo { 110 + // return nil 111 + // } 112 + // } 113 + 114 + // // If we are expanding "cmd", skip main 115 + // // packages under cmd/vendor. At least as of 116 + // // March, 2017, there is one there for the 117 + // // vendored pprof tool. 118 + // if pattern == "cmd" && strings.HasPrefix(pkg.DisplayPath, "cmd/vendor") && pkg.PkgName == "main" { 119 + // return nil 120 + // } 121 + 122 + // m.Pkgs = append(m.Pkgs, pkg) 123 + // return nil 124 + // }) 125 + // } 126 + return m 127 + } 128 + 129 + // matchPackagesInFS is like allPackages but is passed a pattern 130 + // beginning ./ or ../, meaning it should scan the tree rooted 131 + // at the given directory. There are ... in the pattern too. 132 + // (See go help packages for pattern syntax.) 133 + func (l *loader) matchPackagesInFS(pattern string) *match { 134 + c := l.cfg 135 + m := &match{ 136 + Pattern: pattern, 137 + Literal: false, 138 + } 139 + 140 + // Find directory to begin the scan. 141 + // Could be smarter but this one optimization 142 + // is enough for now, since ... is usually at the 143 + // end of a path. 144 + i := strings.Index(pattern, "...") 145 + dir, _ := path.Split(pattern[:i]) 146 + 147 + root := l.abs(dir) 148 + 149 + if c.modRoot != "" { 150 + if !hasFilepathPrefix(root, c.modRoot) { 151 + m.Err = fmt.Errorf( 152 + "cue: pattern %s refers to dir %s, outside module root %s", 153 + pattern, root, c.modRoot) 154 + return m 155 + } 156 + } 157 + 158 + filepath.Walk(root, func(path string, fi os.FileInfo, err error) error { 159 + if err != nil || !fi.IsDir() { 160 + return nil 161 + } 162 + 163 + top := path == root 164 + 165 + // Avoid .foo, _foo, and testdata directory trees, but do not avoid "." or "..". 166 + _, elem := filepath.Split(path) 167 + dot := strings.HasPrefix(elem, ".") && elem != "." && elem != ".." 168 + if dot || strings.HasPrefix(elem, "_") || (elem == "testdata" && !top) { 169 + return filepath.SkipDir 170 + } 171 + 172 + if !top { 173 + // Ignore other modules found in subdirectories. 174 + if _, err := os.Stat(filepath.Join(path, modFile)); err == nil { 175 + return filepath.SkipDir 176 + } 177 + } 178 + 179 + // name := prefix + filepath.ToSlash(path) 180 + // if !match(name) { 181 + // return nil 182 + // } 183 + 184 + // We keep the directory if we can import it, or if we can't import it 185 + // due to invalid CUE source files. This means that directories 186 + // containing parse errors will be built (and fail) instead of being 187 + // silently skipped as not matching the pattern. 188 + p := l.importPkg("."+path[len(root):], root) 189 + if err := p.Err; err != nil && (p == nil || len(p.InvalidCUEFiles) == 0) { 190 + switch err.(type) { 191 + case nil: 192 + break 193 + case *noCUEError: 194 + if c.DataFiles && len(p.DataFiles) > 0 { 195 + break 196 + } 197 + return nil 198 + default: 199 + log.Print(err) 200 + return nil 201 + } 202 + } 203 + 204 + m.Pkgs = append(m.Pkgs, p) 205 + return nil 206 + }) 207 + return m 208 + } 209 + 210 + // treeCanMatchPattern(pattern)(name) reports whether 211 + // name or children of name can possibly match pattern. 212 + // Pattern is the same limited glob accepted by matchPattern. 213 + func treeCanMatchPattern(pattern string) func(name string) bool { 214 + wildCard := false 215 + if i := strings.Index(pattern, "..."); i >= 0 { 216 + wildCard = true 217 + pattern = pattern[:i] 218 + } 219 + return func(name string) bool { 220 + return len(name) <= len(pattern) && hasPathPrefix(pattern, name) || 221 + wildCard && strings.HasPrefix(name, pattern) 222 + } 223 + } 224 + 225 + // matchPattern(pattern)(name) reports whether 226 + // name matches pattern. Pattern is a limited glob 227 + // pattern in which '...' means 'any string' and there 228 + // is no other special syntax. 229 + // Unfortunately, there are two special cases. Quoting "go help packages": 230 + // 231 + // First, /... at the end of the pattern can match an empty string, 232 + // so that net/... matches both net and packages in its subdirectories, like net/http. 233 + // Second, any slash-separted pattern element containing a wildcard never 234 + // participates in a match of the "vendor" element in the path of a vendored 235 + // package, so that ./... does not match packages in subdirectories of 236 + // ./vendor or ./mycode/vendor, but ./vendor/... and ./mycode/vendor/... do. 237 + // Note, however, that a directory named vendor that itself contains code 238 + // is not a vendored package: cmd/vendor would be a command named vendor, 239 + // and the pattern cmd/... matches it. 240 + func matchPattern(pattern string) func(name string) bool { 241 + // Convert pattern to regular expression. 242 + // The strategy for the trailing /... is to nest it in an explicit ? expression. 243 + // The strategy for the vendor exclusion is to change the unmatchable 244 + // vendor strings to a disallowed code point (vendorChar) and to use 245 + // "(anything but that codepoint)*" as the implementation of the ... wildcard. 246 + // This is a bit complicated but the obvious alternative, 247 + // namely a hand-written search like in most shell glob matchers, 248 + // is too easy to make accidentally exponential. 249 + // Using package regexp guarantees linear-time matching. 250 + 251 + const vendorChar = "\x00" 252 + 253 + if strings.Contains(pattern, vendorChar) { 254 + return func(name string) bool { return false } 255 + } 256 + 257 + re := regexp.QuoteMeta(pattern) 258 + re = replaceVendor(re, vendorChar) 259 + switch { 260 + case strings.HasSuffix(re, `/`+vendorChar+`/\.\.\.`): 261 + re = strings.TrimSuffix(re, `/`+vendorChar+`/\.\.\.`) + `(/vendor|/` + vendorChar + `/\.\.\.)` 262 + case re == vendorChar+`/\.\.\.`: 263 + re = `(/vendor|/` + vendorChar + `/\.\.\.)` 264 + case strings.HasSuffix(re, `/\.\.\.`): 265 + re = strings.TrimSuffix(re, `/\.\.\.`) + `(/\.\.\.)?` 266 + } 267 + re = strings.Replace(re, `\.\.\.`, `[^`+vendorChar+`]*`, -1) 268 + 269 + reg := regexp.MustCompile(`^` + re + `$`) 270 + 271 + return func(name string) bool { 272 + if strings.Contains(name, vendorChar) { 273 + return false 274 + } 275 + return reg.MatchString(replaceVendor(name, vendorChar)) 276 + } 277 + } 278 + 279 + // replaceVendor returns the result of replacing 280 + // non-trailing vendor path elements in x with repl. 281 + func replaceVendor(x, repl string) string { 282 + if !strings.Contains(x, "vendor") { 283 + return x 284 + } 285 + elem := strings.Split(x, "/") 286 + for i := 0; i < len(elem)-1; i++ { 287 + if elem[i] == "vendor" { 288 + elem[i] = repl 289 + } 290 + } 291 + return strings.Join(elem, "/") 292 + } 293 + 294 + // warnUnmatched warns about patterns that didn't match any packages. 295 + func warnUnmatched(matches []*match) { 296 + for _, m := range matches { 297 + if len(m.Pkgs) == 0 { 298 + m.Err = 299 + fmt.Errorf("cue: %q matched no packages\n", m.Pattern) 300 + } 301 + } 302 + } 303 + 304 + // importPaths returns the matching paths to use for the given command line. 305 + // It calls ImportPathsQuiet and then WarnUnmatched. 306 + func (l *loader) importPaths(patterns []string) []*match { 307 + matches := l.importPathsQuiet(patterns) 308 + warnUnmatched(matches) 309 + return matches 310 + } 311 + 312 + // importPathsQuiet is like ImportPaths but does not warn about patterns with no matches. 313 + func (l *loader) importPathsQuiet(patterns []string) []*match { 314 + var out []*match 315 + for _, a := range cleanPatterns(patterns) { 316 + if isMetaPackage(a) { 317 + out = append(out, l.matchPackages(a)) 318 + continue 319 + } 320 + if strings.Contains(a, "...") { 321 + if isLocalImport(a) { 322 + out = append(out, l.matchPackagesInFS(a)) 323 + } else { 324 + out = append(out, l.matchPackages(a)) 325 + } 326 + continue 327 + } 328 + 329 + pkg := l.importPkg(a, l.cfg.Dir) 330 + out = append(out, &match{Pattern: a, Literal: true, Pkgs: []*build.Instance{pkg}}) 331 + } 332 + return out 333 + } 334 + 335 + // cleanPatterns returns the patterns to use for the given 336 + // command line. It canonicalizes the patterns but does not 337 + // evaluate any matches. 338 + func cleanPatterns(patterns []string) []string { 339 + if len(patterns) == 0 { 340 + return []string{"."} 341 + } 342 + var out []string 343 + for _, a := range patterns { 344 + // Arguments are supposed to be import paths, but 345 + // as a courtesy to Windows developers, rewrite \ to / 346 + // in command-line arguments. Handles .\... and so on. 347 + if filepath.Separator == '\\' { 348 + a = strings.Replace(a, `\`, `/`, -1) 349 + } 350 + 351 + // Put argument in canonical form, but preserve leading ./. 352 + if strings.HasPrefix(a, "./") { 353 + a = "./" + path.Clean(a) 354 + if a == "./." { 355 + a = "." 356 + } 357 + } else { 358 + a = path.Clean(a) 359 + } 360 + out = append(out, a) 361 + } 362 + return out 363 + } 364 + 365 + // isMetaPackage checks if name is a reserved package name that expands to multiple packages. 366 + func isMetaPackage(name string) bool { 367 + return name == "std" || name == "cmd" || name == "all" 368 + } 369 + 370 + // hasPathPrefix reports whether the path s begins with the 371 + // elements in prefix. 372 + func hasPathPrefix(s, prefix string) bool { 373 + switch { 374 + default: 375 + return false 376 + case len(s) == len(prefix): 377 + return s == prefix 378 + case len(s) > len(prefix): 379 + if prefix != "" && prefix[len(prefix)-1] == '/' { 380 + return strings.HasPrefix(s, prefix) 381 + } 382 + return s[len(prefix)] == '/' && s[:len(prefix)] == prefix 383 + } 384 + } 385 + 386 + // hasFilepathPrefix reports whether the path s begins with the 387 + // elements in prefix. 388 + func hasFilepathPrefix(s, prefix string) bool { 389 + switch { 390 + default: 391 + return false 392 + case len(s) == len(prefix): 393 + return s == prefix 394 + case len(s) > len(prefix): 395 + if prefix != "" && prefix[len(prefix)-1] == filepath.Separator { 396 + return strings.HasPrefix(s, prefix) 397 + } 398 + return s[len(prefix)] == filepath.Separator && s[:len(prefix)] == prefix 399 + } 400 + } 401 + 402 + // isStandardImportPath reports whether $GOROOT/src/path should be considered 403 + // part of the standard distribution. For historical reasons we allow people to add 404 + // their own code to $GOROOT instead of using $GOPATH, but we assume that 405 + // code will start with a domain name (dot in the first element). 406 + // 407 + // Note that this function is meant to evaluate whether a directory found in GOROOT 408 + // should be treated as part of the standard library. It should not be used to decide 409 + // that a directory found in GOPATH should be rejected: directories in GOPATH 410 + // need not have dots in the first element, and they just take their chances 411 + // with future collisions in the standard library. 412 + func isStandardImportPath(path string) bool { 413 + i := strings.Index(path, "/") 414 + if i < 0 { 415 + i = len(path) 416 + } 417 + elem := path[:i] 418 + return !strings.Contains(elem, ".") 419 + } 420 + 421 + // isRelativePath reports whether pattern should be interpreted as a directory 422 + // path relative to the current directory, as opposed to a pattern matching 423 + // import paths. 424 + func isRelativePath(pattern string) bool { 425 + return strings.HasPrefix(pattern, "./") || strings.HasPrefix(pattern, "../") || pattern == "." || pattern == ".." 426 + } 427 + 428 + // inDir checks whether path is in the file tree rooted at dir. 429 + // If so, inDir returns an equivalent path relative to dir. 430 + // If not, inDir returns an empty string. 431 + // inDir makes some effort to succeed even in the presence of symbolic links. 432 + // TODO(rsc): Replace internal/test.inDir with a call to this function for Go 1.12. 433 + func inDir(path, dir string) string { 434 + if rel := inDirLex(path, dir); rel != "" { 435 + return rel 436 + } 437 + xpath, err := filepath.EvalSymlinks(path) 438 + if err != nil || xpath == path { 439 + xpath = "" 440 + } else { 441 + if rel := inDirLex(xpath, dir); rel != "" { 442 + return rel 443 + } 444 + } 445 + 446 + xdir, err := filepath.EvalSymlinks(dir) 447 + if err == nil && xdir != dir { 448 + if rel := inDirLex(path, xdir); rel != "" { 449 + return rel 450 + } 451 + if xpath != "" { 452 + if rel := inDirLex(xpath, xdir); rel != "" { 453 + return rel 454 + } 455 + } 456 + } 457 + return "" 458 + } 459 + 460 + // inDirLex is like inDir but only checks the lexical form of the file names. 461 + // It does not consider symbolic links. 462 + // TODO(rsc): This is a copy of str.HasFilePathPrefix, modified to 463 + // return the suffix. Most uses of str.HasFilePathPrefix should probably 464 + // be calling InDir instead. 465 + func inDirLex(path, dir string) string { 466 + pv := strings.ToUpper(filepath.VolumeName(path)) 467 + dv := strings.ToUpper(filepath.VolumeName(dir)) 468 + path = path[len(pv):] 469 + dir = dir[len(dv):] 470 + switch { 471 + default: 472 + return "" 473 + case pv != dv: 474 + return "" 475 + case len(path) == len(dir): 476 + if path == dir { 477 + return "." 478 + } 479 + return "" 480 + case dir == "": 481 + return path 482 + case len(path) > len(dir): 483 + if dir[len(dir)-1] == filepath.Separator { 484 + if path[:len(dir)] == dir { 485 + return path[len(dir):] 486 + } 487 + return "" 488 + } 489 + if path[len(dir)] == filepath.Separator && path[:len(dir)] == dir { 490 + if len(path) == len(dir)+1 { 491 + return "." 492 + } 493 + return path[len(dir)+1:] 494 + } 495 + return "" 496 + } 497 + }
+175
cue/load/search_test.go
··· 1 + // Copyright 2018 The CUE Authors 2 + // 3 + // Licensed under the Apache License, Version 2.0 (the "License"); 4 + // you may not use this file except in compliance with the License. 5 + // You may obtain a copy of the License at 6 + // 7 + // http://www.apache.org/licenses/LICENSE-2.0 8 + // 9 + // Unless required by applicable law or agreed to in writing, software 10 + // distributed under the License is distributed on an "AS IS" BASIS, 11 + // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 + // See the License for the specific language governing permissions and 13 + // limitations under the License. 14 + 15 + package load 16 + 17 + import ( 18 + "strings" 19 + "testing" 20 + ) 21 + 22 + var matchPatternTests = ` 23 + pattern ... 24 + match foo 25 + 26 + pattern net 27 + match net 28 + not net/http 29 + 30 + pattern net/http 31 + match net/http 32 + not net 33 + 34 + pattern net... 35 + match net net/http netchan 36 + not not/http not/net/http 37 + 38 + # Special cases. Quoting docs: 39 + 40 + # First, /... at the end of the pattern can match an empty string, 41 + # so that net/... matches both net and packages in its subdirectories, like net/http. 42 + pattern net/... 43 + match net net/http 44 + not not/http not/net/http netchan 45 + 46 + # Second, any slash-separted pattern element containing a wildcard never 47 + # participates in a match of the "vendor" element in the path of a vendored 48 + # package, so that ./... does not match packages in subdirectories of 49 + # ./vendor or ./mycode/vendor, but ./vendor/... and ./mycode/vendor/... do. 50 + # Note, however, that a directory named vendor that itself contains code 51 + # is not a vendored package: cmd/vendor would be a command named vendor, 52 + # and the pattern cmd/... matches it. 53 + pattern ./... 54 + match ./vendor ./mycode/vendor 55 + not ./vendor/foo ./mycode/vendor/foo 56 + 57 + pattern ./vendor/... 58 + match ./vendor/foo ./vendor/foo/vendor 59 + not ./vendor/foo/vendor/bar 60 + 61 + pattern mycode/vendor/... 62 + match mycode/vendor mycode/vendor/foo mycode/vendor/foo/vendor 63 + not mycode/vendor/foo/vendor/bar 64 + 65 + pattern x/vendor/y 66 + match x/vendor/y 67 + not x/vendor 68 + 69 + pattern x/vendor/y/... 70 + match x/vendor/y x/vendor/y/z x/vendor/y/vendor x/vendor/y/z/vendor 71 + not x/vendor/y/vendor/z 72 + 73 + pattern .../vendor/... 74 + match x/vendor/y x/vendor/y/z x/vendor/y/vendor x/vendor/y/z/vendor 75 + ` 76 + 77 + func TestMatchPattern(t *testing.T) { 78 + testPatterns(t, "MatchPattern", matchPatternTests, func(pattern, name string) bool { 79 + return matchPattern(pattern)(name) 80 + }) 81 + } 82 + 83 + var treeCanMatchPatternTests = ` 84 + pattern ... 85 + match foo 86 + 87 + pattern net 88 + match net 89 + not net/http 90 + 91 + pattern net/http 92 + match net net/http 93 + 94 + pattern net... 95 + match net netchan net/http 96 + not not/http not/net/http 97 + 98 + pattern net/... 99 + match net net/http 100 + not not/http netchan 101 + 102 + pattern abc.../def 103 + match abcxyz 104 + not xyzabc 105 + 106 + pattern x/y/z/... 107 + match x x/y x/y/z x/y/z/w 108 + 109 + pattern x/y/z 110 + match x x/y x/y/z 111 + not x/y/z/w 112 + 113 + pattern x/.../y/z 114 + match x/a/b/c 115 + not y/x/a/b/c 116 + ` 117 + 118 + func TestTreeCanMatchPattern(t *testing.T) { 119 + testPatterns(t, "TreeCanMatchPattern", treeCanMatchPatternTests, func(pattern, name string) bool { 120 + return treeCanMatchPattern(pattern)(name) 121 + }) 122 + } 123 + 124 + var hasPathPrefixTests = []stringPairTest{ 125 + {"abc", "a", false}, 126 + {"a/bc", "a", true}, 127 + {"a", "a", true}, 128 + {"a/bc", "a/", true}, 129 + } 130 + 131 + func TestHasPathPrefix(t *testing.T) { 132 + testStringPairs(t, "hasPathPrefix", hasPathPrefixTests, hasPathPrefix) 133 + } 134 + 135 + type stringPairTest struct { 136 + in1 string 137 + in2 string 138 + out bool 139 + } 140 + 141 + func testStringPairs(t *testing.T, name string, tests []stringPairTest, f func(string, string) bool) { 142 + for _, tt := range tests { 143 + if out := f(tt.in1, tt.in2); out != tt.out { 144 + t.Errorf("%s(%q, %q) = %v, want %v", name, tt.in1, tt.in2, out, tt.out) 145 + } 146 + } 147 + } 148 + 149 + func testPatterns(t *testing.T, name, tests string, fn func(string, string) bool) { 150 + var patterns []string 151 + for _, line := range strings.Split(tests, "\n") { 152 + if i := strings.Index(line, "#"); i >= 0 { 153 + line = line[:i] 154 + } 155 + f := strings.Fields(line) 156 + if len(f) == 0 { 157 + continue 158 + } 159 + switch f[0] { 160 + default: 161 + t.Fatalf("unknown directive %q", f[0]) 162 + case "pattern": 163 + patterns = f[1:] 164 + case "match", "not": 165 + want := f[0] == "match" 166 + for _, pattern := range patterns { 167 + for _, in := range f[1:] { 168 + if fn(pattern, in) != want { 169 + t.Errorf("%s(%q, %q) = %v, want %v", name, pattern, in, !want, want) 170 + } 171 + } 172 + } 173 + } 174 + } 175 + }
+3
cue/load/test.cue
··· 1 + package test 2 + 3 + "Hello world!"
+1
cue/load/testdata/anon.cue
··· 1 + world: "World"
+1
cue/load/testdata/anon/anon.cue
··· 1 + world: "World"
cue/load/testdata/anon/dummy

This is a binary file and will not be displayed.

+14
cue/load/testdata/cue.mod
··· 1 + // Copyright 2018 The CUE Authors 2 + // 3 + // Licensed under the Apache License, Version 2.0 (the "License"); 4 + // you may not use this file except in compliance with the License. 5 + // You may obtain a copy of the License at 6 + // 7 + // http://www.apache.org/licenses/LICENSE-2.0 8 + // 9 + // Unless required by applicable law or agreed to in writing, software 10 + // distributed under the License is distributed on an "AS IS" BASIS, 11 + // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 + // See the License for the specific language governing permissions and 13 + // limitations under the License. 14 +
cue/load/testdata/empty/dummy

This is a binary file and will not be displayed.

+3
cue/load/testdata/hello/test.cue
··· 1 + package test 2 + 3 + "Hello world!"
+3
cue/load/testdata/ignored/ignored.cue
··· 1 + // +build alwaysignore 2 + 3 + package ignored
+5
cue/load/testdata/multi/file.cue
··· 1 + // Test data - not compiled. 2 + 3 + package main 4 + 5 + {}
+5
cue/load/testdata/multi/file_appengine.cue
··· 1 + // Test data - not compiled. 2 + 3 + package test_package 4 + 5 + {}
+3
cue/load/testdata/other/anon.cue
··· 1 + hello: "Hello \(world)" 2 + 3 + world: string
+5
cue/load/testdata/other/file/file.cue
··· 1 + // Test data - not compiled. 2 + 3 + package file 4 + 5 + {}
+9
cue/load/testdata/other/main.cue
··· 1 + // Test data - not compiled. 2 + 3 + package main 4 + 5 + import ( 6 + "./file" 7 + ) 8 + 9 + {}
+1
cue/load/testdata/test.cue
··· 1 + package test