From 05cfa809f22ab65baf8dc97d66b78e8cf8309768 Mon Sep 17 00:00:00 2001 From: Michael McLoughlin Date: Fri, 4 Jan 2019 15:01:29 -0800 Subject: [PATCH] build: include position information in errors Updates #5 --- build/context.go | 3 ++- build/error.go | 30 ++++++++++++++++++++++++++++++ src/src.go | 34 +++++++++++++++++++++++++++++++++- 3 files changed, 65 insertions(+), 2 deletions(-) create mode 100644 build/error.go diff --git a/build/context.go b/build/context.go index 384f081..b7a0637 100644 --- a/build/context.go +++ b/build/context.go @@ -153,7 +153,8 @@ func (c *Context) activeglobal() *avo.Global { } func (c *Context) AddError(err error) { - c.errs = append(c.errs, err) + e := exterr(err) + c.errs = append(c.errs, e) } func (c *Context) AddErrorMessage(msg string) { diff --git a/build/error.go b/build/error.go new file mode 100644 index 0000000..80753cb --- /dev/null +++ b/build/error.go @@ -0,0 +1,30 @@ +package build + +import ( + "github.com/mmcloughlin/avo/internal/stack" + "github.com/mmcloughlin/avo/src" +) + +// Error represents an error during building, optionally tagged with the position at which it happened. +type Error struct { + Position src.Position + Err error +} + +// exterr constructs an Error with position derived from the first frame in the +// call stack outside this package. +func exterr(err error) Error { + e := Error{Err: err} + if f := stack.ExternalCaller(); f != nil { + e.Position = src.FramePosition(*f).Relwd() + } + return e +} + +func (e Error) Error() string { + msg := e.Err.Error() + if e.Position.IsValid() { + return e.Position.String() + ": " + msg + } + return msg +} diff --git a/src/src.go b/src/src.go index a4a5da9..fa85720 100644 --- a/src/src.go +++ b/src/src.go @@ -1,6 +1,11 @@ package src -import "strconv" +import ( + "os" + "path/filepath" + "runtime" + "strconv" +) // Position represents a position in a source file. type Position struct { @@ -8,6 +13,13 @@ type Position struct { Line int // 1-up } +func FramePosition(f runtime.Frame) Position { + return Position{ + Filename: f.File, + Line: f.Line, + } +} + // IsValid reports whether the position is valid: Line must be positive, but // Filename may be empty. func (p Position) IsValid() bool { @@ -26,3 +38,23 @@ func (p Position) String() string { s += strconv.Itoa(p.Line) return s } + +// Rel returns Position relative to basepath. If the given filename cannot be +// expressed relative to basepath the position will be returned unchanged. +func (p Position) Rel(basepath string) Position { + q := p + if rel, err := filepath.Rel(basepath, q.Filename); err == nil { + q.Filename = rel + } + return q +} + +// Relwd returns Position relative to the current working directory. Returns p +// unchanged if the working directory cannot be determined, or the filename +// cannot be expressed relative to the working directory. +func (p Position) Relwd() Position { + if wd, err := os.Getwd(); err == nil { + return p.Rel(wd) + } + return p +}