github.com/anakojm/hugo-katex@v0.0.0-20231023141351-42d6f5de9c0b/common/herrors/error_locator.go (about) 1 // Copyright 2022 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 78 // If a match will contain the matched line and up to 2 lines before and after. 79 // Will be empty if no match. 80 Lines []string 81 82 // The position of the error in the Lines above. 0 based. 83 LinesPos int 84 85 // The position of the content in the file. Note that this may be different from the error's position set 86 // in FileError. 87 Position text.Position 88 89 // The lexer to use for syntax highlighting. 90 // https://gohugo.io/content-management/syntax-highlighting/#list-of-chroma-highlighting-languages 91 ChromaLexer string 92 } 93 94 func chromaLexerFromType(fileType string) string { 95 switch fileType { 96 case "html", "htm": 97 return "go-html-template" 98 } 99 return fileType 100 } 101 102 func extNoDelimiter(filename string) string { 103 return strings.TrimPrefix(filepath.Ext(filename), ".") 104 } 105 106 func chromaLexerFromFilename(filename string) string { 107 if strings.Contains(filename, "layouts") { 108 return "go-html-template" 109 } 110 111 ext := extNoDelimiter(filename) 112 return chromaLexerFromType(ext) 113 } 114 115 func locateErrorInString(src string, matcher LineMatcherFn) *ErrorContext { 116 return locateError(strings.NewReader(src), &fileError{}, matcher) 117 } 118 119 func locateError(r io.Reader, le FileError, matches LineMatcherFn) *ErrorContext { 120 if le == nil { 121 panic("must provide an error") 122 } 123 124 ectx := &ErrorContext{LinesPos: -1, Position: text.Position{Offset: -1}} 125 126 b, err := io.ReadAll(r) 127 if err != nil { 128 return ectx 129 } 130 131 lines := strings.Split(string(b), "\n") 132 133 lineNo := 0 134 posBytes := 0 135 136 for li, line := range lines { 137 lineNo = li + 1 138 m := LineMatcher{ 139 Position: le.Position(), 140 Error: le, 141 LineNumber: lineNo, 142 Offset: posBytes, 143 Line: line, 144 } 145 v := matches(m) 146 if ectx.LinesPos == -1 && v != -1 { 147 ectx.Position.LineNumber = lineNo 148 ectx.Position.ColumnNumber = v 149 break 150 } 151 152 posBytes += len(line) 153 } 154 155 if ectx.Position.LineNumber > 0 { 156 low := ectx.Position.LineNumber - 3 157 if low < 0 { 158 low = 0 159 } 160 161 if ectx.Position.LineNumber > 2 { 162 ectx.LinesPos = 2 163 } else { 164 ectx.LinesPos = ectx.Position.LineNumber - 1 165 } 166 167 high := ectx.Position.LineNumber + 2 168 if high > len(lines) { 169 high = len(lines) 170 } 171 172 ectx.Lines = lines[low:high] 173 174 } 175 176 return ectx 177 }