Skip to content

Commit

Permalink
Revert whitespace trimming
Browse files Browse the repository at this point in the history
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
  • Loading branch information
benbjohnson committed Jul 14, 2021
1 parent 031c220 commit 34cbc66
Show file tree
Hide file tree
Showing 4 changed files with 44 additions and 135 deletions.
6 changes: 0 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
66 changes: 19 additions & 47 deletions ego.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import (
"io"
"sort"
"strings"
"unicode"
)

// Template represents an entire Ego template.
Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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"`}

Expand Down Expand Up @@ -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() {}
Expand All @@ -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
Expand All @@ -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.
Expand Down Expand Up @@ -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))
Expand Down
68 changes: 25 additions & 43 deletions scanner.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}

Expand All @@ -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
}

Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -615,6 +596,7 @@ func (s *Scanner) skipWhitespace() {
for ch := s.peek(); isWhitespace(ch); ch = s.peek() {
s.read()
}
return
}

const eof = rune(0)
Expand Down
39 changes: 0 additions & 39 deletions scanner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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` {
Expand Down

0 comments on commit 34cbc66

Please sign in to comment.