diff --git a/_gen.go b/_gen.go deleted file mode 100644 index 9828a2a..0000000 --- a/_gen.go +++ /dev/null @@ -1,7 +0,0 @@ -package main - -import ( - _ "github.com/clipperhouse/set" - _ "github.com/clipperhouse/slice" - _ "github.com/clipperhouse/stringer" -) diff --git a/error_formatter.go b/error_formatter.go index c7e559c..e4e1d37 100644 --- a/error_formatter.go +++ b/error_formatter.go @@ -18,25 +18,50 @@ package jsonnet import ( "bytes" "fmt" + "io" - "github.com/fatih/color" "github.com/google/go-jsonnet/ast" "github.com/google/go-jsonnet/parser" ) -type ErrorFormatter struct { - // MaxStackTraceSize is the maximum length of stack trace before cropping - MaxStackTraceSize int +type ErrorFormatter interface { + // Format static, runtime, and unexpected errors prior to printing them. + Format(err error) string + + // Set the the maximum length of stack trace before cropping. + SetMaxStackTraceSize(size int) + + // Set the color formatter for the location color. + SetColorFormatter(color ColorFormatter) +} + +type ColorFormatter func(w io.Writer, f string, a ...interface{}) (n int, err error) + +var _ ErrorFormatter = &termErrorFormatter{} + +type termErrorFormatter struct { + // maxStackTraceSize is the maximum length of stack trace before cropping + maxStackTraceSize int // Examples of current state of the art. // http://elm-lang.org/blog/compiler-errors-for-humans // https://clang.llvm.org/diagnostics.html - pretty bool - colorful bool - SP *ast.SourceProvider + color ColorFormatter + pretty bool + + // sp is currently never set, but is used to format locations. + sp *ast.SourceProvider } -func (ef *ErrorFormatter) format(err error) string { +func (ef *termErrorFormatter) SetMaxStackTraceSize(size int) { + ef.maxStackTraceSize = size +} + +func (ef *termErrorFormatter) SetColorFormatter(color ColorFormatter) { + ef.color = color +} + +func (ef *termErrorFormatter) Format(err error) string { switch err := err.(type) { case RuntimeError: return ef.formatRuntime(&err) @@ -47,11 +72,11 @@ func (ef *ErrorFormatter) format(err error) string { } } -func (ef *ErrorFormatter) formatRuntime(err *RuntimeError) string { +func (ef *termErrorFormatter) formatRuntime(err *RuntimeError) string { return err.Error() + "\n" + ef.buildStackTrace(err.StackTrace) } -func (ef *ErrorFormatter) formatStatic(err *parser.StaticError) string { +func (ef *termErrorFormatter) formatStatic(err *parser.StaticError) string { var buf bytes.Buffer buf.WriteString(err.Error() + "\n") ef.showCode(&buf, err.Loc) @@ -60,15 +85,15 @@ func (ef *ErrorFormatter) formatStatic(err *parser.StaticError) string { const bugURL = "https://github.com/google/go-jsonnet/issues" -func (ef *ErrorFormatter) formatInternal(err error) string { +func (ef *termErrorFormatter) formatInternal(err error) string { return "INTERNAL ERROR: " + err.Error() + "\n" + "Please report a bug here: " + bugURL + "\n" } -func (ef *ErrorFormatter) showCode(buf *bytes.Buffer, loc ast.LocationRange) { +func (ef *termErrorFormatter) showCode(buf *bytes.Buffer, loc ast.LocationRange) { errFprintf := fmt.Fprintf - if ef.colorful { - errFprintf = color.New(color.FgRed).Fprintf + if ef.color != nil { + errFprintf = ef.color } if loc.WithCode() { // TODO(sbarzowski) include line numbers @@ -76,15 +101,15 @@ func (ef *ErrorFormatter) showCode(buf *bytes.Buffer, loc ast.LocationRange) { fmt.Fprintf(buf, "\n") beginning := ast.LineBeginning(&loc) ending := ast.LineEnding(&loc) - fmt.Fprintf(buf, "%v", ef.SP.GetSnippet(beginning)) - errFprintf(buf, "%v", ef.SP.GetSnippet(loc)) - fmt.Fprintf(buf, "%v", ef.SP.GetSnippet(ending)) + fmt.Fprintf(buf, "%v", ef.sp.GetSnippet(beginning)) + errFprintf(buf, "%v", ef.sp.GetSnippet(loc)) + fmt.Fprintf(buf, "%v", ef.sp.GetSnippet(ending)) buf.WriteByte('\n') } fmt.Fprintf(buf, "\n") } -func (ef *ErrorFormatter) frame(frame *TraceFrame, buf *bytes.Buffer) { +func (ef *termErrorFormatter) frame(frame *TraceFrame, buf *bytes.Buffer) { // TODO(sbarzowski) tabs are probably a bad idea fmt.Fprintf(buf, "\t%v\t%v\n", frame.Loc.String(), frame.Name) if ef.pretty { @@ -92,10 +117,10 @@ func (ef *ErrorFormatter) frame(frame *TraceFrame, buf *bytes.Buffer) { } } -func (ef *ErrorFormatter) buildStackTrace(frames []TraceFrame) string { +func (ef *termErrorFormatter) buildStackTrace(frames []TraceFrame) string { // https://github.com/google/jsonnet/blob/master/core/libjsonnet.cpp#L594 - maxAbove := ef.MaxStackTraceSize / 2 - maxBelow := ef.MaxStackTraceSize - maxAbove + maxAbove := ef.maxStackTraceSize / 2 + maxBelow := ef.maxStackTraceSize - maxAbove var buf bytes.Buffer sz := len(frames) for i := 0; i < sz; i++ { @@ -103,7 +128,7 @@ func (ef *ErrorFormatter) buildStackTrace(frames []TraceFrame) string { if ef.pretty { fmt.Fprintf(&buf, "-------------------------------------------------\n") } - if ef.MaxStackTraceSize > 0 && i >= maxAbove && i < sz-maxBelow { + if ef.maxStackTraceSize > 0 && i >= maxAbove && i < sz-maxBelow { if ef.pretty { fmt.Fprintf(&buf, "\t... (skipped %v frames)\n", sz-maxAbove-maxBelow) } else { diff --git a/jsonnet/cmd.go b/jsonnet/cmd.go index 0f18470..61292cb 100644 --- a/jsonnet/cmd.go +++ b/jsonnet/cmd.go @@ -27,6 +27,7 @@ import ( "strconv" "strings" + "github.com/fatih/color" "github.com/google/go-jsonnet" ) @@ -185,12 +186,13 @@ func getVarFile(s string) (string, string, error) { } type processArgsStatus = int + const ( - processArgsStatusContinue = iota + processArgsStatusContinue = iota processArgsStatusSuccessUsage = iota processArgsStatusFailureUsage = iota - processArgsStatusSuccess = iota - processArgsStatusFailure = iota + processArgsStatusSuccess = iota + processArgsStatusFailure = iota ) func processArgs(givenArgs []string, config *config, vm *jsonnet.VM) (processArgsStatus, error) { @@ -304,7 +306,7 @@ func processArgs(givenArgs []string, config *config, vm *jsonnet.VM) (processArg if l < 0 { return processArgsStatusFailure, fmt.Errorf("ERROR: Invalid --max-trace value: %d", l) } - vm.ErrorFormatter.MaxStackTraceSize = l + vm.ErrorFormatter.SetMaxStackTraceSize(l) } else if arg == "-m" || arg == "--multi" { config.evalMulti = true outputDir := nextArg(&i, args) @@ -501,6 +503,7 @@ func main() { } vm := jsonnet.MakeVM() + vm.ErrorFormatter.SetColorFormatter(color.New(color.FgRed).Fprintf) config := makeConfig() status, err := processArgs(os.Args[1:], &config, vm) diff --git a/main_test.go b/main_test.go index 5649a64..f5c0e54 100644 --- a/main_test.go +++ b/main_test.go @@ -90,7 +90,7 @@ func TestMain(t *testing.T) { } mainTests = append(mainTests, mainTest{name: name, input: input, golden: golden, meta: &meta}) } - errFormatter := ErrorFormatter{pretty: true, MaxStackTraceSize: 9} + errFormatter := termErrorFormatter{pretty: true, maxStackTraceSize: 9} for _, test := range mainTests { t.Run(test.name, func(t *testing.T) { vm := MakeVM() @@ -127,7 +127,7 @@ func TestMain(t *testing.T) { if err != nil { // TODO(sbarzowski) perhaps somehow mark that we are processing // an error. But for now we can treat them the same. - output = errFormatter.format(err) + output = errFormatter.Format(err) output += "\n" } else { output = rawOutput.(string) @@ -226,9 +226,9 @@ var minimalErrorTests = []errorFormattingTest{ } func TestMinimalError(t *testing.T) { - formatter := ErrorFormatter{MaxStackTraceSize: 20} + formatter := termErrorFormatter{maxStackTraceSize: 20} genericTestErrorMessage(t, minimalErrorTests, func(r RuntimeError) string { - return formatter.format(r) + return formatter.Format(r) }) } diff --git a/vm.go b/vm.go index 0a62fca..ef089ae 100644 --- a/vm.go +++ b/vm.go @@ -58,7 +58,7 @@ func MakeVM() *VM { ext: make(vmExtMap), tla: make(vmExtMap), nativeFuncs: make(map[string]*NativeFunction), - ErrorFormatter: ErrorFormatter{pretty: false, colorful: false, MaxStackTraceSize: 20}, + ErrorFormatter: &termErrorFormatter{pretty: false, maxStackTraceSize: 20}, importer: &FileImporter{}, } } @@ -132,7 +132,7 @@ func (vm *VM) NativeFunction(f *NativeFunction) { func (vm *VM) EvaluateSnippet(filename string, snippet string) (json string, formattedErr error) { output, err := vm.evaluateSnippet(filename, snippet, evalKindRegular) if err != nil { - return "", errors.New(vm.ErrorFormatter.format(err)) + return "", errors.New(vm.ErrorFormatter.Format(err)) } json = output.(string) return @@ -145,7 +145,7 @@ func (vm *VM) EvaluateSnippet(filename string, snippet string) (json string, for func (vm *VM) EvaluateSnippetStream(filename string, snippet string) (docs []string, formattedErr error) { output, err := vm.evaluateSnippet(filename, snippet, evalKindStream) if err != nil { - return nil, errors.New(vm.ErrorFormatter.format(err)) + return nil, errors.New(vm.ErrorFormatter.Format(err)) } docs = output.([]string) return @@ -158,7 +158,7 @@ func (vm *VM) EvaluateSnippetStream(filename string, snippet string) (docs []str func (vm *VM) EvaluateSnippetMulti(filename string, snippet string) (files map[string]string, formattedErr error) { output, err := vm.evaluateSnippet(filename, snippet, evalKindMulti) if err != nil { - return nil, errors.New(vm.ErrorFormatter.format(err)) + return nil, errors.New(vm.ErrorFormatter.Format(err)) } files = output.(map[string]string) return