document lib
This commit is contained in:
parent
33802919af
commit
c923639245
261
lib.go
261
lib.go
@ -1,4 +1,4 @@
|
|||||||
// Package html provides functions for programmatically composing and rendering HTML.
|
// Package html provides functions for programmatically composing and rendering HTML and HTML templates.
|
||||||
package html
|
package html
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@ -8,34 +8,69 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// when composing html, the Attr convenience function is recommended to construct input attributes
|
// Attributes contain one or more named attributes of HTML tags.
|
||||||
|
//
|
||||||
|
// Use the Attr constructure to define attributes. Attributes can be assigned to tags by calling the tag itself
|
||||||
|
// as a function with one or more Attributes instances as arguments, or by calling the SetAttribute convenience
|
||||||
|
// function. In both cases, the original tag remains unchanged, and only the resulting tag will contain the
|
||||||
|
// additional attributes. For setting CSS class attributes, the SetClass and the AddClass convenience functions
|
||||||
|
// can also be used.
|
||||||
type Attributes struct {
|
type Attributes struct {
|
||||||
names []string
|
names []string
|
||||||
values map[string]any
|
values map[string]any
|
||||||
}
|
}
|
||||||
|
|
||||||
// immutable
|
// Tag instances represent HTML tags.
|
||||||
// calling creates a new copy with the passed in attributes and child nodes applied only to the copy
|
//
|
||||||
// input parameters
|
// The tag subpackage contains a set of the common tags used in HTML. It is recommended to use the tag
|
||||||
// rendering of child nodes
|
// subpackage in code files dedicated to HTML composition and import the tag subpackage in these code files
|
||||||
// instances of tags can be used to create further tags with extended set of attributes and child tags
|
// inline, with the '.' notation. Custom tags can be defined with the Define function.
|
||||||
// builtin tags in the tags sub-package
|
//
|
||||||
// custom tags are supported via the NewTag constructor. Functions with the same signature, but created by other
|
// When applying a tag value as a function, a new tag is created, and the original tag remains unchanged.
|
||||||
// means, will not be rendered, unless they return a tag created by NewTag() or a builtin tag
|
// Therefore tags are immutable. When applying it without arguments, the resulting tag will be equivalent to the
|
||||||
|
// original tag. For the rules of equivalence, see the documentation of the Eq function.
|
||||||
|
//
|
||||||
|
// When applying a tag value as a function with arguments, the arguments can be attributes or children. Children
|
||||||
|
// can be other tags or any value. When a tag is rendered, non-tag children will be rendered with the fmt.Sprint
|
||||||
|
// function. The original tag will remain unchanged, while the resulting tag will have the attributes and
|
||||||
|
// children appended to the attributes and children of the original tag. This way a tag can be used as a
|
||||||
|
// template for further, more specialized tags of the same name. A tag is a valid tag as is, it does not need to
|
||||||
|
// be applied as a function unless additional attributers or children need to be added.
|
||||||
|
//
|
||||||
|
// Non-tag children are HTML escaped when rendering, unless the tag is marked as verbatim, with the Verbatim
|
||||||
|
// function, or as script with the ScriptContent function.
|
||||||
type Tag func(...any) Tag
|
type Tag func(...any) Tag
|
||||||
|
|
||||||
|
// Template instances are templates that convert any input data of a specified type to tags of custom internal
|
||||||
|
// structure. Templates processing data consisting of a list of items must return the resulting tags wrapped by
|
||||||
|
// a single tag.
|
||||||
type Template[Data any] func(Data) Tag
|
type Template[Data any] func(Data) Tag
|
||||||
|
|
||||||
|
// Indentation is used to specify the indentation rules when using indented/wrapped rendering.
|
||||||
type Indentation struct {
|
type Indentation struct {
|
||||||
|
|
||||||
|
// PWidth defines the max width of flow elements and text in the rendered HTML. That is the HTML text, and
|
||||||
|
// not the formatted text that the HTML gives when displayed in a browser. The length of indentation is
|
||||||
|
// subtraced from PWidth for indented child elements. If Indent is defined, it defaults to 120.
|
||||||
PWidth int
|
PWidth int
|
||||||
|
|
||||||
|
// MinPWidth defines the minimum width of rendered HTML flow elements and text. If Indent is defined, it
|
||||||
|
// defaults to 60.
|
||||||
MinPWidth int
|
MinPWidth int
|
||||||
|
|
||||||
// not used for the top level
|
// Indent is the string used as leading space during indentation. Tabs count as 8.
|
||||||
Indent string
|
Indent string
|
||||||
}
|
}
|
||||||
|
|
||||||
// convenience function primarily aimed to help with construction of html with tags
|
// Attr defines Attributes to be passed in to tags for setting HTML attributes. Every argument at odd positions
|
||||||
// the names and values are applied using fmt.Sprint, tolerating fmt.Stringer implementations
|
// is considered to be an attribute name, while every argument at even positions is the value associated with
|
||||||
|
// the preceding name.
|
||||||
|
//
|
||||||
|
// In case of odd total number of arguments, the last one will be considered as a boolean true value, e.g.
|
||||||
|
// Textarea(Attr("disabled")).
|
||||||
|
//
|
||||||
|
// Both the attribute names are rendered using the fmt.Sprint function. Names must be valid symbols. Values are
|
||||||
|
// escaped when necessary.
|
||||||
func Attr(a ...any) Attributes {
|
func Attr(a ...any) Attributes {
|
||||||
if len(a)%2 != 0 {
|
if len(a)%2 != 0 {
|
||||||
a = append(a, true)
|
a = append(a, true)
|
||||||
@ -52,6 +87,7 @@ func Attr(a ...any) Attributes {
|
|||||||
return am
|
return am
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Names returns a copy of the names of all the names in an attribute set.
|
||||||
func (a Attributes) Names() []string {
|
func (a Attributes) Names() []string {
|
||||||
if len(a.names) == 0 {
|
if len(a.names) == 0 {
|
||||||
return nil
|
return nil
|
||||||
@ -62,22 +98,19 @@ func (a Attributes) Names() []string {
|
|||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Has returns true if a name appears in an attribute set.
|
||||||
func (a Attributes) Has(name string) bool {
|
func (a Attributes) Has(name string) bool {
|
||||||
_, ok := a.values[name]
|
_, ok := a.values[name]
|
||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Value returns the attribute value associated with a name, or nil if the attribute does not exist.
|
||||||
func (a Attributes) Value(name string) any {
|
func (a Attributes) Value(name string) any {
|
||||||
return a.values[name]
|
return a.values[name]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t Tag) String() string {
|
// Define creates a new tag with a custom name and an optional initial set of attributes and children. The name
|
||||||
buf := bytes.NewBuffer(nil)
|
// must be a valid symbol.
|
||||||
Render(buf, t)
|
|
||||||
return buf.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// defines a new tag with name and initial attributes and child nodes
|
|
||||||
func Define(name string, children ...any) Tag {
|
func Define(name string, children ...any) Tag {
|
||||||
if handleQuery(name, children) {
|
if handleQuery(name, children) {
|
||||||
children = children[:len(children)-1]
|
children = children[:len(children)-1]
|
||||||
@ -88,6 +121,10 @@ func Define(name string, children ...any) Tag {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Declaration can be used to define custom SGML declarations. Declarations are not real HTML tags. Their name
|
||||||
|
// as returned by the Name function will be universally '!'. The declaration children will not be escaped, it is
|
||||||
|
// the responsibility of the caller code to always pass in valid declaration items. For comments, use the
|
||||||
|
// Comment function, and for the doctype declaration use the Doctype function.
|
||||||
func Declaration(children ...any) Tag {
|
func Declaration(children ...any) Tag {
|
||||||
a := make([]any, len(children)*2)
|
a := make([]any, len(children)*2)
|
||||||
for i, c := range children {
|
for i, c := range children {
|
||||||
@ -98,6 +135,8 @@ func Declaration(children ...any) Tag {
|
|||||||
return Void(Define("!", Attr(a...)))
|
return Void(Define("!", Attr(a...)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Comment defines HTML comments. It does not return a real HTML tag, but it can be used as such during
|
||||||
|
// composing HTML documents or fragments.
|
||||||
func Comment(children ...any) Tag {
|
func Comment(children ...any) Tag {
|
||||||
return Inline(
|
return Inline(
|
||||||
Declaration(
|
Declaration(
|
||||||
@ -109,6 +148,9 @@ func Comment(children ...any) Tag {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Doctype defines an HTML doctype declaration. It does not return a real HTML tag, but it can be used as such
|
||||||
|
// during rendering HTML documents. E.g. Doctype("html") for HTML5. The usage of other doctypes is not
|
||||||
|
// recommended to be used with this package, because the package generally assumes HTML5 output.
|
||||||
func Doctype(children ...any) Tag {
|
func Doctype(children ...any) Tag {
|
||||||
a := []any{"doctype"}
|
a := []any{"doctype"}
|
||||||
for _, c := range children {
|
for _, c := range children {
|
||||||
@ -123,14 +165,24 @@ func Doctype(children ...any) Tag {
|
|||||||
return Declaration(a...)
|
return Declaration(a...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// returns the name of a tag
|
// String returns the rendered HTML text.
|
||||||
|
//
|
||||||
|
// It is meant to be used for debugging. Use the Render function to avoid buffering up an entire HTML document
|
||||||
|
// as string.
|
||||||
|
func (t Tag) String() string {
|
||||||
|
buf := bytes.NewBuffer(nil)
|
||||||
|
Render(buf, t)
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name returns the name of a tag.
|
||||||
func Name(t Tag) string {
|
func Name(t Tag) string {
|
||||||
q := nameQuery{}
|
q := nameQuery{}
|
||||||
t()(&q)
|
t()(&q)
|
||||||
return q.value
|
return q.value
|
||||||
}
|
}
|
||||||
|
|
||||||
// returns all attributes of a tag
|
// AllAttributes returns all attributes of a tag.
|
||||||
func AllAttributes(t Tag) Attributes {
|
func AllAttributes(t Tag) Attributes {
|
||||||
q := attributesQuery{}
|
q := attributesQuery{}
|
||||||
t()(&q)
|
t()(&q)
|
||||||
@ -144,36 +196,48 @@ func AllAttributes(t Tag) Attributes {
|
|||||||
return a
|
return a
|
||||||
}
|
}
|
||||||
|
|
||||||
// returns the value of a named attribute if exists, empty string otherwise
|
// Attribute returns the value of a named attribute if exists, nil otherwise.
|
||||||
func Attribute(t Tag, name string) any {
|
func Attribute(t Tag, name string) any {
|
||||||
q := attributeQuery{name: name}
|
q := attributeQuery{name: name}
|
||||||
t()(&q)
|
t()(&q)
|
||||||
return q.value
|
return q.value
|
||||||
}
|
}
|
||||||
|
|
||||||
// creates a new tag with all the existing attributes and child nodes of the input tag, and the new attribute value
|
// SetAttribute creates a new tag with all the existing attributes and child nodes of the input tag, and the new
|
||||||
|
// attribute value.
|
||||||
|
//
|
||||||
|
// The input tag remains unchanged.
|
||||||
func SetAttribute(t Tag, name string, value any) Tag {
|
func SetAttribute(t Tag, name string, value any) Tag {
|
||||||
return t()(Attr(name, value))
|
return t()(Attr(name, value))
|
||||||
}
|
}
|
||||||
|
|
||||||
// creates a new tag with all the existing attributes and child nodes of the input tag, except the attribute to
|
// DeleteAttribute creates a new tag with all the existing attributes and child nodes of the input tag, except
|
||||||
// be deleted
|
// the attributes that need to be deleted.
|
||||||
func DeleteAttribute(t Tag, name string) Tag {
|
//
|
||||||
|
// The input tag remains unchanged.
|
||||||
|
func DeleteAttribute(t Tag, name ...string) Tag {
|
||||||
n := Name(t)
|
n := Name(t)
|
||||||
a := AllAttributes(t)
|
a := AllAttributes(t)
|
||||||
c := Children(t)
|
c := Children(t)
|
||||||
delete(a.values, name)
|
for _, n := range name {
|
||||||
|
delete(a.values, n)
|
||||||
|
|
||||||
|
var nn []string
|
||||||
for i := range a.names {
|
for i := range a.names {
|
||||||
if a.names[i] == name {
|
if a.names[i] == n {
|
||||||
a.names = append(a.names[:i], a.names[i+1:]...)
|
continue
|
||||||
break
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nn = append(nn, a.names[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
a.names = nn
|
||||||
}
|
}
|
||||||
|
|
||||||
return Define(n, append(c, a)...)
|
return Define(n, append(c, a)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// the same as Attribute(t, "class")
|
// Class is the same as Attribute(t, "class"). When the class attribute is not set, it returns an empty string.
|
||||||
func Class(t Tag) string {
|
func Class(t Tag) string {
|
||||||
c := Attribute(t, "class")
|
c := Attribute(t, "class")
|
||||||
if c == nil {
|
if c == nil {
|
||||||
@ -183,12 +247,17 @@ func Class(t Tag) string {
|
|||||||
return fmt.Sprint(c)
|
return fmt.Sprint(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
// the same as SetAttribute(t, "class", class)
|
// SetClass is the same as SetAttribute(t, "class", class).
|
||||||
|
//
|
||||||
|
// The input tag remains unchanged.
|
||||||
func SetClass(t Tag, class string) Tag {
|
func SetClass(t Tag, class string) Tag {
|
||||||
return SetAttribute(t, "class", class)
|
return SetAttribute(t, "class", class)
|
||||||
}
|
}
|
||||||
|
|
||||||
// like SetClass, but it appends the new class to the existing classes, regardless if the same class exists
|
// AddClass is like SetClass, but it appends the new class to the existing classes separated by space,
|
||||||
|
// regardless if the same class exists.
|
||||||
|
//
|
||||||
|
// The input tag remains unchanged.
|
||||||
func AddClass(t Tag, class string) Tag {
|
func AddClass(t Tag, class string) Tag {
|
||||||
current := Class(t)
|
current := Class(t)
|
||||||
if current != "" {
|
if current != "" {
|
||||||
@ -198,7 +267,9 @@ func AddClass(t Tag, class string) Tag {
|
|||||||
return SetClass(t, class)
|
return SetClass(t, class)
|
||||||
}
|
}
|
||||||
|
|
||||||
// like DeleteAttribute, but it only deletes the specified class from the class attribute
|
// DeleteClass is like DeleteAttribute, but it only deletes the specified class from the class attribute value.
|
||||||
|
//
|
||||||
|
// The input tag remains unchanged.
|
||||||
func DeleteClass(t Tag, class string) Tag {
|
func DeleteClass(t Tag, class string) Tag {
|
||||||
c := Class(t)
|
c := Class(t)
|
||||||
cc := strings.Split(c, " ")
|
cc := strings.Split(c, " ")
|
||||||
@ -213,7 +284,7 @@ func DeleteClass(t Tag, class string) Tag {
|
|||||||
return SetClass(t, strings.Join(ccc, " "))
|
return SetClass(t, strings.Join(ccc, " "))
|
||||||
}
|
}
|
||||||
|
|
||||||
// returns the child nodes of a tag
|
// Children returns the child nodes of a tag.
|
||||||
func Children(t Tag) []any {
|
func Children(t Tag) []any {
|
||||||
var q childrenQuery
|
var q childrenQuery
|
||||||
t()(&q)
|
t()(&q)
|
||||||
@ -222,16 +293,58 @@ func Children(t Tag) []any {
|
|||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Verbatim creates a new tag from t marking the new one verbatim. The content of verbatim tags is rendered
|
||||||
|
// without HTML escaping. This may cause security issues when using it in an incosiderate way. Verbatim content
|
||||||
|
// gets indented when rendering with indentation.
|
||||||
|
//
|
||||||
|
// The input tag remains unchanged.
|
||||||
|
func Verbatim(t Tag) Tag {
|
||||||
|
return t()(renderGuide{verbatim: true})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ScriptContent marks a tag as script-style content for rendering. Script-style content is not escaped and not
|
||||||
|
// indented.
|
||||||
|
//
|
||||||
|
// The input tag remains unchanged.
|
||||||
|
func ScriptContent(t Tag) Tag {
|
||||||
|
return t()(renderGuide{script: true})
|
||||||
|
}
|
||||||
|
|
||||||
|
// InlineChildren marks a tag to have its children rendered as an inline flow, while the tag behaves as a block
|
||||||
|
// from the perspective of the enclosing tags. It takes effect only when rendering with indentation and
|
||||||
|
// wrapping.
|
||||||
|
//
|
||||||
|
// The input tag remains unchanged.
|
||||||
|
func InlineChildren(t Tag) Tag {
|
||||||
|
return t()(renderGuide{inlineChildren: true})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inline tags are not broken into separate lines when rendering with indentation and wrapping.
|
||||||
|
//
|
||||||
|
// The input tag remains unchanged.
|
||||||
|
func Inline(t Tag) Tag {
|
||||||
|
return t()(renderGuide{inline: true})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Void tags do not accept children.
|
||||||
|
//
|
||||||
|
// The input tag remains unchanged.
|
||||||
|
func Void(t Tag) Tag {
|
||||||
|
return t()(renderGuide{void: true})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Indent provides default indentation with the tabs as indentation string, a paragraph with of 120 and a
|
||||||
|
// minimum paragraph with of 60. Indentation and with concerns only the HTML text, not the displayed document.
|
||||||
func Indent() Indentation {
|
func Indent() Indentation {
|
||||||
return Indentation{Indent: "\t"}
|
return Indentation{Indent: "\t"}
|
||||||
}
|
}
|
||||||
|
|
||||||
// renders html with t as the root node with indentation
|
// RenderIndent renders html with t as the root nodes using the specified indentation and wrapping.
|
||||||
// child nodes are rendered via fmt.Sprint, tolerating fmt.Stringer implementations
|
//
|
||||||
// consecutive spaces are considered to be so on purpose, and are converted into
|
// Non-tag child nodes are rendered via fmt.Sprint. Consecutive spaces are considered to be so on purpose, and
|
||||||
// spaces around tags can behave different from when using unindented rendering
|
// are converted into . Spaces around tags, in special cases, can behave different from when using
|
||||||
// as a last resort, one can use rendered html inside a verbatim tag
|
// unindented rendering.
|
||||||
func RenderIndent(out io.Writer, indent Indentation, t Tag) error {
|
func RenderIndent(out io.Writer, indent Indentation, t ...Tag) error {
|
||||||
if indent.PWidth < indent.MinPWidth {
|
if indent.PWidth < indent.MinPWidth {
|
||||||
indent.PWidth = indent.MinPWidth
|
indent.PWidth = indent.MinPWidth
|
||||||
}
|
}
|
||||||
@ -247,45 +360,33 @@ func RenderIndent(out io.Writer, indent Indentation, t Tag) error {
|
|||||||
pwidth: indent.PWidth,
|
pwidth: indent.PWidth,
|
||||||
}
|
}
|
||||||
|
|
||||||
t()(&r)
|
for i, ti := range t {
|
||||||
|
if i > 0 {
|
||||||
|
if _, err := out.Write([]byte{'\n'}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ti()(&r)
|
||||||
|
if r.err != nil {
|
||||||
return r.err
|
return r.err
|
||||||
}
|
}
|
||||||
|
|
||||||
// renders html with t as the root node without indentation
|
|
||||||
func Render(out io.Writer, t Tag) error {
|
|
||||||
return RenderIndent(out, Indentation{}, t)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// creates a new tag from t marking it verbatim. The content of verbatim tags is rendered without HTML escaping.
|
return nil
|
||||||
// This may cause security issues when using it in an incosiderate way. The tag can contain non-tag child nodes.
|
|
||||||
// Verbatim content gets indented when rendering with indentation
|
|
||||||
func Verbatim(t Tag) Tag {
|
|
||||||
return t()(renderGuide{verbatim: true})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// marks a tag as script-style content for rendering. Script-style content is not escaped and not indented
|
// Render renders html with t as the root nodes without indentation or wrapping.
|
||||||
func ScriptContent(t Tag) Tag {
|
func Render(out io.Writer, t ...Tag) error {
|
||||||
return t()(renderGuide{script: true})
|
return RenderIndent(out, Indentation{}, t...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func InlineChildren(t Tag) Tag {
|
// Eq returns if one or more tags are considered equivalent. Equivalent nodes, in the most cases, will result in
|
||||||
return t()(renderGuide{inlineChildren: true})
|
// the same HTML text when rendered, but not every tag that renders the same is equivalent. The following rules
|
||||||
}
|
// make two tags equivalent: same name, same number of attributes, same name and value of attributes, same order
|
||||||
|
// of attributes, same number of children, children at the same position in the children list are equal by '==',
|
||||||
// inline tags are not broken into separate lines when rendering with indentation
|
// and same rendering rules. Since the children are compared via '==', it is recommended to keep the children
|
||||||
// deprecated in HTML, but only used for indentation
|
// that were passed to a tag unchanged.
|
||||||
func Inline(t Tag) Tag {
|
|
||||||
return t()(renderGuide{inline: true})
|
|
||||||
}
|
|
||||||
|
|
||||||
// void tags do not accept child nodes
|
|
||||||
// deprecated in HTML, but only used for indentation
|
|
||||||
func Void(t Tag) Tag {
|
|
||||||
return t()(renderGuide{void: true})
|
|
||||||
}
|
|
||||||
|
|
||||||
// same name, same attributes, same child tags, child nodes in the same order and equal by reference or value
|
|
||||||
// depending on the child node type
|
|
||||||
func Eq(t ...Tag) bool {
|
func Eq(t ...Tag) bool {
|
||||||
tt := make([]Tag, len(t))
|
tt := make([]Tag, len(t))
|
||||||
for i := range t {
|
for i := range t {
|
||||||
@ -295,7 +396,10 @@ func Eq(t ...Tag) bool {
|
|||||||
return eq(tt...)
|
return eq(tt...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// turns a template into a tag for composition
|
// FromTemplate turns a template into a tag that can be used for composition, a template tag. The template tag
|
||||||
|
// can be used multiple times for template binding, but the tag resulting from a template binding cannot be used
|
||||||
|
// for binding anymore, only for setting attributes, rendering rules or adding additional children to the top
|
||||||
|
// level tag.
|
||||||
func FromTemplate[Data any](t Template[Data]) Tag {
|
func FromTemplate[Data any](t Template[Data]) Tag {
|
||||||
return func(a ...any) Tag {
|
return func(a ...any) Tag {
|
||||||
var (
|
var (
|
||||||
@ -320,7 +424,8 @@ func FromTemplate[Data any](t Template[Data]) Tag {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// in the functional programming sense
|
// Map takes a list of data items and returns a list of tags where each have the data item at the same position
|
||||||
|
// passed in a child. It can be used for template binding of list input.
|
||||||
func Map[Data any](data []Data, tag Tag) []Tag {
|
func Map[Data any](data []Data, tag Tag) []Tag {
|
||||||
var ret []Tag
|
var ret []Tag
|
||||||
for _, d := range data {
|
for _, d := range data {
|
||||||
@ -330,6 +435,8 @@ func Map[Data any](data []Data, tag Tag) []Tag {
|
|||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MapChildren is like Map, but converts the resulting tag slice to a slice of 'any', such making it easier to
|
||||||
|
// use during tag composition.
|
||||||
func MapChildren[Data any](data []Data, tag Tag) []any {
|
func MapChildren[Data any](data []Data, tag Tag) []any {
|
||||||
c := Map(data, tag)
|
c := Map(data, tag)
|
||||||
|
|
||||||
@ -341,11 +448,13 @@ func MapChildren[Data any](data []Data, tag Tag) []any {
|
|||||||
return a
|
return a
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Escape escapes HTML.
|
||||||
func Escape(s string) string {
|
func Escape(s string) string {
|
||||||
return escape(s)
|
return escape(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
// does not escape single quotes
|
// EscapeAttribute escape attribute values. It does not escape single-quotes, because it assumes attribute
|
||||||
|
// values are always rendered with double-quotes.
|
||||||
func EscapeAttribute(s string) string {
|
func EscapeAttribute(s string) string {
|
||||||
return escapeAttribute(s)
|
return escapeAttribute(s)
|
||||||
}
|
}
|
||||||
|
|||||||
35
lib_test.go
35
lib_test.go
@ -120,6 +120,15 @@ func TestLib(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("delete multiple attributes", func(t *testing.T) {
|
||||||
|
div := Div(Attr("foo", "bar", "baz", "qux", "quux", "corge"))
|
||||||
|
div = html.DeleteAttribute(div, "foo", "quux")
|
||||||
|
a := html.AllAttributes(div)
|
||||||
|
if a.Has("foo") || a.Has("quux") || !a.Has("baz") || a.Value("baz") != "qux" {
|
||||||
|
t.Fatal(a.Has("foo"), a.Has("quux"), a.Has("baz"), a.Value("baz"))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
t.Run("setting attribute immutable", func(t *testing.T) {
|
t.Run("setting attribute immutable", func(t *testing.T) {
|
||||||
div0 := Div(Attr("foo", "bar"))
|
div0 := Div(Attr("foo", "bar"))
|
||||||
div1 := div0(Attr("baz", "qux"))
|
div1 := div0(Attr("baz", "qux"))
|
||||||
@ -410,6 +419,32 @@ func TestLib(t *testing.T) {
|
|||||||
t.Fatal(b.String())
|
t.Fatal(b.String())
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("doc unindented", func(t *testing.T) {
|
||||||
|
doc := Html(Body(P("foo bar baz")))
|
||||||
|
|
||||||
|
var b bytes.Buffer
|
||||||
|
if err := html.Render(&b, Doctype("html"), doc); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.String() != "<!doctype html>\n<html><body><p>foo bar baz</p></body></html>" {
|
||||||
|
t.Fatal(b.String())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("doc indented", func(t *testing.T) {
|
||||||
|
doc := Html(Body(P("foo bar baz")))
|
||||||
|
|
||||||
|
var b bytes.Buffer
|
||||||
|
if err := html.RenderIndent(&b, html.Indent(), Doctype("html"), doc); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.String() != "<!doctype html>\n<html>\n\t<body>\n\t\t<p>foo bar baz</p>\n\t</body>\n</html>" {
|
||||||
|
t.Fatal(b.String())
|
||||||
|
}
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("templates", func(t *testing.T) {
|
t.Run("templates", func(t *testing.T) {
|
||||||
|
|||||||
@ -1,6 +1 @@
|
|||||||
explain the immutability guarantee in the Go docs: for children yes, for children references no. The general
|
|
||||||
recommendation is not to mutate children. Ofc, creatively breaking the rules is always well appreciated by the
|
|
||||||
right audience
|
|
||||||
test wrapped templates
|
|
||||||
review which tags should be of type inline-children
|
review which tags should be of type inline-children
|
||||||
export Escape and EscapeAttribute
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user