error_locator.go - hugo - [fork] hugo port for 9front
(HTM) git clone https://git.drkhsh.at/hugo.git
(DIR) Log
(DIR) Files
(DIR) Refs
(DIR) Submodules
(DIR) README
(DIR) LICENSE
---
error_locator.go (4419B)
---
1 // Copyright 2024 The Hugo Authors. All rights reserved.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 // http://www.apache.org/licenses/LICENSE-2.0
7 //
8 // Unless required by applicable law or agreed to in writing, software
9 // distributed under the License is distributed on an "AS IS" BASIS,
10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 // See the License for the specific language governing permissions and
12 // limitations under the License.
13
14 // Package herrors contains common Hugo errors and error related utilities.
15 package herrors
16
17 import (
18 "io"
19 "path/filepath"
20 "strings"
21
22 "github.com/gohugoio/hugo/common/text"
23 )
24
25 // LineMatcher contains the elements used to match an error to a line
26 type LineMatcher struct {
27 Position text.Position
28 Error error
29
30 LineNumber int
31 Offset int
32 Line string
33 }
34
35 // LineMatcherFn is used to match a line with an error.
36 // It returns the column number or 0 if the line was found, but column could not be determined. Returns -1 if no line match.
37 type LineMatcherFn func(m LineMatcher) int
38
39 // SimpleLineMatcher simply matches by line number.
40 var SimpleLineMatcher = func(m LineMatcher) int {
41 if m.Position.LineNumber == m.LineNumber {
42 // We found the line, but don't know the column.
43 return 0
44 }
45 return -1
46 }
47
48 // NopLineMatcher is a matcher that always returns 1.
49 // This will effectively give line 1, column 1.
50 var NopLineMatcher = func(m LineMatcher) int {
51 return 1
52 }
53
54 // OffsetMatcher is a line matcher that matches by offset.
55 var OffsetMatcher = func(m LineMatcher) int {
56 if m.Offset+len(m.Line) >= m.Position.Offset {
57 // We found the line, but return 0 to signal that we want to determine
58 // the column from the error.
59 return 0
60 }
61 return -1
62 }
63
64 // ContainsMatcher is a line matcher that matches by line content.
65 func ContainsMatcher(text string) func(m LineMatcher) int {
66 return func(m LineMatcher) int {
67 if idx := strings.Index(m.Line, text); idx != -1 {
68 return idx + 1
69 }
70 return -1
71 }
72 }
73
74 // ErrorContext contains contextual information about an error. This will
75 // typically be the lines surrounding some problem in a file.
76 type ErrorContext struct {
77 // If a match will contain the matched line and up to 2 lines before and after.
78 // Will be empty if no match.
79 Lines []string
80
81 // The position of the error in the Lines above. 0 based.
82 LinesPos int
83
84 // The position of the content in the file. Note that this may be different from the error's position set
85 // in FileError.
86 Position text.Position
87
88 // The lexer to use for syntax highlighting.
89 // https://gohugo.io/content-management/syntax-highlighting/#list-of-chroma-highlighting-languages
90 ChromaLexer string
91 }
92
93 func chromaLexerFromType(fileType string) string {
94 switch fileType {
95 case "html", "htm":
96 return "go-html-template"
97 }
98 return fileType
99 }
100
101 func extNoDelimiter(filename string) string {
102 return strings.TrimPrefix(filepath.Ext(filename), ".")
103 }
104
105 func chromaLexerFromFilename(filename string) string {
106 if strings.Contains(filename, "layouts") {
107 return "go-html-template"
108 }
109
110 ext := extNoDelimiter(filename)
111 return chromaLexerFromType(ext)
112 }
113
114 func locateErrorInString(src string, matcher LineMatcherFn) *ErrorContext {
115 return locateError(strings.NewReader(src), &fileError{}, matcher)
116 }
117
118 func locateError(r io.Reader, le FileError, matches LineMatcherFn) *ErrorContext {
119 if le == nil {
120 panic("must provide an error")
121 }
122
123 ectx := &ErrorContext{LinesPos: -1, Position: text.Position{Offset: -1}}
124
125 b, err := io.ReadAll(r)
126 if err != nil {
127 return ectx
128 }
129
130 lines := strings.Split(string(b), "\n")
131
132 lineNo := 0
133 posBytes := 0
134
135 for li, line := range lines {
136 lineNo = li + 1
137 m := LineMatcher{
138 Position: le.Position(),
139 Error: le,
140 LineNumber: lineNo,
141 Offset: posBytes,
142 Line: line,
143 }
144 v := matches(m)
145 if ectx.LinesPos == -1 && v != -1 {
146 ectx.Position.LineNumber = lineNo
147 ectx.Position.ColumnNumber = v
148 break
149 }
150
151 posBytes += len(line)
152 }
153
154 if ectx.Position.LineNumber > 0 {
155 low := max(ectx.Position.LineNumber-3, 0)
156
157 if ectx.Position.LineNumber > 2 {
158 ectx.LinesPos = 2
159 } else {
160 ectx.LinesPos = ectx.Position.LineNumber - 1
161 }
162
163 high := min(ectx.Position.LineNumber+2, len(lines))
164
165 ectx.Lines = lines[low:high]
166
167 }
168
169 return ectx
170 }