|
|
|
|
@ -10,6 +10,7 @@ import (
|
|
|
|
|
"os"
|
|
|
|
|
"path/filepath"
|
|
|
|
|
"sort"
|
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
|
|
"github.com/hashicorp/go-multierror"
|
|
|
|
|
"github.com/mitchellh/mapstructure"
|
|
|
|
|
@ -311,24 +312,6 @@ func Parse(r io.Reader) (*Template, error) {
|
|
|
|
|
return rawTpl.Template()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Find line number and position based on the offset
|
|
|
|
|
func findLinePos(f *os.File, offset int64) (int64, int64, string) {
|
|
|
|
|
scanner := bufio.NewScanner(f)
|
|
|
|
|
count := int64(0)
|
|
|
|
|
for scanner.Scan() {
|
|
|
|
|
count += 1
|
|
|
|
|
scanLength := len(scanner.Text()) + 1
|
|
|
|
|
if offset < int64(scanLength) {
|
|
|
|
|
return count, offset, scanner.Text()
|
|
|
|
|
}
|
|
|
|
|
offset = offset - int64(scanLength)
|
|
|
|
|
}
|
|
|
|
|
if err := scanner.Err(); err != nil {
|
|
|
|
|
return 0, 0, err.Error()
|
|
|
|
|
}
|
|
|
|
|
return 0, 0, ""
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ParseFile is the same as Parse but is a helper to automatically open
|
|
|
|
|
// a file for parsing.
|
|
|
|
|
func ParseFile(path string) (*Template, error) {
|
|
|
|
|
@ -359,8 +342,9 @@ func ParseFile(path string) (*Template, error) {
|
|
|
|
|
}
|
|
|
|
|
// Rewind the file and get a better error
|
|
|
|
|
f.Seek(0, os.SEEK_SET)
|
|
|
|
|
line, pos, errorLine := findLinePos(f, syntaxErr.Offset)
|
|
|
|
|
err = fmt.Errorf("Error in line %d, char %d: %s\n%s", line, pos, syntaxErr, errorLine)
|
|
|
|
|
// Grab the error location, and return a string to point to offending syntax error
|
|
|
|
|
line, col, highlight := highlightPosition(f, syntaxErr.Offset)
|
|
|
|
|
err = fmt.Errorf("Error parsing JSON: %s\nAt line %d, column %d (offset %d):\n%s", err, line, col, syntaxErr.Offset, highlight)
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ -374,3 +358,46 @@ func ParseFile(path string) (*Template, error) {
|
|
|
|
|
tpl.Path = path
|
|
|
|
|
return tpl, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Takes a file and the location in bytes of a parse error
|
|
|
|
|
// from json.SyntaxError.Offset and returns the line, column,
|
|
|
|
|
// and pretty-printed context around the error with an arrow indicating the exact
|
|
|
|
|
// position of the syntax error.
|
|
|
|
|
func highlightPosition(f *os.File, pos int64) (line, col int, highlight string) {
|
|
|
|
|
// Modified version of the function in Camlistore by Brad Fitzpatrick
|
|
|
|
|
// https://github.com/camlistore/camlistore/blob/4b5403dd5310cf6e1ae8feb8533fd59262701ebc/vendor/go4.org/errorutil/highlight.go
|
|
|
|
|
line = 1
|
|
|
|
|
// New io.Reader for file
|
|
|
|
|
br := bufio.NewReader(f)
|
|
|
|
|
// Initialize lines
|
|
|
|
|
lastLine := ""
|
|
|
|
|
thisLine := new(bytes.Buffer)
|
|
|
|
|
// Loop through template to find line, column
|
|
|
|
|
for n := int64(0); n < pos; n++ {
|
|
|
|
|
// read byte from io.Reader
|
|
|
|
|
b, err := br.ReadByte()
|
|
|
|
|
if err != nil {
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
// If end of line, save line as previous line in case next line is offender
|
|
|
|
|
if b == '\n' {
|
|
|
|
|
lastLine = thisLine.String()
|
|
|
|
|
thisLine.Reset()
|
|
|
|
|
line++
|
|
|
|
|
col = 1
|
|
|
|
|
} else {
|
|
|
|
|
// Write current line, until line is safe, or error point is encountered
|
|
|
|
|
col++
|
|
|
|
|
thisLine.WriteByte(b)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Populate highlight string to place a '^' char at offending column
|
|
|
|
|
if line > 1 {
|
|
|
|
|
highlight += fmt.Sprintf("%5d: %s\n", line-1, lastLine)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
highlight += fmt.Sprintf("%5d: %s\n", line, thisLine.String())
|
|
|
|
|
highlight += fmt.Sprintf("%s^\n", strings.Repeat(" ", col+5))
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|