Add API Framework Revel Source Files
[iec.git] / src / foundation / api / revel / errors.go
diff --git a/src/foundation/api/revel/errors.go b/src/foundation/api/revel/errors.go
new file mode 100644 (file)
index 0000000..dc3807a
--- /dev/null
@@ -0,0 +1,155 @@
+// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
+// Revel Framework source code and usage is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package revel
+
+import (
+       "fmt"
+       "path/filepath"
+       "runtime/debug"
+       "strconv"
+       "strings"
+)
+
+// Error description, used as an argument to the error template.
+type Error struct {
+       SourceType               string   // The type of source that failed to build.
+       Title, Path, Description string   // Description of the error, as presented to the user.
+       Line, Column             int      // Where the error was encountered.
+       SourceLines              []string // The entire source file, split into lines.
+       Stack                    string   // The raw stack trace string from debug.Stack().
+       MetaError                string   // Error that occurred producing the error page.
+       Link                     string   // A configurable link to wrap the error source in
+}
+
+// SourceLine structure to hold the per-source-line details.
+type SourceLine struct {
+       Source  string
+       Line    int
+       IsError bool
+}
+
+// NewErrorFromPanic method finds the deepest stack from in user code and
+// provide a code listing of that, on the line that eventually triggered
+// the panic.  Returns nil if no relevant stack frame can be found.
+func NewErrorFromPanic(err interface{}) *Error {
+
+       // Parse the filename and line from the originating line of app code.
+       // /Users/robfig/code/gocode/src/revel/examples/booking/app/controllers/hotels.go:191 (0x44735)
+       stack := string(debug.Stack())
+       frame, basePath := findRelevantStackFrame(stack)
+       if frame == -1 {
+               return nil
+       }
+
+       stack = stack[frame:]
+       stackElement := stack[:strings.Index(stack, "\n")]
+       colonIndex := strings.LastIndex(stackElement, ":")
+       filename := stackElement[:colonIndex]
+       var line int
+       fmt.Sscan(stackElement[colonIndex+1:], &line)
+
+       // Show an error page.
+       description := "Unspecified error"
+       if err != nil {
+               description = fmt.Sprint(err)
+       }
+       lines, readErr := ReadLines(filename)
+       if readErr != nil {
+               utilLog.Error("Unable to read file", "file", filename, "error", readErr)
+       }
+       return &Error{
+               Title:       "Runtime Panic",
+               Path:        filename[len(basePath):],
+               Line:        line,
+               Description: description,
+               SourceLines: lines,
+               Stack:       stack,
+       }
+}
+
+// Error method constructs a plaintext version of the error, taking
+// account that fields are optionally set. Returns e.g. Compilation Error
+// (in views/header.html:51): expected right delim in end; got "}"
+func (e *Error) Error() string {
+       loc := ""
+       if e.Path != "" {
+               line := ""
+               if e.Line != 0 {
+                       line = fmt.Sprintf(":%d", e.Line)
+               }
+               loc = fmt.Sprintf("(in %s%s)", e.Path, line)
+       }
+       header := loc
+       if e.Title != "" {
+               if loc != "" {
+                       header = fmt.Sprintf("%s %s: ", e.Title, loc)
+               } else {
+                       header = fmt.Sprintf("%s: ", e.Title)
+               }
+       }
+       return fmt.Sprintf("%s%s Stack: %s", header, e.Description, e.Stack)
+}
+
+// ContextSource method returns a snippet of the source around
+// where the error occurred.
+func (e *Error) ContextSource() []SourceLine {
+       if e.SourceLines == nil {
+               return nil
+       }
+       start := (e.Line - 1) - 5
+       if start < 0 {
+               start = 0
+       }
+       end := (e.Line - 1) + 5
+       if end > len(e.SourceLines) {
+               end = len(e.SourceLines)
+       }
+
+       lines := make([]SourceLine, end-start)
+       for i, src := range e.SourceLines[start:end] {
+               fileLine := start + i + 1
+               lines[i] = SourceLine{src, fileLine, fileLine == e.Line}
+       }
+       return lines
+}
+
+// SetLink method prepares a link and assign to Error.Link attribute
+func (e *Error) SetLink(errorLink string) {
+       errorLink = strings.Replace(errorLink, "{{Path}}", e.Path, -1)
+       errorLink = strings.Replace(errorLink, "{{Line}}", strconv.Itoa(e.Line), -1)
+
+       e.Link = "<a href=" + errorLink + ">" + e.Path + ":" + strconv.Itoa(e.Line) + "</a>"
+}
+
+// Return the character index of the first relevant stack frame, or -1 if none were found.
+// Additionally it returns the base path of the tree in which the identified code resides.
+func findRelevantStackFrame(stack string) (int, string) {
+       // Find first item in SourcePath that isn't in RevelPath.
+       // If first item is in RevelPath, keep track of position, trim and check again.
+       partialStack := stack
+       sourcePath := filepath.ToSlash(SourcePath)
+       revelPath := filepath.ToSlash(RevelPath)
+       sumFrame := 0
+       for {
+               frame := strings.Index(partialStack, sourcePath)
+               revelFrame := strings.Index(partialStack, revelPath)
+
+               if frame == -1 {
+                       break
+               } else if frame != revelFrame {
+                       return sumFrame + frame, SourcePath
+               } else {
+                       // Need to at least trim off the first character so this frame isn't caught again.
+                       partialStack = partialStack[frame+1:]
+                       sumFrame += frame + 1
+               }
+       }
+       for _, module := range Modules {
+               if frame := strings.Index(stack, filepath.ToSlash(module.Path)); frame != -1 {
+                       return frame, module.Path
+               }
+       }
+       return -1, ""
+}