package hatmill import "fmt" import "io" import "os" import "strings" // Html is either Element or Text type Html interface { io.WriterTo // isHtml is a no-op method to prevent external Html implementations, because // Go doesn't have sum types. isHtml() } // writeStringsTo attempts to write the strings in ss to w, incrementing n by the // number of bytes written. func writeStringsTo(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 } type Attrib struct { Key string Value string } func (a Attrib) WriteTo(w io.Writer) (n int64, err error) { err = writeStringsTo(w, &n, a.Key) if err != nil { return } if a.Value != "" { err = writeStringsTo(w, &n, "='", a.Value, "'") } return } type EmptyElement struct { TagName string Attribs []Attrib } func (EmptyElement) isHtml() {} func (e EmptyElement) WriteTo(w io.Writer) (n int64, err error) { err = writeStringsTo(w, &n, "<", e.TagName) if err != nil { return } for _, attrib := range e.Attribs { err = writeStringsTo(w, &n, " ") if err != nil { return } var nCurr int64 nCurr, err = attrib.WriteTo(w) n += int64(nCurr) if err != nil { return } } err = writeStringsTo(w, &n, ">") return } type ParentElement struct { EmptyElement Children []Html } func (e *ParentElement) isHtml() {} func (e *ParentElement) WriteTo(w io.Writer) (n int64, err error) { n, err = e.EmptyElement.WriteTo(w) if err != nil { return } for _, child := range e.Children { var nCurr int64 nCurr, err = child.WriteTo(w) n += int64(nCurr) if err != nil { return } } err = writeStringsTo(w, &n, "") return } type Text string func (Text) isHtml() {} func (t Text) WriteTo(w io.Writer) (n int64, err error) { err = writeStringsTo(w, &n, string(t)) return } type RText struct { io.Reader } 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) *ParentElement { return func(children ...Html) *ParentElement { return &ParentElement{ EmptyElement: EmptyElement{ TagName: "div", Attribs: attribs, }, Children: children, } } } func Img(attribs ...Attrib) EmptyElement { return EmptyElement{ TagName: "img", Attribs: attribs, } } func Id(id string) Attrib { return Attrib{ Key: "id", Value: id, } } func Disabled() Attrib { return Attrib{ Key: "disabled", } } // WriteDocument writes an HTML5 doctype declaration, followed by root. // root should probably be an element. func WriteDocument(w io.Writer, root *ParentElement) (n int64, err error) { err = writeStringsTo(w, &n, "") if err != nil { return } var nroot int64 nroot, err = root.WriteTo(w) n += nroot return } func Main() { var page = Div(Disabled(), Id("container"))( Img(Id("profile-photo")), Text("hello"), Img(Disabled()), RText{strings.NewReader("goodbye!")}, ) n, err := WriteDocument(os.Stdout, page) fmt.Println() fmt.Println(n) if err != nil { fmt.Println(err) } }