Skip to content

Commit 02c2902

Browse files
committed
Add passthrough attribute support.
1 parent bf2331d commit 02c2902

File tree

4 files changed

+304
-28
lines changed

4 files changed

+304
-28
lines changed

ego.go

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"go/parser"
99
"go/token"
1010
"io"
11+
"sort"
1112
"strings"
1213
)
1314

@@ -84,9 +85,17 @@ func writeBlocksTo(buf *bytes.Buffer, blks []Block) {
8485
fmt.Fprintf(buf, "EGO.%s = %s\n", field.Name, field.Value)
8586
}
8687

87-
for _, attr := range blk.Attrs {
88-
fmt.Fprintf(buf, "EGO.%s = func() {\n", attr.Name)
89-
writeBlocksTo(buf, attr.Yield)
88+
if len(blk.Attrs) > 0 {
89+
fmt.Fprintf(buf, "EGO.Attrs = map[string]string{\n")
90+
for _, attr := range blk.Attrs {
91+
fmt.Fprintf(buf, " %q: fmt.Sprint(%s),\n", attr.Name, attr.Value)
92+
}
93+
fmt.Fprintf(buf, "}\n")
94+
}
95+
96+
for _, attrBlock := range blk.AttrBlocks {
97+
fmt.Fprintf(buf, "EGO.%s = func() {\n", attrBlock.Name)
98+
writeBlocksTo(buf, attrBlock.Yield)
9099
fmt.Fprint(buf, "}\n")
91100
}
92101

@@ -260,13 +269,14 @@ type RawPrintBlock struct {
260269

261270
// ComponentStartBlock represents the opening block of an ego component.
262271
type ComponentStartBlock struct {
263-
Pos Pos
264-
Package string
265-
Name string
266-
Closed bool
267-
Fields []*Field
268-
Attrs []*AttrStartBlock
269-
Yield []Block
272+
Pos Pos
273+
Package string
274+
Name string
275+
Closed bool
276+
Fields []*Field
277+
Attrs []*Attr
278+
AttrBlocks []*AttrStartBlock
279+
Yield []Block
270280
}
271281

272282
// Namespace returns the block package, if defined. Otherwise returns "ego".
@@ -347,6 +357,15 @@ type Field struct {
347357
ValuePos Pos
348358
}
349359

360+
// Attr represents a key/value passthrough pair on a component.
361+
type Attr struct {
362+
Name string
363+
NamePos Pos
364+
365+
Value string
366+
ValuePos Pos
367+
}
368+
350369
// Position returns the position of the block.
351370
func Position(blk Block) Pos {
352371
switch blk := blk.(type) {
@@ -390,3 +409,13 @@ type stackElem struct {
390409
block Block
391410
yield []Block
392411
}
412+
413+
// AttrNames returns a sorted list of names for an attribute set.
414+
func AttrNames(attrs map[string]interface{}) []string {
415+
a := make([]string, 0, len(attrs))
416+
for k := range attrs {
417+
a = append(a, k)
418+
}
419+
sort.Strings(a)
420+
return a
421+
}

parse.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ func parseComponentBlock(s *Scanner, start *ComponentStartBlock) error {
7979
if err := parseAttrBlock(s, blk); err != nil {
8080
return err
8181
}
82-
start.Attrs = append(start.Attrs, blk)
82+
start.AttrBlocks = append(start.AttrBlocks, blk)
8383

8484
case *AttrEndBlock:
8585
return NewSyntaxError(blk.Pos, "Attribute end block found without start block: %s", shortComponentBlockString(blk))

scanner.go

Lines changed: 69 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ func (s *Scanner) scanComponentStartBlock() (_ *ComponentStartBlock, err error)
155155
return nil, err
156156
}
157157

158-
// Scan fields.
158+
// Scan attributes & fields.
159159
for {
160160
s.skipWhitespace()
161161
if ch := s.peek(); ch == '>' {
@@ -167,11 +167,20 @@ func (s *Scanner) scanComponentStartBlock() (_ *ComponentStartBlock, err error)
167167
break
168168
}
169169

170-
field, err := s.scanField()
170+
if ch := s.peek(); unicode.IsUpper(ch) {
171+
field, err := s.scanField()
172+
if err != nil {
173+
return nil, err
174+
}
175+
b.Fields = append(b.Fields, field)
176+
continue
177+
}
178+
179+
attr, err := s.scanAttr()
171180
if err != nil {
172181
return nil, err
173182
}
174-
b.Fields = append(b.Fields, field)
183+
b.Attrs = append(b.Attrs, attr)
175184
}
176185

177186
return b, nil
@@ -378,6 +387,45 @@ func (s *Scanner) scanField() (*Field, error) {
378387
}, nil
379388
}
380389

390+
func (s *Scanner) scanAttr() (*Attr, error) {
391+
s.skipWhitespace()
392+
393+
// First scan is the HTML attribute name.
394+
namePos := s.pos
395+
name, err := s.scanAttrName()
396+
if err != nil {
397+
return nil, err
398+
}
399+
s.skipWhitespace()
400+
401+
// If we see an identifier or tag close then only save the name.
402+
if ch := s.peek(); ch == '>' || isIdentStart(ch) {
403+
return &Attr{Name: name, NamePos: namePos}, nil
404+
} else if ch := s.peekN(2); ch == "/>" {
405+
return &Attr{Name: name, NamePos: namePos}, nil
406+
}
407+
408+
// Expect an equals sign next.
409+
if ch := s.read(); ch != '=' {
410+
return nil, NewSyntaxError(s.pos, "Expected '=', found %s", runeString(ch))
411+
}
412+
s.skipWhitespace()
413+
414+
// Parse expression.
415+
valuePos := s.pos
416+
value, err := s.scanExpr()
417+
if err != nil {
418+
return nil, err
419+
}
420+
421+
return &Attr{
422+
Name: name,
423+
NamePos: namePos,
424+
Value: value,
425+
ValuePos: valuePos,
426+
}, nil
427+
}
428+
381429
func (s *Scanner) peekIdent() bool {
382430
ident, _ := s.scanIdent()
383431
return ident != ""
@@ -401,6 +449,24 @@ func (s *Scanner) scanIdent() (string, error) {
401449
return buf.String(), nil
402450
}
403451

452+
func (s *Scanner) scanAttrName() (string, error) {
453+
var buf bytes.Buffer
454+
455+
// First rune must be a letter.
456+
ch := s.read()
457+
if !isIdentStart(ch) {
458+
return "", NewSyntaxError(s.pos, "Expected identifier, found %s", runeString(ch))
459+
}
460+
buf.WriteRune(ch)
461+
462+
// Keep scanning while we have letters or digits.
463+
for ch := s.peek(); unicode.IsLetter(ch) || unicode.IsDigit(ch) || ch == '_' || ch == ':' || ch == '.' || ch == '-'; ch = s.peek() {
464+
buf.WriteRune(s.read())
465+
}
466+
467+
return buf.String(), nil
468+
}
469+
404470
func (s *Scanner) scanExpr() (string, error) {
405471
var buf bytes.Buffer
406472
pos := s.pos

0 commit comments

Comments
 (0)