~repos /gromer
git clone
https://pyrossh.dev/repos/gromer.git
gromer is a framework and cli to build multipage web apps in golang using htmx and alpinejs.
file:
gsx/parser.go
package gsx
import ( "strings"
"github.com/alecthomas/participle/v2" "github.com/alecthomas/participle/v2/lexer" "github.com/goneric/stack" "github.com/rotisserie/eris")
type Module struct { Pos lexer.Position Nodes []*AstNode `@@*`}
type AstNode struct { Pos lexer.Position Open *Open `@@` Close *Close `| @@` Content *Literal `| @@`}
type Open struct { Pos lexer.Position Name string `"<" @Ident` Attributes []*Attribute `[ @@ { @@ } ]` SelfClose string `@("/")? ">"`}
type Close struct { Pos lexer.Position Name string `"<""/"@Ident">"`}
type ForStatement struct { Pos lexer.Position `"for"` Index string `@Ident ","` Key string `@Ident` Reference string `":""=""range" @Ident` Statements []*Statement `"{" @@* "}"`}
type Statement struct { ReturnStatement *ReturnStatement `@@`}
type ReturnStatement struct { Nodes []*AstNode `"return" "(" @@* ")"` Tags []*Tag}
type Attribute struct { Pos lexer.Position Key string `@":"? @Ident ( @"-" @Ident )*` Value *Literal `"=" @@`}
type KV struct { Pos lexer.Position Key string `@String` Value string `":" @"!"? @Ident ( @"." @Ident )*`}
type Literal struct { Pos lexer.Position Str *string `@String` Ref *string `| "{" @Ident ( @"." @Ident )* "}"` KV []*KV `| "{" [ @@ { "," @@ } ] "}"` For *ForStatement `| @@`}
func (l *Literal) Clone() *Literal { if l == nil { return nil } newLiteral := &Literal{} if l.Str != nil { v := "" + *l.Str newLiteral.Str = &v } if l.Ref != nil { v := "" + *l.Ref newLiteral.Ref = &v } if l.KV != nil { newLiteral.KV = []*KV{} for _, kv := range l.KV { newLiteral.KV = append(newLiteral.KV, &KV{ Key: "" + kv.Key, Value: "" + kv.Value, }) } } // TODO copy for return newLiteral}
var htmlParser = participle.MustBuild[Module]()
type Tag struct { Name string Text *Literal Attributes []*Attribute Children []*Tag SelfClosing bool}
func (t *Tag) Clone() *Tag { newTag := &Tag{ Name: t.Name, Text: t.Text.Clone(), Attributes: []*Attribute{}, SelfClosing: t.SelfClosing, Children: []*Tag{}, } for _, v := range t.Attributes { newTag.Attributes = append(newTag.Attributes, &Attribute{ Key: v.Key, Value: v.Value.Clone(), }) } for _, child := range t.Children { newTag.Children = append(newTag.Children, child.Clone()) } return newTag}
func cloneTags(tags []*Tag) []*Tag { newTags := []*Tag{} for _, v := range tags { newTags = append(newTags, v.Clone()) } return newTags}
func RenderString(tags []*Tag) string { s := "" for _, t := range tags { s += RenderTagString(t, "") + "\n" } return s}
func RenderTagString(x *Tag, space string) string { if x.Name == "" { if x.Text != nil && x.Text.Str != nil { return space + strings.ReplaceAll(*x.Text.Str, `"`, "") } if x.Text != nil && x.Text.Ref != nil { return space + "{" + *x.Text.Ref + "}" } } if x.Name == "fragment" { s := "" for _, c := range x.Children { s += RenderTagString(c, space) + "\n" } return s } s := space + "<" + x.Name for _, a := range x.Attributes { if a.Value.Str != nil && *a.Value.Str != "" { s += " " + a.Key + `="` + *a.Value.Str + `"` } } if x.SelfClosing { s += " />" } else { s += ">\n" } if !x.SelfClosing { for _, c := range x.Children { s += RenderTagString(c, space+" ") + "\n" } s += space + "</" + x.Name + ">" } return s}
func processTree(nodes []*AstNode) []*Tag { tags := []*Tag{} var prevTag *Tag stack := stack.New[*Tag]() for _, n := range nodes { if n.Open != nil { newTag := &Tag{ Name: n.Open.Name, Attributes: n.Open.Attributes, SelfClosing: n.Open.SelfClose == "/", } if prevTag != nil { prevTag.Children = append(prevTag.Children, newTag) if !newTag.SelfClosing { stack.Push(prevTag) prevTag = newTag } } else { tags = append(tags, newTag) if !newTag.SelfClosing { prevTag = newTag } } } else if n.Close != nil { if n.Close.Name == prevTag.Name { prevTag, _ = stack.Pop() } else { panic(eris.Errorf("Brackets not matching for tag %s in line %d:%d, prevTag: %s", n.Close.Name, n.Close.Pos.Line, n.Close.Pos.Column, prevTag.Name)) } } else if n.Content != nil { newTag := &Tag{ Name: "", Text: n.Content, } if n.Content.For != nil { for _, s := range n.Content.For.Statements { if s.ReturnStatement != nil { s.ReturnStatement.Tags = processTree(s.ReturnStatement.Nodes) } } } if prevTag != nil { prevTag.Children = append(prevTag.Children, newTag) } else { tags = append(tags, newTag) } } } return tags}
func parse(name, s string) []*Tag { ast, err := htmlParser.ParseString(name, s) if err != nil { println("name", name) panic(err) } return processTree(ast.Nodes)}