prettyerror.go 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. package engine
  2. import (
  3. "bytes"
  4. "net/http"
  5. "strconv"
  6. "strings"
  7. )
  8. const (
  9. // Highlight of errors in the code
  10. preHighlight = "<font style='color: red !important'>"
  11. postHighlight = "</font>"
  12. )
  13. // Given a lowercase string for the language, return an approprite error page title
  14. func errorPageTitle(lang string) string {
  15. switch lang {
  16. case "":
  17. return "Error"
  18. case "css":
  19. return "CSS Error"
  20. case "gcss":
  21. return "GCSS Error"
  22. case "html":
  23. return "HTML Error"
  24. case "jsx":
  25. return "JSX Error"
  26. default:
  27. // string.Title(lang) was used here before, but staticcheck recommends against it
  28. return lang + " Error"
  29. }
  30. }
  31. // PrettyError serves an informative error page to the user
  32. // Takes a ResponseWriter, title (can be empty), filename, filebytes, errormessage and
  33. // programming/scripting/template language (i.e. "lua". Can be empty).
  34. func (ac *Config) PrettyError(w http.ResponseWriter, req *http.Request, filename string, filebytes []byte, errormessage, lang string) {
  35. // HTTP status
  36. // w.WriteHeader(http.StatusInternalServerError)
  37. w.WriteHeader(http.StatusOK)
  38. // HTTP content type
  39. w.Header().Add("Content-Type", "text/html;charset=utf-8")
  40. var (
  41. // If there is code to be displayed
  42. code string
  43. err error
  44. )
  45. // The line that the error refers to, for the case of Lua
  46. linenr := -1
  47. if len(filebytes) > 0 {
  48. if lang == "lua" {
  49. // If the first line of the error message has two colons, see if the second field is a number
  50. fields := strings.SplitN(errormessage, ":", 3)
  51. if len(fields) > 2 {
  52. // Extract the line number from the error message, if possible
  53. numberfield := fields[1]
  54. if strings.Contains(numberfield, "(") {
  55. numberfield = strings.Split(numberfield, "(")[0]
  56. }
  57. linenr, err = strconv.Atoi(numberfield)
  58. // Subtract one to make it a slice index instead of human-friendly line number
  59. linenr--
  60. // Set linenumber to -1 if the conversion failed
  61. if err != nil {
  62. linenr = -1
  63. }
  64. }
  65. } else if lang == "amber" {
  66. // If the error contains "- Line: ", extract the line number
  67. if strings.Contains(errormessage, "- Line: ") {
  68. fields := strings.SplitN(errormessage, "- Line: ", 2)
  69. if strings.Contains(fields[1], ",") {
  70. numberfields := strings.SplitN(fields[1], ",", 2)
  71. linenr, err = strconv.Atoi(strings.TrimSpace(numberfields[0]))
  72. // Subtract one to make it a slice index instead of human-friendly line number
  73. linenr--
  74. // Set linenumber to -1 if the conversion failed
  75. if err != nil {
  76. linenr = -1
  77. }
  78. }
  79. }
  80. }
  81. // Escape any HTML in the code, so that the pretty printer is not confused
  82. filebytes = bytes.ReplaceAll(filebytes, []byte("<"), []byte("&lt;"))
  83. // Modify the line that is to be highlighted
  84. bytelines := bytes.Split(filebytes, []byte("\n"))
  85. if (linenr >= 0) && (linenr < len(bytelines)) {
  86. bytelines[linenr] = []byte(preHighlight + string(bytelines[linenr]) + postHighlight)
  87. }
  88. // Build a string from the bytelines slice
  89. code = string(bytes.Join(bytelines, []byte("\n")))
  90. }
  91. // Set an appropriate title
  92. title := errorPageTitle(lang)
  93. // Set the highlight class
  94. langclass := lang
  95. // Turn off highlighting for some languages
  96. switch lang {
  97. case "", "amber", "gcss":
  98. langclass = "nohighlight"
  99. }
  100. // Highlighting for the error message
  101. errorclass := "json" // "nohighlight"
  102. // Inform the user of the error
  103. htmldata := []byte(`<!doctype html>
  104. <html>
  105. <head>
  106. <title>` + title + `</title>
  107. <style>
  108. body {
  109. background-color: #f0f0f0;
  110. color: #0b0b0b;
  111. font-family: Lato,'Trebuchet MS',Helvetica,sans-serif;
  112. font-weight: 300;
  113. margin: 3.5em;
  114. font-size: 1.3em;
  115. }
  116. h1 {
  117. color: #101010;
  118. }
  119. div {
  120. margin-bottom: 35pt;
  121. }
  122. #right {
  123. text-align: right;
  124. }
  125. #wrap {
  126. white-space: pre-wrap;
  127. }
  128. </style>
  129. </head>
  130. <body>
  131. <div style="font-size: 3em; font-weight: bold;">` + title + `</div>
  132. Contents of ` + filename + `:
  133. <div>
  134. <pre><code class="` + langclass + `">` + code + `</code></pre>
  135. </div>
  136. Error message:
  137. <div>
  138. <pre id="wrap"><code style="color: #A00000;" class="` + errorclass + `">` + strings.TrimSpace(errormessage) + `</code></pre>
  139. </div>
  140. <div id="right">` + ac.versionString + `</div>
  141. `)
  142. if ac.autoRefresh {
  143. // Insert JavaScript for refreshing the page into the generated HTML
  144. htmldata = ac.InsertAutoRefresh(req, htmldata)
  145. }
  146. w.Write(htmldata)
  147. }