diff --git a/pkg/token/kind/kind.go b/pkg/token/kind/kind.go index 4ba1b39..7d8985c 100644 --- a/pkg/token/kind/kind.go +++ b/pkg/token/kind/kind.go @@ -62,3 +62,14 @@ func (k Kind) String() string { } return "Unknown" } + +func (k Kind) IsTag() bool { + switch k { + case + Var, + OptionalBlock, + EndTag: + return true + } + return false +} diff --git a/pkg/token/token.go b/pkg/token/token.go index fd6bd0c..5d77200 100644 --- a/pkg/token/token.go +++ b/pkg/token/token.go @@ -129,20 +129,26 @@ func Tokenize(input []byte) (Slice, error) { } } - if ct.Kind == kind.Var && b == '>' { + if b == '>' && ct.Kind.IsTag() { ct.End = i tokens = append(tokens, ct) ct = T{} continue } - if b == '<' && len(input) > i && input[i+1] == '!' { + if b == '<' && len(input) > i { if ct.Kind != kind.Unset { ct.End = i tokens = append(tokens, ct) } - ct.Kind = kind.Var + switch input[i+1] { + case '!': + ct.Kind = kind.Var + case '?': + ct.Kind = kind.OptionalBlock + case '/': + ct.Kind = kind.EndTag + } ct.Start = i + 2 - } if b == '\n' { diff --git a/pkg/token/token_test.go b/pkg/token/token_test.go index fd920f7..e979064 100644 --- a/pkg/token/token_test.go +++ b/pkg/token/token_test.go @@ -374,3 +374,129 @@ func TestCombined(t *testing.T) { func joinLines(in ...[]byte) []byte { return bytes.Join(in, []byte{'\n'}) } + +func TestOptionalBlock(t *testing.T) { + testcases := []TestCase{ + { + name: "simple optional block", + def: func() ([]byte, token.Slice, error) { + start := []byte("") + text := []byte("some optional text") + end := []byte("") + input := bytes.Join([][]byte{start, text, end}, []byte{'\n'}) + want := token.Slice{ + { + Kind: kind.OptionalBlock, + Start: 2, + End: 10, + }, + { + Kind: kind.Text, + Start: 11, + End: 31, + }, + { + Kind: kind.EndTag, + Start: 33, + End: 41, + }, + } + return input, want, nil + }, + }, + { + name: "optional block with variable", + def: func() ([]byte, token.Slice, error) { + start := []byte("") + text1 := []byte("Hello") + varStart := []byte("") + text2 := []byte(" how are you?") + end := []byte("") + input := bytes.Join([][]byte{start, text1, varStart, text2, end}, []byte{' '}) + want := token.Slice{ + { + Kind: kind.OptionalBlock, + Start: 2, + End: 7, + }, + { + Kind: kind.Text, + Start: 8, + End: 15, + }, + { + Kind: kind.Var, + Start: 17, + End: 21, + }, + { + Kind: kind.Text, + Start: 22, + End: 37, + }, + { + Kind: kind.EndTag, + Start: 39, + End: 44, + }, + } + return input, want, nil + }, + }, + { + name: "nested optional blocks", + def: func() ([]byte, token.Slice, error) { + outer := []byte("") + text1 := []byte("start") + inner := []byte("") + text2 := []byte("inner text") + innerEnd := []byte("") + text3 := []byte("end") + outerEnd := []byte("") + input := bytes.Join([][]byte{outer, text1, inner, text2, innerEnd, text3, outerEnd}, []byte{'\n'}) + want := token.Slice{ + { + Kind: kind.OptionalBlock, + Start: 2, + End: 7, + }, + { + Kind: kind.Text, + Start: 8, + End: 15, + }, + { + Kind: kind.OptionalBlock, + Start: 17, + End: 22, + }, + { + Kind: kind.Text, + Start: 23, + End: 35, + }, + { + Kind: kind.EndTag, + Start: 37, + End: 42, + }, + { + Kind: kind.Text, + Start: 43, + End: 48, + }, + { + Kind: kind.EndTag, + Start: 50, + End: 55, + }, + } + return input, want, nil + }, + }, + } + + for _, tc := range testcases { + tc.Run(t) + } +}