shithub: hugo

Download patch

ref: 78e9229c52dfdaf335673b171a3d66fc1ace8d9e
parent: b6ab66189341907b5d8daaffdb73f1151fa7a467
author: Tatsushi Demachi <[email protected]>
date: Sat Jan 10 11:15:51 EST 2015

Fix "hugo new" EOF error with an archetype file without the final EOL

This rewrites `extractFrontMatterDelims` function to make it work with
an archetype file without the final EOL and adds more detailed error
messages and comments.

It also removes `matches` and `matches_quick` functions which aren't
called anywhere.

--- a/hugolib/page_test.go
+++ b/hugolib/page_test.go
@@ -447,7 +447,7 @@
 		r   string
 		err string
 	}{
-		{INVALID_FRONT_MATTER_SHORT_DELIM_ENDING, "Unable to read frontmatter at filepos 45: EOF"},
+		{INVALID_FRONT_MATTER_SHORT_DELIM_ENDING, "unable to read frontmatter at filepos 45: EOF"},
 	}
 	for _, test := range tests {
 
--- a/parser/page.go
+++ b/parser/page.go
@@ -159,17 +159,13 @@
 
 func determineDelims(firstLine []byte) (left, right []byte) {
 	switch len(firstLine) {
+	case 5:
+		fallthrough
 	case 4:
 		if firstLine[0] == YAML_LEAD[0] {
-			return []byte(YAML_DELIM_UNIX), []byte(YAML_DELIM_UNIX)
+			return []byte(YAML_DELIM), []byte(YAML_DELIM)
 		}
-		return []byte(TOML_DELIM_UNIX), []byte(TOML_DELIM_UNIX)
-
-	case 5:
-		if firstLine[0] == YAML_LEAD[0] {
-			return []byte(YAML_DELIM_DOS), []byte(YAML_DELIM_DOS)
-		}
-		return []byte(TOML_DELIM_DOS), []byte(TOML_DELIM_DOS)
+		return []byte(TOML_DELIM), []byte(TOML_DELIM)
 	case 3:
 		fallthrough
 	case 2:
@@ -181,102 +177,96 @@
 	}
 }
 
+// extractFrontMatterDelims takes a frontmatter from the content bufio.Reader.
+// Begining white spaces of the bufio.Reader must be trimmed before call this
+// function.
 func extractFrontMatterDelims(r *bufio.Reader, left, right []byte) (fm FrontMatter, err error) {
 	var (
 		c         byte
-		level     int = 0
-		bytesRead int = 0
-		sameDelim     = bytes.Equal(left, right)
+		buf       bytes.Buffer
+		level     int  = 0
+		sameDelim bool = bytes.Equal(left, right)
 	)
 
-	wr := new(bytes.Buffer)
+	// Frontmatter must start with a delimiter. To check it first,
+	// pre-reads beginning delimiter length - 1 bytes from Reader
+	for i := 0; i < len(left)-1; i++ {
+		if c, err = r.ReadByte(); err != nil {
+			return nil, fmt.Errorf("unable to read frontmatter at filepos %d: %s", buf.Len(), err)
+		}
+		if err = buf.WriteByte(c); err != nil {
+			return nil, err
+		}
+	}
+
+	// Reads a character from Reader one by one and checks it matches the
+	// last character of one of delemiters to find the last character of
+	// frontmatter. If it matches, makes sure it contains the delimiter
+	// and if so, also checks it is followed by CR+LF or LF when YAML,
+	// TOML case. In JSON case, nested delimiters must be parsed and it
+	// is expected that the delimiter only contains one character.
 	for {
 		if c, err = r.ReadByte(); err != nil {
-			return nil, fmt.Errorf("Unable to read frontmatter at filepos %d: %s", bytesRead, err)
+			return nil, fmt.Errorf("unable to read frontmatter at filepos %d: %s", buf.Len(), err)
 		}
-		bytesRead += 1
+		if err = buf.WriteByte(c); err != nil {
+			return nil, err
+		}
 
 		switch c {
-		case left[0]:
-			var (
-				buf       []byte = []byte{c}
-				remaining []byte
-			)
-
-			if remaining, err = r.Peek(len(left) - 1); err != nil {
-				return nil, err
-			}
-
-			buf = append(buf, remaining...)
-
-			if bytes.Equal(buf, left) {
-				if sameDelim {
+		case left[len(left)-1]:
+			if sameDelim { // YAML, TOML case
+				if bytes.HasSuffix(buf.Bytes(), left) {
+					c, err = r.ReadByte()
+					if err != nil {
+						// It is ok that the end delimiter ends with EOF
+						if err != io.EOF || level != 1 {
+							return nil, fmt.Errorf("unable to read frontmatter at filepos %d: %s", buf.Len(), err)
+						}
+					} else {
+						switch c {
+						case '\n':
+							// ok
+						case '\r':
+							if err = buf.WriteByte(c); err != nil {
+								return nil, err
+							}
+							if c, err = r.ReadByte(); err != nil {
+								return nil, fmt.Errorf("unable to read frontmatter at filepos %d: %s", buf.Len(), err)
+							}
+							if c != '\n' {
+								return nil, fmt.Errorf("frontmatter delimiter must be followed by CR+LF or LF but those can't be found at filepos %d", buf.Len())
+							}
+						default:
+							return nil, fmt.Errorf("frontmatter delimiter must be followed by CR+LF or LF but those can't be found at filepos %d", buf.Len())
+						}
+						if err = buf.WriteByte(c); err != nil {
+							return nil, err
+						}
+					}
 					if level == 0 {
 						level = 1
 					} else {
 						level = 0
 					}
-				} else {
-					level += 1
 				}
+			} else { // JSON case
+				level++
 			}
-
-			if _, err = wr.Write([]byte{c}); err != nil {
-				return nil, err
-			}
-
-			if level == 0 {
-				if _, err = r.Read(remaining); err != nil {
-					return nil, err
-				}
-				if _, err = wr.Write(remaining); err != nil {
-					return nil, err
-				}
-			}
-		case right[0]:
-			match, err := matches(r, wr, []byte{c}, right)
-			if err != nil {
-				return nil, err
-			}
-			if match {
-				level -= 1
-			}
-		default:
-			if err = wr.WriteByte(c); err != nil {
-				return nil, err
-			}
+		case right[len(right)-1]: // JSON case only reaches here
+			level--
 		}
 
-		if level == 0 && !unicode.IsSpace(rune(c)) {
+		if level == 0 {
+			// Consumes white spaces immediately behind frontmatter
 			if err = chompWhitespace(r); err != nil {
 				if err != io.EOF {
 					return nil, err
 				}
 			}
-			return wr.Bytes(), nil
+			return buf.Bytes(), nil
 		}
 	}
-}
-
-func matches_quick(buf, expected []byte) (ok bool, err error) {
-	return bytes.Equal(expected, buf), nil
-}
-
-func matches(r *bufio.Reader, wr io.Writer, c, expected []byte) (ok bool, err error) {
-	if len(expected) == 1 {
-		if _, err = wr.Write(c); err != nil {
-			return
-		}
-		return bytes.Equal(c, expected), nil
-	}
-
-	buf := make([]byte, len(expected)-1)
-	if buf, err = r.Peek(len(expected) - 1); err != nil {
-		return
-	}
-
-	buf = append(c, buf...)
-	return bytes.Equal(expected, buf), nil
 }
 
 func extractContent(r io.Reader) (content Content, err error) {
--- a/parser/parse_frontmatter_test.go
+++ b/parser/parse_frontmatter_test.go
@@ -54,7 +54,6 @@
 	}{
 		{CONTENT_MISSING_END_FM_DELIM},
 		{CONTENT_INCOMPLETE_END_FM_DELIM},
-		{CONTENT_FM_NO_DOC},
 	}
 
 	for _, test := range tests {
@@ -230,6 +229,7 @@
 		{"---\nfoobar\nbarfoo\nfizbaz\n", nil, false},
 		{"---\nblar\n-\n", nil, false},
 		{"---\nralb\n---\n", []byte("---\nralb\n---\n"), true},
+		{"---\neof\n---", []byte("---\neof\n---"), true},
 		{"---\nminc\n---\ncontent", []byte("---\nminc\n---\n"), true},
 		{"---\ncnim\n---\ncontent\n", []byte("---\ncnim\n---\n"), true},
 		{"---\ntitle: slug doc 2\nslug: slug-doc-2\n---\ncontent\n", []byte("---\ntitle: slug doc 2\nslug: slug-doc-2\n---\n"), true},
@@ -274,7 +274,6 @@
 		{"", "", errExpected},
 		{"{", "", errExpected},
 		{"{}", "{}", noErrExpected},
-		{" {}", " {}", noErrExpected},
 		{"{} ", "{}", noErrExpected},
 		{"{ } ", "{ }", noErrExpected},
 		{"{ { }", "", errExpected},