package main import "fmt" import "io" import "os" import "strings" // Html is either Element or Text type Html interface { io.WriterTo // isHtml is a non-functional marker to differentiate Html from interface{} isHtml() } func Reader() io.Reader { // TODO in-order traversal of HTML tree to spit out bytes return nil } func String() string { panic("this could be trivially implemented with a Reader and a bytes.Buffer") } type Attrib struct { Key string Value string } type element struct { tagName string attribs []Attrib empty bool children []Html } // writeTo attempts to write the strings in ss to w, incrementing n by the // number of bytes written. func writeTo(w io.Writer, n *int64, ss ...string) error { for _, s := range ss { nCurr, err := io.WriteString(w, s) *n += int64(nCurr) if err != nil { return err } } return nil } func (a Attrib) WriteTo(w io.Writer) (n int64, err error) { err = writeTo(w, &n, a.Key) if err != nil { return } if a.Value != "" { err = writeTo(w, &n, "='", a.Value, "'") } return } func (e *element) WriteTo(w io.Writer) (n int64, err error) { err = writeTo(w, &n, "<", e.tagName) if err != nil { return } for _, attrib := range e.attribs { err = writeTo(w, &n, " ") if err != nil { return } var nCurr int64 nCurr, err = attrib.WriteTo(w) n += int64(nCurr) if err != nil { return } } err = writeTo(w, &n, ">") if err != nil { return } if !e.empty { for _, child := range e.children { var nCurr int64 nCurr, err = child.WriteTo(w) n += int64(nCurr) if err != nil { return } } err = writeTo(w, &n, "") } return } func (*element) isHtml() {} func emptyElement(tagName string, attribs []Attrib) Html { return &element{ tagName: tagName, attribs: attribs, empty: true, } } func elementFunc(tagName string, attribs []Attrib) func(...Html) Html { return func(children ...Html) Html { return &element{ tagName: tagName, attribs: attribs, children: children, empty: false, } } } type text string func Text(s string) Html { return text(s) } func (text) isHtml() {} func (t text) WriteTo(w io.Writer) (n int64, err error) { err = writeTo(w, &n, string(t)) return } type rText struct { io.Reader } func RText(r io.Reader) Html { return rText{Reader: r} } func (rText) isHtml() {} func (rt rText) WriteTo(w io.Writer) (n int64, err error) { return io.Copy(w, rt.Reader) } // Html5 functions func Div(attribs ...Attrib) func(children ...Html) Html { return elementFunc("div", attribs) } func Img(attribs ...Attrib) Html { return emptyElement("img", attribs) } func Id(id string) Attrib { return Attrib{ Key: "id", Value: id, } } func Disabled() Attrib { return Attrib { Key: "disabled", } } func main() { var page = Div(Disabled(), Id("container"))( Img(Id("profile-photo")), Text("hello"), Img(Disabled()), RText(strings.NewReader("goodbye!")), ) n, err := page.WriteTo(os.Stdout) fmt.Println() fmt.Println(n) fmt.Println(err) }