"net/http"
"net/url"
"path"
+ "regexp"
"strconv"
"strings"
+ texttemplate "text/template"
"time"
"code.gitea.io/gitea/models/db"
ctx.Redirect(setting.AppSubURL + "/")
}
+var templateExecutingErr = regexp.MustCompile(`^template: (.*):([1-9][0-9]*):([1-9][0-9]*): executing (?:"(.*)" at <(.*)>: )?`)
+
// HTML calls Context.HTML and renders the template to HTTP response
func (ctx *Context) HTML(status int, name base.TplName) {
log.Debug("Template: %s", name)
ctx.PlainText(http.StatusInternalServerError, "Unable to find status/500 template")
return
}
+ if execErr, ok := err.(texttemplate.ExecError); ok {
+ if groups := templateExecutingErr.FindStringSubmatch(err.Error()); len(groups) > 0 {
+ errorTemplateName, lineStr, posStr := groups[1], groups[2], groups[3]
+ target := ""
+ if len(groups) == 6 {
+ target = groups[5]
+ }
+ line, _ := strconv.Atoi(lineStr) // Cannot error out as groups[2] is [1-9][0-9]*
+ pos, _ := strconv.Atoi(posStr) // Cannot error out as groups[3] is [1-9][0-9]*
+ filename, filenameErr := templates.GetAssetFilename("templates/" + errorTemplateName + ".tmpl")
+ if filenameErr != nil {
+ filename = "(template) " + errorTemplateName
+ }
+ if errorTemplateName != string(name) {
+ filename += " (subtemplate of " + string(name) + ")"
+ }
+ err = fmt.Errorf("%w\nin template file %s:\n%s", err, filename, templates.GetLineFromTemplate(errorTemplateName, line, target, pos))
+ } else {
+ filename, filenameErr := templates.GetAssetFilename("templates/" + execErr.Name + ".tmpl")
+ if filenameErr != nil {
+ filename = "(template) " + execErr.Name
+ }
+ if execErr.Name != string(name) {
+ filename += " (subtemplate of " + string(name) + ")"
+ }
+ err = fmt.Errorf("%w\nin template file %s", err, filename)
+ }
+ }
ctx.ServerError("Render failed", err)
}
}
lineNumber, _ := strconv.Atoi(lineNumberStr)
- line := getLineFromAsset(templateName, lineNumber, "")
+ line := GetLineFromTemplate(templateName, lineNumber, "", -1)
return "PANIC: Unable to compile templates!\n%s in template file %s at line %d:\n\n%s\nStacktrace:\n\n%s", []interface{}{message, filename, lineNumber, log.NewColoredValue(line, log.Reset), log.Stack(2)}
}
lineNumber, _ := strconv.Atoi(lineNumberStr)
- line := getLineFromAsset(templateName, lineNumber, functionName)
+ line := GetLineFromTemplate(templateName, lineNumber, functionName, -1)
return "PANIC: Unable to compile templates!\nUndefined function %q in template file %s at line %d:\n\n%s", []interface{}{functionName, filename, lineNumber, log.NewColoredValue(line, log.Reset)}
}
lineNumber, _ := strconv.Atoi(lineNumberStr)
- line := getLineFromAsset(templateName, lineNumber, unexpected)
+ line := GetLineFromTemplate(templateName, lineNumber, unexpected, -1)
return "PANIC: Unable to compile templates!\nUnexpected %q in template file %s at line %d:\n\n%s", []interface{}{unexpected, filename, lineNumber, log.NewColoredValue(line, log.Reset)}
}
lineNumber, _ := strconv.Atoi(lineNumberStr)
- line := getLineFromAsset(templateName, lineNumber, unexpected)
+ line := GetLineFromTemplate(templateName, lineNumber, unexpected, -1)
return "PANIC: Unable to compile templates!\nMissing end with unexpected %q in template file %s at line %d:\n\n%s", []interface{}{unexpected, filename, lineNumber, log.NewColoredValue(line, log.Reset)}
}
const dashSeparator = "----------------------------------------------------------------------\n"
-func getLineFromAsset(templateName string, targetLineNum int, target string) string {
+// GetLineFromTemplate returns a line from a template with some context
+func GetLineFromTemplate(templateName string, targetLineNum int, target string, position int) string {
bs, err := GetAsset("templates/" + templateName + ".tmpl")
if err != nil {
return fmt.Sprintf("(unable to read template file: %v)", err)
// If there is a provided target to look for in the line add a pointer to it
// e.g. ^^^^^^^
if target != "" {
- idx := bytes.Index(lineBs, []byte(target))
-
- if idx >= 0 {
- // take the current line and replace preceding text with whitespace (except for tab)
- for i := range lineBs[:idx] {
- if lineBs[i] != '\t' {
- lineBs[i] = ' '
- }
+ targetPos := bytes.Index(lineBs, []byte(target))
+ if targetPos >= 0 {
+ position = targetPos
+ }
+ }
+ if position >= 0 {
+ // take the current line and replace preceding text with whitespace (except for tab)
+ for i := range lineBs[:position] {
+ if lineBs[i] != '\t' {
+ lineBs[i] = ' '
}
+ }
- // write the preceding "space"
- _, _ = sb.Write(lineBs[:idx])
+ // write the preceding "space"
+ _, _ = sb.Write(lineBs[:position])
- // Now write the ^^ pointer
- _, _ = sb.WriteString(strings.Repeat("^", len(target)))
- _ = sb.WriteByte('\n')
+ // Now write the ^^ pointer
+ targetLen := len(target)
+ if targetLen == 0 {
+ targetLen = 1
}
+ _, _ = sb.WriteString(strings.Repeat("^", targetLen))
+ _ = sb.WriteByte('\n')
}
// Finally write the footer