pulumi/pkg/diag/sink.go
joeduffy ff0059cd7b Print relative filenames in errors
Error messages could get quite lengthy as the code was written previously,
because we always used the complete absolute path for the file in question.
This change "prettifies" this to be relative to whatever contextual path
the user has chosen during compilation.  This shortens messages considerably.
2016-11-15 18:00:43 -08:00

107 lines
2.7 KiB
Go

// Copyright 2016 Marapongo, Inc. All rights reserved.
package diag
import (
"bytes"
"fmt"
"os"
"path/filepath"
"strconv"
"github.com/golang/glog"
)
// Sink facilitates pluggable diagnostics messages.
type Sink interface {
// Count fetches the total number of diagnostics issued (errors plus warnings).
Count() int
// Errors fetches the number of errors issued.
Errors() int
// Warnings fetches the number of warnings issued.
Warnings() int
// Error issues a new error diagnostic.
Errorf(diag *Diag, args ...interface{})
// Warning issues a new warning diagnostic.
Warningf(diag *Diag, args ...interface{})
}
// DefaultDiags returns a default sink that simply logs output to stderr/stdout.
func DefaultSink(pwd string) Sink {
return &defaultSink{pwd: pwd}
}
// defaultSink is the default sink which logs output to stderr/stdout.
type defaultSink struct {
pwd string // an optional present working directory to which output paths will be relative to.
errors int // the number of errors that have been issued.
warnings int // the number of warnings that have been issued.
}
func (d *defaultSink) Count() int {
return d.errors + d.warnings
}
func (d *defaultSink) Errors() int {
return d.errors
}
func (d *defaultSink) Warnings() int {
return d.warnings
}
func (d *defaultSink) Errorf(diag *Diag, args ...interface{}) {
msg := d.stringify(diag, "error", args...)
if glog.V(3) {
glog.V(3).Infof("defaultSink::Error(%v)", msg)
}
fmt.Fprintln(os.Stderr, msg)
}
func (d *defaultSink) Warningf(diag *Diag, args ...interface{}) {
msg := d.stringify(diag, "warning", args...)
if glog.V(4) {
glog.V(4).Infof("defaultSink::Warning(%v)", msg)
}
fmt.Fprintln(os.Stdout, msg)
}
// stringify stringifies a diagnostic in the usual way (e.g., "error: MU123: Mu.yaml:7:39: error goes here\n").
func (d *defaultSink) stringify(diag *Diag, prefix string, args ...interface{}) string {
var buffer bytes.Buffer
buffer.WriteString(prefix)
buffer.WriteString(": ")
if diag.ID > 0 {
buffer.WriteString("MU")
buffer.WriteString(strconv.Itoa(int(diag.ID)))
buffer.WriteString(": ")
}
if diag.Doc != nil {
rel := diag.Doc.File
if d.pwd != "" {
// If a PWD is available, convert the file to be relative to it.
rel, _ = filepath.Rel(d.pwd, rel)
}
buffer.WriteString(rel)
if diag.Loc != nil && !diag.Loc.IsEmpty() {
buffer.WriteRune(':')
buffer.WriteString(strconv.Itoa(diag.Loc.Start.Ln))
buffer.WriteRune(':')
buffer.WriteString(strconv.Itoa(diag.Loc.Start.Col))
}
buffer.WriteString(": ")
}
buffer.WriteString(fmt.Sprintf(diag.Message, args...))
buffer.WriteRune('\n')
// TODO: support Clang-style caret diagnostics; e.g., see http://clang.llvm.org/diagnostics.html.
return buffer.String()
}