cue/build: adding positions to LoadFunc and supportive API
And add it to LoadFunc.
Issue #52
Change-Id: Icc6cf385897a6954cfa29ac5c93379d26e9bc954
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/2202
Reviewed-by: Marcel van Lohuizen <mpvl@google.com>
diff --git a/cue/build.go b/cue/build.go
index 59845be..67c2b52 100644
--- a/cue/build.go
+++ b/cue/build.go
@@ -33,7 +33,7 @@
ctxt *build.Context
}
-func dummyLoad(string) *build.Instance { return nil }
+func dummyLoad(token.Pos, string) *build.Instance { return nil }
func (r *Runtime) index() *index {
if r.idx == nil {
diff --git a/cue/build/import.go b/cue/build/import.go
index 40c2711..d63f7e4 100644
--- a/cue/build/import.go
+++ b/cue/build/import.go
@@ -24,7 +24,7 @@
"cuelang.org/go/cue/token"
)
-type LoadFunc func(path string) *Instance
+type LoadFunc func(pos token.Pos, path string) *Instance
func (inst *Instance) complete() error {
// TODO: handle case-insensitive collisions.
@@ -80,14 +80,15 @@
imp := c.imports[path]
if imp == nil {
- imp = inst.loadFunc(path)
+ pos := token.NoPos
+ if len(imported[path]) > 0 {
+ pos = imported[path][0]
+ }
+ imp = inst.loadFunc(pos, path)
if imp == nil {
continue
}
if imp.Err != nil {
- if len(imported[path]) > 0 {
- imp.Err = errors.Augment(imp.Err, imported[path][0])
- }
return imp.Err
}
imp.ImportPath = path
diff --git a/cue/build_test.go b/cue/build_test.go
index e02f80d..fcb7d1e 100644
--- a/cue/build_test.go
+++ b/cue/build_test.go
@@ -166,7 +166,7 @@
imports map[string]*bimport
}
-func (b *builder) load(path string) *build.Instance {
+func (b *builder) load(pos token.Pos, path string) *build.Instance {
p := b.ctxt.NewInstance(path, b.load)
bi := b.imports[path]
if bi == nil {
diff --git a/cue/errors/errors.go b/cue/errors/errors.go
index ad98138..99198e0 100644
--- a/cue/errors/errors.go
+++ b/cue/errors/errors.go
@@ -19,9 +19,8 @@
"io"
"sort"
- "github.com/mpvl/unique"
-
"cuelang.org/go/cue/token"
+ "github.com/mpvl/unique"
"golang.org/x/exp/errors"
"golang.org/x/exp/errors/fmt"
"golang.org/x/xerrors"
@@ -37,6 +36,30 @@
// The position points to the beginning of the offending value.
type Handler func(pos token.Position, msg string)
+// A Message implements the error interface as well as Message to allow
+// internationalized messages.
+type Message struct {
+ format string
+ args []interface{}
+}
+
+// NewMessage creates an error message for human consumption. The arguments
+// are for later consumption, allowing the message to be localized at a later
+// time.
+func NewMessage(format string, args []interface{}) Message {
+ return Message{format: format, args: args}
+}
+
+// Msg returns a printf-style format string and its arguments for human
+// consumption.
+func (m *Message) Msg() (format string, args []interface{}) {
+ return m.format, m.args
+}
+
+func (m *Message) Error() string {
+ return fmt.Sprintf(m.format, m.args...)
+}
+
// Error is the common error message.
type Error interface {
Position() token.Position
@@ -47,13 +70,39 @@
// // TODO: make Error an interface that returns a list of positions.
+// Newf creates an Error with the associated position and message.
+func Newf(pos token.Pos, format string, args ...interface{}) Error {
+ var p token.Position
+ if pos.IsValid() {
+ p = pos.Position()
+ }
+ return &posError{
+ pos: p,
+ Message: NewMessage(format, args),
+ }
+}
+
+// Wrapf creates an Error with the associated position and message. The provided
+// error is added for inspection context.
+func Wrapf(err error, pos token.Pos, format string, args ...interface{}) Error {
+ var p token.Position
+ if pos.IsValid() {
+ p = pos.Position()
+ }
+ return &posError{
+ pos: p,
+ Message: NewMessage(format, args),
+ err: err,
+ }
+}
+
// In an List, an error is represented by an *posError.
// The position Pos, if valid, points to the beginning of
// the offending token, and the error condition is described
// by Msg.
type posError struct {
pos token.Position
- msg string
+ Message
// The underlying error that triggered this one, if any.
err error
@@ -81,7 +130,7 @@
for _, a := range args {
switch x := a.(type) {
case string:
- e.msg = x
+ e.Message = NewMessage("%s", []interface{}{x})
case token.Position:
e.pos = x
case []token.Position:
@@ -127,10 +176,10 @@
func (e *posError) FormatError(p errors.Printer) error {
next := e.err
- if e.msg == "" {
+ if format, args := e.Msg(); format == "" {
next = errFormat(p, e.err)
} else {
- p.Print(e.msg)
+ p.Printf(format, args...)
}
if p.Detail() && e.pos.Filename != "" || e.pos.IsValid() {
p.Printf("%s", e.pos.String())
@@ -166,7 +215,7 @@
// AddNew adds an Error with given position and error message to an List.
func (p *List) AddNew(pos token.Position, msg string) {
- p.add(&posError{pos: pos, msg: msg})
+ p.add(&posError{pos: pos, Message: Message{format: msg}})
}
// Add adds an Error with given position and error message to an List.
diff --git a/cue/load/import.go b/cue/load/import.go
index 9585e33..89f0595 100644
--- a/cue/load/import.go
+++ b/cue/load/import.go
@@ -16,7 +16,6 @@
import (
"bytes"
- "fmt"
"log"
"os"
pathpkg "path"
@@ -30,6 +29,7 @@
"cuelang.org/go/cue/ast"
build "cuelang.org/go/cue/build"
"cuelang.org/go/cue/encoding"
+ "cuelang.org/go/cue/errors"
"cuelang.org/go/cue/parser"
"cuelang.org/go/cue/token"
)
@@ -67,7 +67,7 @@
// If an error occurs, importPkg sets the error in the returned instance,
// which then may contain partial information.
//
-func (l *loader) importPkg(path, srcDir string) *build.Instance {
+func (l *loader) importPkg(pos token.Pos, path, srcDir string) *build.Instance {
l.stk.Push(path)
defer l.stk.Pop()
@@ -117,7 +117,7 @@
if f.IsDir() {
continue
}
- if fp.add(dir, f.Name(), importComment) {
+ if fp.add(pos, dir, f.Name(), importComment) {
root = dir
}
if f.Name() == "cue.mod" {
@@ -160,14 +160,14 @@
// loadFunc creates a LoadFunc that can be used to create new build.Instances.
func (l *loader) loadFunc(parentPath string) build.LoadFunc {
- return func(path string) *build.Instance {
+ return func(pos token.Pos, path string) *build.Instance {
cfg := l.cfg
if !isLocalImport(path) {
// is it a builtin?
if strings.IndexByte(strings.Split(path, "/")[0], '.') == -1 {
if l.cfg.StdRoot != "" {
- return l.importPkg(path, l.cfg.StdRoot)
+ return l.importPkg(pos, path, l.cfg.StdRoot)
}
return nil
}
@@ -177,7 +177,7 @@
"import %q not found in the pkg directory", path))
return i
}
- return l.importPkg(path, filepath.Join(cfg.modRoot, "pkg"))
+ return l.importPkg(pos, path, filepath.Join(cfg.modRoot, "pkg"))
}
if strings.Contains(path, "@") {
@@ -187,7 +187,7 @@
return i
}
- return l.importPkg(path, parentPath)
+ return l.importPkg(pos, path, parentPath)
}
}
@@ -195,16 +195,16 @@
ctxt := &c.fileSystem
// path := p.ImportPath
if path == "" {
- return fmt.Errorf("import %q: invalid import path", path)
+ return errors.Newf(token.NoPos, "import %q: invalid import path", path)
}
if ctxt.isAbsPath(path) || strings.HasPrefix(path, "/") {
- return fmt.Errorf("load: absolute import path %q not allowed", path)
+ return errors.Newf(token.NoPos, "absolute import path %q not allowed", path)
}
if isLocalImport(path) {
if srcDir == "" {
- return fmt.Errorf("import %q: import relative to unknown directory", path)
+ return errors.Newf(token.NoPos, "import %q: import relative to unknown directory", path)
}
if !ctxt.isAbsPath(path) {
p.Dir = ctxt.joinPath(srcDir, path)
@@ -219,7 +219,7 @@
}
// package was not found
- return fmt.Errorf("cannot find package %q", path)
+ return errors.Newf(token.NoPos, "cannot find package %q", path)
}
func normPrefix(root, path string, isLocal bool) string {
@@ -308,7 +308,7 @@
return nil
}
-func (fp *fileProcessor) add(root, path string, mode importMode) (added bool) {
+func (fp *fileProcessor) add(pos token.Pos, root, path string, mode importMode) (added bool) {
fullPath := path
if !filepath.IsAbs(path) {
fullPath = filepath.Join(root, path)
@@ -361,7 +361,7 @@
return false
}
// TODO: package does not conform with requested.
- return badFile(fmt.Errorf("%s: found package %q; want %q", filename, pkg, fp.c.Package))
+ return badFile(errors.Newf(pos, "%s: found package %q; want %q", filename, pkg, fp.c.Package))
}
} else if fp.firstFile == "" {
p.PkgName = pkg
@@ -386,12 +386,12 @@
if line != 0 {
com, err := strconv.Unquote(qcom)
if err != nil {
- badFile(fmt.Errorf("%s:%d: cannot parse import comment", filename, line))
+ badFile(errors.Newf(pos, "%s:%d: cannot parse import comment", filename, line))
} else if p.ImportComment == "" {
p.ImportComment = com
fp.firstCommentFile = name
} else if p.ImportComment != com {
- badFile(fmt.Errorf("found import comments %q (%s) and %q (%s) in %s", p.ImportComment, fp.firstCommentFile, com, name, p.Dir))
+ badFile(errors.Newf(pos, "found import comments %q (%s) and %q (%s) in %s", p.ImportComment, fp.firstCommentFile, com, name, p.Dir))
}
}
}
diff --git a/cue/load/import_test.go b/cue/load/import_test.go
index 341999b..26caedc 100644
--- a/cue/load/import_test.go
+++ b/cue/load/import_test.go
@@ -21,6 +21,7 @@
"testing"
build "cuelang.org/go/cue/build"
+ "cuelang.org/go/cue/token"
)
const testdata = "./testdata/"
@@ -28,17 +29,17 @@
func getInst(pkg, cwd string) (*build.Instance, error) {
c, _ := (&Config{}).complete()
l := loader{cfg: c}
- p := l.importPkg(pkg, cwd)
+ p := l.importPkg(token.NoPos, pkg, cwd)
return p, p.Err
}
func TestDotSlashImport(t *testing.T) {
c, _ := (&Config{}).complete()
l := loader{cfg: c}
- p := l.importPkg(".", testdata+"other")
- err := p.Err
- if err != nil {
- t.Fatal(err)
+ p := l.importPkg(token.NoPos, ".", testdata+"other")
+ errl := p.Err
+ if errl != nil {
+ t.Fatal(errl)
}
if len(p.ImportPaths) != 1 || p.ImportPaths[0] != "./file" {
t.Fatalf("testdata/other: Imports=%v, want [./file]", p.ImportPaths)
diff --git a/cue/load/loader.go b/cue/load/loader.go
index 670bf30..eb4597d 100644
--- a/cue/load/loader.go
+++ b/cue/load/loader.go
@@ -20,8 +20,6 @@
// - go/build
import (
- "errors"
- "fmt"
"os"
pathpkg "path"
"path/filepath"
@@ -29,6 +27,7 @@
"unicode"
build "cuelang.org/go/cue/build"
+ "cuelang.org/go/cue/errors"
"cuelang.org/go/cue/token"
)
@@ -112,6 +111,7 @@
// cueFilesPackage creates a package for building a collection of CUE files
// (typically named on the command line).
func (l *loader) cueFilesPackage(files []string) *build.Instance {
+ pos := token.NoPos
cfg := l.cfg
// ModInit() // TODO: support modules
for _, f := range files {
@@ -135,9 +135,9 @@
}
if fi.IsDir() {
return cfg.newErrInstance(nil, path,
- fmt.Errorf("%s is a directory, should be a CUE file", file))
+ errors.Newf(pos, "%s is a directory, should be a CUE file", file))
}
- fp.add(cfg.Dir, file, allowAnonymous)
+ fp.add(pos, cfg.Dir, file, allowAnonymous)
}
// TODO: ModImportFromFiles(files)
diff --git a/cue/load/search.go b/cue/load/search.go
index 57ab9a5..ebb2c72 100644
--- a/cue/load/search.go
+++ b/cue/load/search.go
@@ -23,6 +23,7 @@
"strings"
build "cuelang.org/go/cue/build"
+ "cuelang.org/go/cue/token"
)
// A match represents the result of matching a single package pattern.
@@ -189,7 +190,7 @@
// due to invalid CUE source files. This means that directories
// containing parse errors will be built (and fail) instead of being
// silently skipped as not matching the pattern.
- p := l.importPkg("."+path[len(root):], root)
+ p := l.importPkg(token.NoPos, "."+path[len(root):], root)
if err := p.Err; err != nil && (p == nil || len(p.InvalidCUEFiles) == 0) {
switch err.(type) {
case nil:
@@ -329,7 +330,7 @@
continue
}
- pkg := l.importPkg(a, l.cfg.Dir)
+ pkg := l.importPkg(token.NoPos, a, l.cfg.Dir)
out = append(out, &match{Pattern: a, Literal: true, Pkgs: []*build.Instance{pkg}})
}
return out