Skip to content

Instantly share code, notes, and snippets.

@breadchris
Created August 17, 2024 20:32
Show Gist options
  • Save breadchris/cee5c14b3a3521637e3eadfb6c7c12e6 to your computer and use it in GitHub Desktop.
Save breadchris/cee5c14b3a3521637e3eadfb6c7c12e6 to your computer and use it in GitHub Desktop.
HTML as a go library
package main
import (
"fmt"
"io"
"net/http"
)
type Node struct {
Name string
text string
Attrs map[string]string
Children []*Node
}
func (s *Node) Init(_ *Node) *Node {
return s
}
func (s *Node) Render() string {
c := ""
for _, t := range s.Children {
c += t.Render()
}
a := ""
for k, v := range s.Attrs {
a += fmt.Sprintf("%s=%s ", k, v)
}
return fmt.Sprintf("<%s %s>%s</%s>", s.Name, a, c, s.Name)
}
type NodeOption interface {
Init(n *Node) *Node
}
func newNode(s string, o []NodeOption) *Node {
n := &Node{
Name: s,
Attrs: map[string]string{},
}
for _, op := range o {
if c := op.Init(n); c != nil {
n.Children = append(n.Children, op.Init(n))
}
}
return n
}
func Html(o ...NodeOption) *Node {
return newNode("html", o)
}
func Head(o ...NodeOption) *Node {
return newNode("head", o)
}
func Meta(o ...NodeOption) *Node {
return newNode("meta", o)
}
func Body(o ...NodeOption) *Node {
return newNode("body", o)
}
type TransformNode struct {
transform func(p *Node) *Node
}
func (s *TransformNode) Init(p *Node) *Node {
return s.transform(p)
}
func Class(s string) *TransformNode {
return &TransformNode{
transform: func(p *Node) *Node {
p.Attrs["class"] = s
return nil
},
}
}
func Div(o ...NodeOption) *Node {
return newNode("div", o)
}
func Header(o ...NodeOption) *Node {
return newNode("header", o)
}
func Nav(o ...NodeOption) *Node {
return newNode("nav", o)
}
func Ul(o ...NodeOption) *Node {
return newNode("ul", o)
}
func Li(o ...NodeOption) *Node {
return newNode("li", o)
}
func A(o ...NodeOption) *Node {
return newNode("a", o)
}
func H1(o ...NodeOption) *Node {
return newNode("h1", o)
}
func H2(o ...NodeOption) *Node {
return newNode("h2", o)
}
func Form(o ...NodeOption) *Node {
return newNode("form", o)
}
func Label(o ...NodeOption) *Node {
return newNode("label", o)
}
func Input(o ...NodeOption) *Node {
return newNode("input", o)
}
func TextArea(o ...NodeOption) *Node {
return newNode("textarea", o)
}
func Button(o ...NodeOption) *Node {
return newNode("button", o)
}
func Title(o ...NodeOption) *Node {
return newNode("title", o)
}
func Link(o ...NodeOption) *Node {
return newNode("link", o)
}
func Script(o ...NodeOption) *Node {
return newNode("script", o)
}
func Style(o ...NodeOption) *Node {
return newNode("style", o)
}
func Main(o ...NodeOption) *Node {
return newNode("main", o)
}
func Section(o ...NodeOption) *Node {
return newNode("section", o)
}
func Method(s string) *TransformNode {
return &TransformNode{
transform: func(p *Node) *Node {
p.Attrs["method"] = s
return nil
},
}
}
func Action(s string) *TransformNode {
return &TransformNode{
transform: func(p *Node) *Node {
p.Attrs["action"] = s
return nil
},
}
}
func Text(s string) *TransformNode {
return &TransformNode{
transform: func(p *Node) *Node {
p.text = s
return nil
},
}
}
func Src(s string) *TransformNode {
return &TransformNode{
transform: func(p *Node) *Node {
p.Attrs["src"] = s
return nil
},
}
}
func Href(s string) *TransformNode {
return &TransformNode{
transform: func(p *Node) *Node {
p.Attrs["href"] = s
return nil
},
}
}
func Attr(k, v string) *TransformNode {
return &TransformNode{
transform: func(p *Node) *Node {
p.Attrs[k] = v
return nil
},
}
}
func For(s string) *TransformNode {
return &TransformNode{
transform: func(p *Node) *Node {
p.Attrs["for"] = s
return nil
},
}
}
func Id(s string) *TransformNode {
return &TransformNode{
transform: func(p *Node) *Node {
p.Attrs["id"] = s
return nil
},
}
}
func Name(s string) *TransformNode {
return &TransformNode{
transform: func(p *Node) *Node {
p.Attrs["name"] = s
return nil
},
}
}
func Rows(i int) *TransformNode {
return &TransformNode{
transform: func(p *Node) *Node {
p.Attrs["rows"] = fmt.Sprintf("%d", i)
return nil
},
}
}
func Type(s string) *TransformNode {
return &TransformNode{
transform: func(p *Node) *Node {
p.Attrs["type"] = s
return nil
},
}
}
var (
At = Attr
T = Text
)
func render(w io.Writer, n *Node) (int, error) {
return w.Write([]byte(n.Render()))
}
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
m := Main(Class("mt-8"),
Section(Class("text-center"),
H2(Text("Submit a New Recipe")),
Form(Method("POST"), Action("/submit"),
Div(Class("mb-4"),
Label(For("title"), T("Recipe Title")),
Input(Type("text"), Id("title"), Name("title"), Class("border rounded w-full py-2 px-3")),
),
Div(Class("mb-4"),
Label(For("ingredients"), T("Ingredients")),
TextArea(Id("ingredients"), Name("ingredients"), Class("border rounded w-full py-2 px-3"), Rows(5)),
),
Div(Class("mb-4"),
Label(For("instructions"), T("Instructions")),
TextArea(Id("instructions"), Name("instructions"), Class("border rounded w-full py-2 px-3"), Rows(5)),
),
Div(Class("text-center"),
Button(Type("submit"), Class("bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"), T("Submit Recipe")),
)),
),
)
nav := Nav(
Ul(Class("flex justify-center space-x-4"),
Li(A(Href("/"), T("Home")),
Li(A(Href("/recipes"), T("Recipes"))),
Li(A(Href("/submit"), T("Submit a Recipe"))),
),
),
)
render(w, Html(
Head(
Title(T("Recipe Site")),
Link(Href("https://cdn.jsdelivr.net/npm/[email protected]/dist/full.min.css"), At("rel", "stylesheet"), At("type", "text/css")),
Script(Src("https://cdn.tailwindcss.com")),
Style(T("body { font-family: 'Inter', sans-serif; }")),
),
Body(Class("min-h-screen flex flex-col items-center justify-center"),
Div(Class("container mx-auto p-4"),
Header(Class("text-center mb-4"),
H1(Text("Welcome to the Recipe Site")),
nav,
m,
),
),
),
))
})
if err := http.ListenAndServe(":8080", nil); err != nil {
panic(err)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment