Skip to content

Commit

Permalink
Add links to godoc (#183)
Browse files Browse the repository at this point in the history
Also add an example for `http.Adapt`.

Fixes #182
  • Loading branch information
markuswustenberg authored Jun 26, 2024
1 parent a79e6d9 commit 8b43a90
Show file tree
Hide file tree
Showing 5 changed files with 54 additions and 39 deletions.
5 changes: 3 additions & 2 deletions components/components.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
. "github.com/maragudk/gomponents/html"
)

// HTML5Props for HTML5.
// HTML5Props for [HTML5].
// Title is set no matter what, Description and Language elements only if the strings are non-empty.
type HTML5Props struct {
Title string
Expand Down Expand Up @@ -41,6 +41,7 @@ func HTML5(p HTML5Props) g.Node {
// for which the corresponding map value is true.
type Classes map[string]bool

// Render satisfies [g.Node].
func (c Classes) Render(w io.Writer) error {
var included []string
for c, include := range c {
Expand All @@ -56,7 +57,7 @@ func (c Classes) Type() g.NodeType {
return g.AttributeType
}

// String satisfies fmt.Stringer.
// String satisfies [fmt.Stringer].
func (c Classes) String() string {
var b strings.Builder
_ = c.Render(&b)
Expand Down
64 changes: 34 additions & 30 deletions gomponents.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
// Package gomponents provides view components in Go, that render to HTML 5.
// Package gomponents provides HTML components in Go, that render to HTML 5.
//
// The primary interface is a Node. It describes a function Render, which should render the Node
// The primary interface is a [Node]. It describes a function Render, which should render the [Node]
// to the given writer as a string.
//
// All DOM elements and attributes can be created by using the El and Attr functions.
// The functions Text, Textf, Raw, and Rawf can be used to create text nodes, either HTML-escaped or unescaped.
// See also helper functions Group, Map, and If for mapping data to Nodes and inserting them conditionally.
// All DOM elements and attributes can be created by using the [El] and [Attr] functions.
// The functions [Text], [Textf], [Raw], and [Rawf] can be used to create text nodes, either HTML-escaped or unescaped.
// See also helper functions [Group], [Map], [If], and [Iff] for mapping data to nodes and inserting them conditionally.
//
// For basic HTML elements and attributes, see the package html.
//
// For higher-level HTML components, see the package components.
//
// For SVG elements and attributes, see the package svg.
//
// For HTTP helpers, see the package http.
package gomponents

Expand All @@ -20,31 +23,32 @@ import (
"strings"
)

// Node is a DOM node that can Render itself to a io.Writer.
// Node is a DOM node that can Render itself to a [io.Writer].
type Node interface {
Render(w io.Writer) error
}

// NodeType describes what type of Node it is, currently either an ElementType or an AttributeType.
// This decides where a Node should be rendered.
// Nodes default to being ElementType.
// NodeType describes what type of [Node] it is, currently either an [ElementType] or an [AttributeType].
// This decides where a [Node] should be rendered.
// Nodes default to being [ElementType].
type NodeType int

const (
ElementType = NodeType(iota)
AttributeType
)

// nodeTypeDescriber can be implemented by Nodes to let callers know whether the Node is
// an ElementType or an AttributeType. This is used for rendering.
// nodeTypeDescriber can be implemented by Nodes to let callers know whether the [Node] is
// an [ElementType] or an [AttributeType].
// See [NodeType].
type nodeTypeDescriber interface {
Type() NodeType
}

// NodeFunc is a render function that is also a Node of ElementType.
// NodeFunc is a render function that is also a [Node] of [ElementType].
type NodeFunc func(io.Writer) error

// Render satisfies Node.
// Render satisfies [Node].
func (n NodeFunc) Render(w io.Writer) error {
return n(w)
}
Expand All @@ -54,19 +58,19 @@ func (n NodeFunc) Type() NodeType {
return ElementType
}

// String satisfies fmt.Stringer.
// String satisfies [fmt.Stringer].
func (n NodeFunc) String() string {
var b strings.Builder
_ = n.Render(&b)
return b.String()
}

// El creates an element DOM Node with a name and child Nodes.
// El creates an element DOM [Node] with a name and child Nodes.
// See https://dev.w3.org/html5/spec-LC/syntax.html#elements-0 for how elements are rendered.
// No tags are ever omitted from normal tags, even though it's allowed for elements given at
// https://dev.w3.org/html5/spec-LC/syntax.html#optional-tags
// If an element is a void element, non-attribute children nodes are ignored.
// Use this if no convenience creator exists.
// Use this if no convenience creator exists in the html package.
func El(name string, children ...Node) Node {
return NodeFunc(func(w2 io.Writer) error {
w := &statefulWriter{w: w2}
Expand Down Expand Up @@ -156,11 +160,11 @@ func isVoidElement(name string) bool {
return ok
}

// Attr creates an attribute DOM Node with a name and optional value.
// Attr creates an attribute DOM [Node] with a name and optional value.
// If only a name is passed, it's a name-only (boolean) attribute (like "required").
// If a name and value are passed, it's a name-value attribute (like `class="header"`).
// More than one value make Attr panic.
// Use this if no convenience creator exists.
// More than one value make [Attr] panic.
// Use this if no convenience creator exists in the html package.
func Attr(name string, value ...string) Node {
switch len(value) {
case 0:
Expand All @@ -177,7 +181,7 @@ type attr struct {
value *string
}

// Render satisfies Node.
// Render satisfies [Node].
func (a *attr) Render(w io.Writer) error {
if a.value == nil {
_, err := w.Write([]byte(" " + a.name))
Expand All @@ -187,43 +191,43 @@ func (a *attr) Render(w io.Writer) error {
return err
}

// Type satisfies nodeTypeDescriber.
// Type satisfies [nodeTypeDescriber].
func (a *attr) Type() NodeType {
return AttributeType
}

// String satisfies fmt.Stringer.
// String satisfies [fmt.Stringer].
func (a *attr) String() string {
var b strings.Builder
_ = a.Render(&b)
return b.String()
}

// Text creates a text DOM Node that Renders the escaped string t.
// Text creates a text DOM [Node] that Renders the escaped string t.
func Text(t string) Node {
return NodeFunc(func(w io.Writer) error {
_, err := w.Write([]byte(template.HTMLEscapeString(t)))
return err
})
}

// Textf creates a text DOM Node that Renders the interpolated and escaped string format.
// Textf creates a text DOM [Node] that Renders the interpolated and escaped string format.
func Textf(format string, a ...interface{}) Node {
return NodeFunc(func(w io.Writer) error {
_, err := w.Write([]byte(template.HTMLEscapeString(fmt.Sprintf(format, a...))))
return err
})
}

// Raw creates a text DOM Node that just Renders the unescaped string t.
// Raw creates a text DOM [Node] that just Renders the unescaped string t.
func Raw(t string) Node {
return NodeFunc(func(w io.Writer) error {
_, err := w.Write([]byte(t))
return err
})
}

// Rawf creates a text DOM Node that just Renders the interpolated and unescaped string format.
// Rawf creates a text DOM [Node] that just Renders the interpolated and unescaped string format.
func Rawf(format string, a ...interface{}) Node {
return NodeFunc(func(w io.Writer) error {
_, err := w.Write([]byte(fmt.Sprintf(format, a...)))
Expand All @@ -235,12 +239,12 @@ type group struct {
children []Node
}

// String satisfies fmt.Stringer.
// String satisfies [fmt.Stringer].
func (g group) String() string {
panic("cannot render group directly")
}

// Render satisfies Node.
// Render satisfies [Node].
func (g group) Render(io.Writer) error {
panic("cannot render group directly")
}
Expand All @@ -252,9 +256,9 @@ func Group(children []Node) Node {
return group{children: children}
}

// If condition is true, return the given Node. Otherwise, return nil.
// If condition is true, return the given [Node]. Otherwise, return nil.
// This helper function is good for inlining elements conditionally.
// If it's important that the given Node is only evaluated if condition is true
// If it's important that the given [Node] is only evaluated if condition is true
// (for example, when using nilable variables), use [Iff] instead.
func If(condition bool, n Node) Node {
if condition {
Expand Down
4 changes: 3 additions & 1 deletion html/elements.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
// Package html provides common HTML elements and attributes.
//
// See https://developer.mozilla.org/en-US/docs/Web/HTML/Element for a list of elements.
//
// See https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes for a list of attributes.
package html

Expand All @@ -9,7 +11,7 @@ import (
g "github.com/maragudk/gomponents"
)

// Doctype returns a special kind of Node that prefixes its sibling with the string "<!doctype html>".
// Doctype returns a special kind of [g.Node] that prefixes its sibling with the string "<!doctype html>".
func Doctype(sibling g.Node) g.Node {
return g.NodeFunc(func(w io.Writer) error {
if _, err := w.Write([]byte("<!doctype html>")); err != nil {
Expand Down
12 changes: 6 additions & 6 deletions http/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,18 @@ import (
g "github.com/maragudk/gomponents"
)

// Handler is like http.Handler but returns a Node and an error.
// See Adapt for how errors are translated to HTTP responses.
// Handler is like [http.Handler] but returns a [g.Node] and an error.
// See [Adapt] for how errors are translated to HTTP responses.
type Handler = func(http.ResponseWriter, *http.Request) (g.Node, error)

type errorWithStatusCode interface {
StatusCode() int
}

// Adapt a Handler to a http.Handlerfunc.
// The returned Node is rendered to the ResponseWriter, in both normal and error cases.
// If the Handler returns an error, and it implements a "StatusCode() int" method, that HTTP status code is sent
// in the response header. Otherwise, the status code http.StatusInternalServerError (500) is used.
// Adapt a [Handler] to a [http.HandlerFunc].
// The returned [g.Node] is rendered to the [http.ResponseWriter], in both normal and error cases.
// If the [Handler] returns an error, and it implements a "StatusCode() int" method, that HTTP status code is sent
// in the response header. Otherwise, the status code [http.StatusInternalServerError] (500) is used.
func Adapt(h Handler) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
n, err := h(w, r)
Expand Down
8 changes: 8 additions & 0 deletions http/handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,3 +112,11 @@ func get(t *testing.T, h http.Handler) (int, string) {
}
return result.StatusCode, string(body)
}

func ExampleAdapt() {
h := ghttp.Adapt(func(w http.ResponseWriter, r *http.Request) (g.Node, error) {
return g.El("div"), nil
})
mux := http.NewServeMux()
mux.Handle("/", h)
}

0 comments on commit 8b43a90

Please sign in to comment.