pulumi/pkg/diag/sink.go
joeduffy 2dd8665c46 Prepare for semantic analysis
This change begins to lay the groundwork for doing semantic analysis and
lowering to the cloud target's representation.  In particular:

* Split the mu/schema package.  There is now mu/ast which contains the
  core types and mu/encoding which concerns itself with JSON and YAML
  serialization.

* Notably I am *not* yet introducing a second AST form.  Instead, we will
  keep the parse tree and AST unified for the time being.  I envision very
  little difference between them -- at least for now -- and so this keeps
  things simpler, at the expense of two downsides: 1) the trees will be
  mutable (which turns out to be a good thing for performance), and 2) some
  fields will need to be ignored during de/serialization.  We can always
  revisit this later when and if the need to split them arises.

* Add a binder phase.  It is currently a no-op.
2016-11-16 09:29:44 -08:00

116 lines
3 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{})
// Stringify stringifies a diagnostic in the usual way (e.g., "error: MU123: Mu.yaml:7:39: error goes here\n").
Stringify(diag *Diag, prefix string, args ...interface{}) string
}
// DefaultDiags returns a default sink that simply logs output to stderr/stdout.
func DefaultSink(pwd string) Sink {
return &defaultSink{pwd: pwd}
}
const DefaultSinkIDPrefix = "MU"
const DefaultSinkErrorPrefix = "error"
const DefaultSinkWarningPrefix = "warning"
// 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, DefaultSinkErrorPrefix, args...)
if glog.V(3) {
glog.V(3).Infof("defaultSink::Error(%v)", msg)
}
fmt.Fprintf(os.Stderr, msg)
}
func (d *defaultSink) Warningf(diag *Diag, args ...interface{}) {
msg := d.Stringify(diag, DefaultSinkWarningPrefix, args...)
if glog.V(4) {
glog.V(4).Infof("defaultSink::Warning(%v)", msg)
}
fmt.Fprintf(os.Stdout, msg)
}
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(DefaultSinkIDPrefix)
buffer.WriteString(strconv.Itoa(int(diag.ID)))
buffer.WriteString(": ")
}
if diag.Doc != nil {
file := diag.Doc.File
if d.pwd != "" {
// If a PWD is available, try to create a relative path.
rel, err := filepath.Rel(d.pwd, file)
if err == nil {
file = rel
}
}
buffer.WriteString(file)
if diag.Loc != nil && !diag.Loc.IsEmpty() {
buffer.WriteRune(':')
buffer.WriteString(strconv.Itoa(diag.Loc.From.Ln))
buffer.WriteRune(':')
buffer.WriteString(strconv.Itoa(diag.Loc.From.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()
}