From 61d35db0e60156e041ce91299a138399fe006421 Mon Sep 17 00:00:00 2001 From: Radu Lucut Date: Wed, 13 Dec 2023 14:08:27 +0200 Subject: [PATCH] Add test for ToHTML --- README.md | 4 +- fixtures/basic.html | 1 + example.htnoml => fixtures/basic.htnoml | 4 +- go.mod | 2 + go.sum | 2 + parser.go | 64 ++++++++++++++++++++----- parser_test.go | 20 +++++--- utils.go | 10 ++++ 8 files changed, 85 insertions(+), 22 deletions(-) create mode 100644 fixtures/basic.html rename example.htnoml => fixtures/basic.htnoml (83%) create mode 100644 go.sum create mode 100644 utils.go diff --git a/README.md b/README.md index 572ab22..555a5a5 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # htnoml -Experimental DSL that compiles to HTML. +Experimental alternative to HTML ### Example @@ -14,7 +14,7 @@ Experimental DSL that compiles to HTML. {:link rel[stylesheet] href[css/style.css]} } {:body - {class[container main] > This HTnoML syntax} + {class[container main] > This is HTnoML syntax} } } ``` diff --git a/fixtures/basic.html b/fixtures/basic.html new file mode 100644 index 0000000..2a3e792 --- /dev/null +++ b/fixtures/basic.html @@ -0,0 +1 @@ + This is a HTML5 template
This is HTnoML syntax
\ No newline at end of file diff --git a/example.htnoml b/fixtures/basic.htnoml similarity index 83% rename from example.htnoml rename to fixtures/basic.htnoml index 792e44c..93f7a78 100644 --- a/example.htnoml +++ b/fixtures/basic.htnoml @@ -7,6 +7,6 @@ {:link rel[stylesheet] href[css/style.css]} } {:body - {class[container main] > This HTnoML syntax} + {class[container main] > This is HTnoML syntax} } -} +} \ No newline at end of file diff --git a/go.mod b/go.mod index 8abcea1..d50277f 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,5 @@ module github.com/radulucut/htnoml go 1.20 + +require github.com/google/go-cmp v0.6.0 // indirect diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..5a8d551 --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= diff --git a/parser.go b/parser.go index bb98f40..22b4472 100644 --- a/parser.go +++ b/parser.go @@ -1,13 +1,8 @@ package htnoml -import "io" - -type Node struct { - ElementType *Chunk - Text *Chunk // Only for text nodes - Attributes []Attribute // Only for element nodes - Children []Node // Only for element nodes -} +import ( + "io" +) type Chunk struct { Start int @@ -19,6 +14,13 @@ type Attribute struct { Value *Chunk } +type Node struct { + ElementType *Chunk + Text *Chunk // Only for text nodes + Attributes []Attribute // Only for element nodes + Children []Node // Only for element nodes +} + type TokenType byte const ( @@ -54,7 +56,42 @@ func NewParser(reader io.ReadSeeker) (*Parser, error) { }, nil } -func (p *Parser) Parse() { +func (p *Parser) ToHTML() string { + if p.node == nil { + p.parse() + } + return p.nodeToHTML(p.node, true) +} + +func (p *Parser) nodeToHTML(node *Node, isRoot bool) string { + if node.Text != nil { + return string(p.buf[node.Text.Start:node.Text.End]) + } + children := "" + for _, child := range node.Children { + children += p.nodeToHTML(&child, false) + } + if isRoot { + return children + } + attributes := "" + for _, attr := range node.Attributes { + attributes += " " + string(p.buf[attr.Name.Start:attr.Name.End]) + if attr.Value != nil { + attributes += "=" + "\"" + string(p.buf[attr.Value.Start:attr.Value.End]) + "\"" + } + } + if node.ElementType == nil { + return "" + children + "" + } + tag := string(p.buf[node.ElementType.Start:node.ElementType.End]) + if children == "" && isVoidElement(tag) { + return "<" + tag + attributes + " />" + } + return "<" + tag + attributes + ">" + children + "" +} + +func (p *Parser) parse() { p.node = &Node{} childParentMap := map[*Node]*Node{} curr := p.node @@ -64,10 +101,12 @@ func (p *Parser) Parse() { continue } if t.Type == TokenTypeReserved && p.buf[t.Start] == ':' { + t = p.nextToken() curr.ElementType = &Chunk{ Start: t.Start, End: t.End, } + continue } if t.Type == TokenTypeIdentifier { name := &Chunk{ @@ -93,6 +132,7 @@ func (p *Parser) Parse() { Name: name, Value: value, }) + continue } if t.Type == TokenTypeReserved && p.buf[t.Start] == '{' { curr.Children = append(curr.Children, Node{}) @@ -119,6 +159,7 @@ func (p *Parser) Parse() { End: t.End, }, }) + continue } } } @@ -154,7 +195,7 @@ func (p *Parser) readNextToken() *Token { return &Token{ Type: TokenTypeWhitespace, Start: start, - End: p.pos - 1, + End: p.pos, } } if p.isReserved(p.buf[p.pos]) { @@ -163,6 +204,7 @@ func (p *Parser) readNextToken() *Token { return &Token{ Type: TokenTypeReserved, Start: p.pos - 1, + End: p.pos, } } start := p.pos @@ -173,7 +215,7 @@ func (p *Parser) readNextToken() *Token { return &Token{ Type: TokenTypeIdentifier, Start: start, - End: p.pos - 1, + End: p.pos, } } diff --git a/parser_test.go b/parser_test.go index ee4ec02..f7dc119 100644 --- a/parser_test.go +++ b/parser_test.go @@ -1,15 +1,16 @@ package htnoml import ( - "log" "os" "testing" + + "github.com/google/go-cmp/cmp" ) func TestParser(t *testing.T) { - t.Run("Parse", func(t *testing.T) { - t.Run("should return correct html", func(t *testing.T) { - f, err := os.Open("example.htnoml") + t.Run("ToHTML", func(t *testing.T) { + t.Run("basic.htnoml - should return correct html", func(t *testing.T) { + f, err := os.Open("fixtures/basic.htnoml") if err != nil { t.Fatal(err) } @@ -18,9 +19,14 @@ func TestParser(t *testing.T) { if err != nil { t.Fatal(err) } - p.Parse() - log.Printf("%+v", p.node) - // TODO: assert + html := p.ToHTML() + expected, err := os.ReadFile("fixtures/basic.html") + if err != nil { + t.Fatal(err) + } + if diff := cmp.Diff(string(expected), html); diff != "" { + t.Errorf("ToHTML() mismatch (-want +got):\n%s", diff) + } }) }) } diff --git a/utils.go b/utils.go new file mode 100644 index 0000000..11d6ecd --- /dev/null +++ b/utils.go @@ -0,0 +1,10 @@ +package htnoml + +func isVoidElement(tag string) bool { + switch tag { + case "area", "base", "br", "col", "embed", "hr", "img", "input", "link", + "meta", "param", "source", "track", "wbr": + return true + } + return false +}