From 34cbc66956aad17f0629b01b71619713b92059b7 Mon Sep 17 00:00:00 2001 From: Ben Johnson Date: Wed, 14 Jul 2021 08:14:52 -0600 Subject: [PATCH] Revert whitespace trimming This commit reverts whitespace trimming after it was reported in issue #40 that it breaks some use cases. Reverted commits: - eb5dd3e - e79bb8c - 6f55900 - 6d69dcf - 438bb0a --- README.md | 6 ----- ego.go | 66 ++++++++++++++--------------------------------- scanner.go | 68 ++++++++++++++++++------------------------------- scanner_test.go | 39 ---------------------------- 4 files changed, 44 insertions(+), 135 deletions(-) diff --git a/README.md b/README.md index 7336fb6..df3ba79 100644 --- a/README.md +++ b/README.md @@ -152,12 +152,6 @@ The `<%= %>` block will print your text as escaped HTML, however, sometimes you To do this, simply wrap your Go expression with `<%==` and `%>` tags. -#### Trim Space - -The `<% %>` blocks can be optionally adorned with a `-` on one or both sides to trigger trimming of whitespace on that side. -For example `<%-= r.Name %>` will trim whitespace before the print block, but not after it. - - ### Components Simple code and print tags work well for simple templates but it can be difficult to make reusable functionality. diff --git a/ego.go b/ego.go index d15b98f..75ed3aa 100644 --- a/ego.go +++ b/ego.go @@ -10,7 +10,6 @@ import ( "io" "sort" "strings" - "unicode" ) // Template represents an entire Ego template. @@ -114,8 +113,7 @@ func writeBlocksTo(buf *bytes.Buffer, blks []Block) { // Normalize joins together adjacent text blocks. func normalizeBlocks(a []Block) []Block { a = joinAdjacentTextBlocks(a) - a = trimLeftRight(a) - a = trimEmptyTextBlocks(a) + a = trimTrailingEmptyTextBlocks(a) return a } @@ -144,33 +142,18 @@ func joinAdjacentTextBlocks(a []Block) []Block { return other } -func trimLeftRight(a []Block) []Block { - for i, blk := range a { - trimLeft, trimRight := blk.trim() - if trimLeft && i > 1 { - if textBlock, ok := a[i-1].(*TextBlock); ok { - textBlock.Content = strings.TrimRightFunc(textBlock.Content, unicode.IsSpace) - } - } - if trimRight && i+1 < len(a) { - if textBlock, ok := a[i+1].(*TextBlock); ok { - textBlock.Content = strings.TrimLeftFunc(textBlock.Content, unicode.IsSpace) - } +func trimTrailingEmptyTextBlocks(a []Block) []Block { + for len(a) > 0 { + blk, ok := a[len(a)-1].(*TextBlock) + if !ok || strings.TrimSpace(blk.Content) != "" { + break } + a[len(a)-1] = nil + a = a[:len(a)-1] } return a } -func trimEmptyTextBlocks(a []Block) []Block { - b := make([]Block, 0, len(a)) - for _, blk := range a { - if tb, ok := blk.(*TextBlock); !ok || strings.TrimSpace(tb.Content) != "" { - b = append(b, blk) - } - } - return b -} - func injectImports(f *ast.File) { names := []string{`"fmt"`, `"html"`, `"io"`, `"context"`} @@ -249,7 +232,6 @@ func removeImportSpecs(decl *ast.GenDecl, names []string) { // Block represents an element of the template. type Block interface { block() - trim() (bool, bool) } func (*TextBlock) block() {} @@ -261,15 +243,6 @@ func (*ComponentEndBlock) block() {} func (*AttrStartBlock) block() {} func (*AttrEndBlock) block() {} -func (*TextBlock) trim() (bool, bool) { return false, false } -func (b *CodeBlock) trim() (bool, bool) { return b.TrimLeft, b.TrimRight } -func (b *PrintBlock) trim() (bool, bool) { return b.TrimLeft, b.TrimRight } -func (b *RawPrintBlock) trim() (bool, bool) { return b.TrimLeft, b.TrimRight } -func (*ComponentStartBlock) trim() (bool, bool) { return false, false } -func (*ComponentEndBlock) trim() (bool, bool) { return false, false } -func (*AttrStartBlock) trim() (bool, bool) { return false, false } -func (*AttrEndBlock) trim() (bool, bool) { return false, false } - // TextBlock represents a UTF-8 encoded block of text that is written to the writer as-is. type TextBlock struct { Pos Pos @@ -278,26 +251,20 @@ type TextBlock struct { // CodeBlock represents a Go code block that is printed as-is to the template. type CodeBlock struct { - Pos Pos - Content string - TrimLeft bool - TrimRight bool + Pos Pos + Content string } // PrintBlock represents a block that will HTML escape the contents before outputting type PrintBlock struct { - Pos Pos - Content string - TrimLeft bool - TrimRight bool + Pos Pos + Content string } // RawPrintBlock represents a block of the template that is printed out to the writer. type RawPrintBlock struct { - Pos Pos - Content string - TrimLeft bool - TrimRight bool + Pos Pos + Content string } // ComponentStartBlock represents the opening block of an ego component. @@ -438,6 +405,11 @@ func stringSliceContains(a []string, v string) bool { return false } +type stackElem struct { + block Block + yield []Block +} + // AttrNames returns a sorted list of names for an attribute set. func AttrNames(attrs map[string]interface{}) []string { a := make([]string, 0, len(attrs)) diff --git a/scanner.go b/scanner.go index c5db49d..480c574 100644 --- a/scanner.go +++ b/scanner.go @@ -53,11 +53,11 @@ func (s *Scanner) Scan() (Block, error) { } // Special handling for ego blocks. - if s.peekN(4) == "<%==" || s.peekN(5) == "<%-==" { + if s.peekN(4) == "<%==" { return s.scanRawPrintBlock() - } else if s.peekN(3) == "<%=" || s.peekN(4) == "<%-=" { + } else if s.peekN(3) == "<%=" { return s.scanPrintBlock() - } else if s.peekN(2) == "<%" || s.peekN(3) == "<%-" { + } else if s.peekN(2) == "<%" { return s.scanCodeBlock() } @@ -78,66 +78,45 @@ func (s *Scanner) scanTextBlock() (*TextBlock, error) { buf.WriteRune(s.read()) } - b.Content = buf.String() + b.Content = string(buf.Bytes()) return b, nil } func (s *Scanner) scanCodeBlock() (*CodeBlock, error) { b := &CodeBlock{Pos: s.pos} + assert(s.readN(2) == "<%") - if s.peekN(3) == "<%-" { - assert(s.readN(3) == "<%-") - b.TrimLeft = true - } else { - assert(s.readN(2) == "<%") - } - - content, trimRight, err := s.scanContent() + content, err := s.scanContent() if err != nil { return nil, err } b.Content = content - b.TrimRight = trimRight return b, nil } func (s *Scanner) scanPrintBlock() (*PrintBlock, error) { b := &PrintBlock{Pos: s.pos} + assert(s.readN(3) == "<%=") - if s.peekN(3) == "<%-" { - assert(s.readN(4) == "<%-=") - b.TrimLeft = true - } else { - assert(s.readN(3) == "<%=") - } - - content, trimRight, err := s.scanContent() + content, err := s.scanContent() if err != nil { return nil, err } b.Content = content - b.TrimRight = trimRight return b, nil } func (s *Scanner) scanRawPrintBlock() (*RawPrintBlock, error) { b := &RawPrintBlock{Pos: s.pos} + assert(s.readN(4) == "<%==") - if s.peekN(3) == "<%-" { - assert(s.readN(5) == "<%-==") - b.TrimLeft = true - } else { - assert(s.readN(4) == "<%==") - } - - content, trimRight, err := s.scanContent() + content, err := s.scanContent() if err != nil { return nil, err } b.Content = content - b.TrimRight = trimRight return b, nil } @@ -345,26 +324,28 @@ func (s *Scanner) scanAttrEndBlock() (_ *AttrEndBlock, err error) { return b, nil } -// scans the reader until %> or -%> is reached. -func (s *Scanner) scanContent() (string, bool, error) { +// scans the reader until %> is reached. +func (s *Scanner) scanContent() (string, error) { var buf bytes.Buffer - var trimRight bool for { ch := s.read() if ch == eof { - return "", false, &SyntaxError{Message: "Expected close tag, found EOF", Pos: s.pos} - } else if ch == '-' && s.peekN(2) == "%>" { - s.readN(2) - trimRight = true - break - } else if ch == '%' && s.peek() == '>' { - s.read() - break + return "", &SyntaxError{Message: "Expected close tag, found EOF", Pos: s.pos} + } else if ch == '%' { + ch := s.read() + if ch == eof { + return "", &SyntaxError{Message: "Expected close tag, found EOF", Pos: s.pos} + } else if ch == '>' { + break + } else { + buf.WriteRune('%') + buf.WriteRune(ch) + } } else { buf.WriteRune(ch) } } - return buf.String(), trimRight, nil + return string(buf.Bytes()), nil } func (s *Scanner) scanField() (*Field, error) { @@ -615,6 +596,7 @@ func (s *Scanner) skipWhitespace() { for ch := s.peek(); isWhitespace(ch); ch = s.peek() { s.read() } + return } const eof = rune(0) diff --git a/scanner_test.go b/scanner_test.go index 98aa1a9..e931769 100644 --- a/scanner_test.go +++ b/scanner_test.go @@ -62,45 +62,6 @@ func TestScanner(t *testing.T) { } }) - t.Run("TrimLeft", func(t *testing.T) { - s := ego.NewScanner(bytes.NewBufferString(`<%- x := 1 %>`), "tmpl.ego") - if blk, err := s.Scan(); err != nil { - t.Fatal(err) - } else if blk, ok := blk.(*ego.CodeBlock); !ok { - t.Fatalf("unexpected block type: %T", blk) - } else if blk.Content != " x := 1 " { - t.Fatalf("unexpected content: %s", blk.Content) - } else if !blk.TrimLeft || blk.TrimRight { - t.Fatal("expected TrimLeft only") - } - }) - - t.Run("TrimRight", func(t *testing.T) { - s := ego.NewScanner(bytes.NewBufferString(`<% x := 1 -%>`), "tmpl.ego") - if blk, err := s.Scan(); err != nil { - t.Fatal(err) - } else if blk, ok := blk.(*ego.CodeBlock); !ok { - t.Fatalf("unexpected block type: %T", blk) - } else if blk.Content != " x := 1 " { - t.Fatalf("unexpected content: %s", blk.Content) - } else if blk.TrimLeft || !blk.TrimRight { - t.Fatal("expected TrimRight only") - } - }) - - t.Run("Trim", func(t *testing.T) { - s := ego.NewScanner(bytes.NewBufferString(`<%- x := 1 -%>`), "tmpl.ego") - if blk, err := s.Scan(); err != nil { - t.Fatal(err) - } else if blk, ok := blk.(*ego.CodeBlock); !ok { - t.Fatalf("unexpected block type: %T", blk) - } else if blk.Content != " x := 1 " { - t.Fatalf("unexpected content: %s", blk.Content) - } else if !blk.TrimLeft || !blk.TrimRight { - t.Fatal("expected TrimLeft and TrimRight") - } - }) - t.Run("UnexpectedEOF/1", func(t *testing.T) { s := ego.NewScanner(bytes.NewBufferString(`<%`), "tmpl.ego") if _, err := s.Scan(); err == nil || err.Error() != `Expected close tag, found EOF at tmpl.ego:1` {