Fix #2742: Include template line numbers on error

This commit is contained in:
Mark Peek 2015-10-25 12:28:06 -07:00
parent 5bfa6ce2b8
commit 71ed8e4a38
6 changed files with 76 additions and 10 deletions

View File

@ -39,11 +39,7 @@ func (c BuildCommand) Run(args []string) int {
// Parse the template
var tpl *template.Template
var err error
if args[0] == "-" {
tpl, err = template.Parse(os.Stdin)
} else {
tpl, err = template.ParseFile(args[0])
}
tpl, err = template.ParseFile(args[0])
if err != nil {
c.Ui.Error(fmt.Sprintf("Failed to parse template: %s", err))
return 1

View File

@ -1,10 +1,12 @@
package template
import (
"bufio"
"bytes"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"sort"
@ -309,17 +311,56 @@ 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) {
f, err := os.Open(path)
if err != nil {
return nil, err
var f *os.File
var err error
if path == "-" {
// Create a temp file for stdin in case of errors
f, err = ioutil.TempFile(os.TempDir(), "packer")
if err != nil {
return nil, err
}
defer os.Remove(f.Name())
defer f.Close()
io.Copy(f, os.Stdin)
f.Seek(0, os.SEEK_SET)
} else {
f, err = os.Open(path)
if err != nil {
return nil, err
}
defer f.Close()
}
defer f.Close()
tpl, err := Parse(f)
if err != nil {
syntaxErr, ok := err.(*json.SyntaxError)
if !ok {
return nil, err
}
// 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)
return nil, err
}

View File

@ -350,3 +350,23 @@ func TestParse_contents(t *testing.T) {
t.Fatalf("bad: %s\n\n%s", actual, expected)
}
}
func TestParse_bad(t *testing.T) {
cases := []struct {
File string
Expected string
}{
{"error-beginning.json", "line 1, char 1"},
{"error-middle.json", "line 5, char 5"},
{"error-end.json", "line 1, char 30"},
}
for _, tc := range cases {
_, err := ParseFile(fixtureDir(tc.File))
if err == nil {
t.Fatalf("expected error")
}
if !strings.Contains(err.Error(), tc.Expected) {
t.Fatalf("file: %s\nExpected: %s\n%s\n", tc.File, tc.Expected, err.Error())
}
}
}

View File

@ -0,0 +1 @@
*"builders": [ { "type":"test", }]}

View File

@ -0,0 +1 @@
{"builders":[{"type":"test"}]*

View File

@ -0,0 +1,7 @@
{
"builders": [
{
"type":"test",
}
]
}