aboutsummaryrefslogtreecommitdiff
path: root/src/text/template/parse
diff options
context:
space:
mode:
authorRob Pike <r@golang.org>2015-09-08 14:58:12 -0700
committerRob Pike <r@golang.org>2015-09-09 05:28:11 +0000
commite6ee26a03b79d0e8b658463bdb29349ca68e1460 (patch)
tree3c8ba47d66ba2ba5d9d00675a1fb67de8b4ebd08 /src/text/template/parse
parent49fb8cc10c2d61ebdf3829f42bba9bec7b0a7ff7 (diff)
downloadgo-e6ee26a03b79d0e8b658463bdb29349ca68e1460.tar.xz
text/template: provide a way to trim leading and trailing space between actions
Borrowing a suggestion from the issue listed below, we modify the lexer to trim spaces at the beginning (end) of a block of text if the action immediately before (after) is marked with a minus sign. To avoid parsing/lexing ambiguity, we require an ASCII space between the minus sign and the rest of the action. Thus: {{23 -}} < {{- 45}} produces the output 23<45 All the work is done in the lexer. The modification is invisible to the parser or any outside package (except I guess for noticing some gaps in the input if one tracks error positions). Thus it slips in without worry in text/template and html/template both. Fixes long-requested issue #9969. Change-Id: I3774be650bfa6370cb993d0899aa669c211de7b2 Reviewed-on: https://go-review.googlesource.com/14391 Reviewed-by: Andrew Gerrand <adg@golang.org>
Diffstat (limited to 'src/text/template/parse')
-rw-r--r--src/text/template/parse/lex.go96
-rw-r--r--src/text/template/parse/lex_test.go15
-rw-r--r--src/text/template/parse/parse_test.go7
3 files changed, 110 insertions, 8 deletions
diff --git a/src/text/template/parse/lex.go b/src/text/template/parse/lex.go
index 8f9fe1d4d8..9061731b2b 100644
--- a/src/text/template/parse/lex.go
+++ b/src/text/template/parse/lex.go
@@ -83,6 +83,21 @@ var key = map[string]itemType{
const eof = -1
+// Trimming spaces.
+// If the action begins "{{- " rather than "{{", then all space/tab/newlines
+// preceding the action are trimmed; conversely if it ends " -}}" the
+// leading spaces are trimmed. This is done entirely in the lexer; the
+// parser never sees it happen. We require an ASCII space to be
+// present to avoid ambiguity with things like "{{-3}}". It reads
+// better with the space present anyway. For simplicity, only ASCII
+// space does the job.
+const (
+ spaceChars = " \t\r\n" // These are the space characters defined by Go itself.
+ leftTrimMarker = "- " // Attached to left delimiter, trims trailing spaces from preceding text.
+ rightTrimMarker = " -" // Attached to right delimiter, trims leading spaces from following text.
+ trimMarkerLen = Pos(len(leftTrimMarker))
+)
+
// stateFn represents the state of the scanner as a function that returns the next state.
type stateFn func(*lexer) stateFn
@@ -220,10 +235,18 @@ const (
// lexText scans until an opening action delimiter, "{{".
func lexText(l *lexer) stateFn {
for {
- if strings.HasPrefix(l.input[l.pos:], l.leftDelim) {
+ delim, trimSpace := l.atLeftDelim()
+ if delim {
+ trimLength := Pos(0)
+ if trimSpace {
+ trimLength = rightTrimLength(l.input[l.start:l.pos])
+ }
+ l.pos -= trimLength
if l.pos > l.start {
l.emit(itemText)
}
+ l.pos += trimLength
+ l.ignore()
return lexLeftDelim
}
if l.next() == eof {
@@ -238,13 +261,56 @@ func lexText(l *lexer) stateFn {
return nil
}
-// lexLeftDelim scans the left delimiter, which is known to be present.
+// atLeftDelim reports whether the lexer is at a left delimiter, possibly followed by a trim marker.
+func (l *lexer) atLeftDelim() (delim, trimSpaces bool) {
+ if !strings.HasPrefix(l.input[l.pos:], l.leftDelim) {
+ return false, false
+ }
+ // The left delim might have the marker afterwards.
+ trimSpaces = strings.HasPrefix(l.input[l.pos+Pos(len(l.leftDelim)):], leftTrimMarker)
+ return true, trimSpaces
+}
+
+// rightTrimLength returns the length of the spaces at the end of the string.
+func rightTrimLength(s string) Pos {
+ return Pos(len(s) - len(strings.TrimRight(s, spaceChars)))
+}
+
+// atRightDelim reports whether the lexer is at a right delimiter, possibly preceded by a trim marker.
+func (l *lexer) atRightDelim() (delim, trimSpaces bool) {
+ if strings.HasPrefix(l.input[l.pos:], l.rightDelim) {
+ return true, false
+ }
+ // The right delim might have the marker before.
+ if strings.HasPrefix(l.input[l.pos:], rightTrimMarker) {
+ if strings.HasPrefix(l.input[l.pos+trimMarkerLen:], l.rightDelim) {
+ return true, true
+ }
+ }
+ return false, false
+}
+
+// leftTrimLength returns the length of the spaces at the beginning of the string.
+func leftTrimLength(s string) Pos {
+ return Pos(len(s) - len(strings.TrimLeft(s, spaceChars)))
+}
+
+// lexLeftDelim scans the left delimiter, which is known to be present, possibly with a trim marker.
func lexLeftDelim(l *lexer) stateFn {
l.pos += Pos(len(l.leftDelim))
- if strings.HasPrefix(l.input[l.pos:], leftComment) {
+ trimSpace := strings.HasPrefix(l.input[l.pos:], leftTrimMarker)
+ afterMarker := Pos(0)
+ if trimSpace {
+ afterMarker = trimMarkerLen
+ }
+ if strings.HasPrefix(l.input[l.pos+afterMarker:], leftComment) {
+ l.pos += afterMarker
+ l.ignore()
return lexComment
}
l.emit(itemLeftDelim)
+ l.pos += afterMarker
+ l.ignore()
l.parenDepth = 0
return lexInsideAction
}
@@ -257,19 +323,34 @@ func lexComment(l *lexer) stateFn {
return l.errorf("unclosed comment")
}
l.pos += Pos(i + len(rightComment))
- if !strings.HasPrefix(l.input[l.pos:], l.rightDelim) {
+ delim, trimSpace := l.atRightDelim()
+ if !delim {
return l.errorf("comment ends before closing delimiter")
-
+ }
+ if trimSpace {
+ l.pos += trimMarkerLen
}
l.pos += Pos(len(l.rightDelim))
+ if trimSpace {
+ l.pos += leftTrimLength(l.input[l.pos:])
+ }
l.ignore()
return lexText
}
-// lexRightDelim scans the right delimiter, which is known to be present.
+// lexRightDelim scans the right delimiter, which is known to be present, possibly with a trim marker.
func lexRightDelim(l *lexer) stateFn {
+ trimSpace := strings.HasPrefix(l.input[l.pos:], rightTrimMarker)
+ if trimSpace {
+ l.pos += trimMarkerLen
+ l.ignore()
+ }
l.pos += Pos(len(l.rightDelim))
l.emit(itemRightDelim)
+ if trimSpace {
+ l.pos += leftTrimLength(l.input[l.pos:])
+ l.ignore()
+ }
return lexText
}
@@ -278,7 +359,8 @@ func lexInsideAction(l *lexer) stateFn {
// Either number, quoted string, or identifier.
// Spaces separate arguments; runs of spaces turn into itemSpace.
// Pipe symbols separate and are emitted.
- if strings.HasPrefix(l.input[l.pos:], l.rightDelim) {
+ delim, _ := l.atRightDelim()
+ if delim {
if l.parenDepth == 0 {
return lexRightDelim
}
diff --git a/src/text/template/parse/lex_test.go b/src/text/template/parse/lex_test.go
index be551d8780..17dbe28a9f 100644
--- a/src/text/template/parse/lex_test.go
+++ b/src/text/template/parse/lex_test.go
@@ -278,6 +278,19 @@ var lexTests = []lexTest{
tRight,
tEOF,
}},
+ {"trimming spaces before and after", "hello- {{- 3 -}} -world", []item{
+ {itemText, 0, "hello-"},
+ tLeft,
+ {itemNumber, 0, "3"},
+ tRight,
+ {itemText, 0, "-world"},
+ tEOF,
+ }},
+ {"trimming spaces before and after comment", "hello- {{- /* hello */ -}} -world", []item{
+ {itemText, 0, "hello-"},
+ {itemText, 0, "-world"},
+ tEOF,
+ }},
// errors
{"badchar", "#{{\x01}}", []item{
{itemText, 0, "#"},
@@ -339,7 +352,7 @@ var lexTests = []lexTest{
{itemText, 0, "hello-"},
{itemError, 0, `unclosed comment`},
}},
- {"text with comment close separted from delim", "hello-{{/* */ }}-world", []item{
+ {"text with comment close separated from delim", "hello-{{/* */ }}-world", []item{
{itemText, 0, "hello-"},
{itemError, 0, `comment ends before closing delimiter`},
}},
diff --git a/src/text/template/parse/parse_test.go b/src/text/template/parse/parse_test.go
index 200d50c194..28b5f7cb90 100644
--- a/src/text/template/parse/parse_test.go
+++ b/src/text/template/parse/parse_test.go
@@ -228,6 +228,13 @@ var parseTests = []parseTest{
`{{with .X}}"hello"{{end}}`},
{"with with else", "{{with .X}}hello{{else}}goodbye{{end}}", noError,
`{{with .X}}"hello"{{else}}"goodbye"{{end}}`},
+ // Trimming spaces.
+ {"trim left", "x \r\n\t{{- 3}}", noError, `"x"{{3}}`},
+ {"trim right", "{{3 -}}\n\n\ty", noError, `{{3}}"y"`},
+ {"trim left and right", "x \r\n\t{{- 3 -}}\n\n\ty", noError, `"x"{{3}}"y"`},
+ {"comment trim left", "x \r\n\t{{- /* hi */}}", noError, `"x"`},
+ {"comment trim right", "{{/* hi */ -}}\n\n\ty", noError, `"y"`},
+ {"comment trim left and right", "x \r\n\t{{- /* */ -}}\n\n\ty", noError, `"x""y"`},
// Errors.
{"unclosed action", "hello{{range", hasError, ""},
{"unmatched end", "{{end}}", hasError, ""},