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 }