hugo

Fork of github.com/gohugoio/hugo with reverse pagination support

git clone git://git.shimmy1996.com/hugo.git

error_locator.go (4242B)

    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 	"io/ioutil"
   20 	"path/filepath"
   21 	"strings"
   22 
   23 	"github.com/gohugoio/hugo/common/text"
   24 )
   25 
   26 // LineMatcher contains the elements used to match an error to a line
   27 type LineMatcher struct {
   28 	Position text.Position
   29 	Error    error
   30 
   31 	LineNumber int
   32 	Offset     int
   33 	Line       string
   34 }
   35 
   36 // LineMatcherFn is used to match a line with an error.
   37 // It returns the column number or 0 if the line was found, but column could not be determinde. Returns -1 if no line match.
   38 type LineMatcherFn func(m LineMatcher) int
   39 
   40 // SimpleLineMatcher simply matches by line number.
   41 var SimpleLineMatcher = func(m LineMatcher) int {
   42 	if m.Position.LineNumber == m.LineNumber {
   43 		// We found the line, but don't know the column.
   44 		return 0
   45 	}
   46 	return -1
   47 }
   48 
   49 // NopLineMatcher is a matcher that always returns 1.
   50 // This will effectively give line 1, column 1.
   51 var NopLineMatcher = func(m LineMatcher) int {
   52 	return 1
   53 }
   54 
   55 // OffsetMatcher is a line matcher that matches by offset.
   56 var OffsetMatcher = func(m LineMatcher) int {
   57 	if m.Offset+len(m.Line) >= m.Position.Offset {
   58 		// We found the line, but return 0 to signal that we want to determine
   59 		// the column from the error.
   60 		return 0
   61 	}
   62 	return -1
   63 }
   64 
   65 // ErrorContext contains contextual information about an error. This will
   66 // typically be the lines surrounding some problem in a file.
   67 type ErrorContext struct {
   68 
   69 	// If a match will contain the matched line and up to 2 lines before and after.
   70 	// Will be empty if no match.
   71 	Lines []string
   72 
   73 	// The position of the error in the Lines above. 0 based.
   74 	LinesPos int
   75 
   76 	// The position of the content in the file. Note that this may be different from the error's position set
   77 	// in FileError.
   78 	Position text.Position
   79 
   80 	// The lexer to use for syntax highlighting.
   81 	// https://gohugo.io/content-management/syntax-highlighting/#list-of-chroma-highlighting-languages
   82 	ChromaLexer string
   83 }
   84 
   85 func chromaLexerFromType(fileType string) string {
   86 	switch fileType {
   87 	case "html", "htm":
   88 		return "go-html-template"
   89 	}
   90 	return fileType
   91 }
   92 
   93 func extNoDelimiter(filename string) string {
   94 	return strings.TrimPrefix(filepath.Ext(filename), ".")
   95 }
   96 
   97 func chromaLexerFromFilename(filename string) string {
   98 	if strings.Contains(filename, "layouts") {
   99 		return "go-html-template"
  100 	}
  101 
  102 	ext := extNoDelimiter(filename)
  103 	return chromaLexerFromType(ext)
  104 }
  105 
  106 func locateErrorInString(src string, matcher LineMatcherFn) *ErrorContext {
  107 	return locateError(strings.NewReader(src), &fileError{}, matcher)
  108 }
  109 
  110 func locateError(r io.Reader, le FileError, matches LineMatcherFn) *ErrorContext {
  111 	if le == nil {
  112 		panic("must provide an error")
  113 	}
  114 
  115 	ectx := &ErrorContext{LinesPos: -1, Position: text.Position{Offset: -1}}
  116 
  117 	b, err := ioutil.ReadAll(r)
  118 	if err != nil {
  119 		return ectx
  120 	}
  121 
  122 	lines := strings.Split(string(b), "\n")
  123 
  124 	lineNo := 0
  125 	posBytes := 0
  126 
  127 	for li, line := range lines {
  128 		lineNo = li + 1
  129 		m := LineMatcher{
  130 			Position:   le.Position(),
  131 			Error:      le,
  132 			LineNumber: lineNo,
  133 			Offset:     posBytes,
  134 			Line:       line,
  135 		}
  136 		v := matches(m)
  137 		if ectx.LinesPos == -1 && v != -1 {
  138 			ectx.Position.LineNumber = lineNo
  139 			ectx.Position.ColumnNumber = v
  140 			break
  141 		}
  142 
  143 		posBytes += len(line)
  144 	}
  145 
  146 	if ectx.Position.LineNumber > 0 {
  147 		low := ectx.Position.LineNumber - 3
  148 		if low < 0 {
  149 			low = 0
  150 		}
  151 
  152 		if ectx.Position.LineNumber > 2 {
  153 			ectx.LinesPos = 2
  154 		} else {
  155 			ectx.LinesPos = ectx.Position.LineNumber - 1
  156 		}
  157 
  158 		high := ectx.Position.LineNumber + 2
  159 		if high > len(lines) {
  160 			high = len(lines)
  161 		}
  162 
  163 		ectx.Lines = lines[low:high]
  164 
  165 	}
  166 
  167 	return ectx
  168 }