1
0

support reader as content

This commit is contained in:
Arpad Ryszka 2025-10-05 21:25:53 +02:00
parent 7610db6e71
commit 2f496f4ca2
15 changed files with 262 additions and 36 deletions

View File

@ -1,10 +1,10 @@
package html package html
import ( import (
"io"
"bufio" "bufio"
"bytes" "bytes"
"errors" "errors"
"io"
"unicode" "unicode"
) )
@ -67,6 +67,11 @@ func (w *escapeWriter) Write(p []byte) (int, error) {
return len(p), nil return len(p), nil
} }
if err != nil {
w.err = err
return len(p), w.err
}
if r == unicode.ReplacementChar { if r == unicode.ReplacementChar {
continue continue
} }

View File

@ -1,10 +1,10 @@
package html_test package html_test
import ( import (
"bytes"
"code.squareroundforest.org/arpio/html" "code.squareroundforest.org/arpio/html"
. "code.squareroundforest.org/arpio/html/tags" . "code.squareroundforest.org/arpio/html/tags"
"testing" "testing"
"bytes"
) )
func TestEscape(t *testing.T) { func TestEscape(t *testing.T) {

92
indent.go Normal file
View File

@ -0,0 +1,92 @@
package html
import (
"bufio"
"bytes"
"errors"
"io"
"unicode"
)
type indentWriter struct {
out *bufio.Writer
indent string
started, lineStarted bool
err error
}
func newIndentWriter(out io.Writer, indent string) *indentWriter {
return &indentWriter{
out: bufio.NewWriter(out),
indent: indent,
}
}
func (w *indentWriter) write(r ...rune) {
if w.err != nil {
return
}
for _, ri := range r {
if _, w.err = w.out.WriteRune(ri); w.err != nil {
return
}
}
}
func (w *indentWriter) Write(p []byte) (int, error) {
if w.err != nil {
return 0, w.err
}
if len(p) == 0 {
return 0, nil
}
runes := bytes.NewBuffer(nil)
if n, err := runes.Write(p); err != nil {
w.err = err
return n, w.err
}
for {
r, _, err := runes.ReadRune()
if errors.Is(err, io.EOF) {
return len(p), nil
}
if err != nil {
w.err = err
return len(p), w.err
}
if r == unicode.ReplacementChar {
continue
}
if r == '\n' {
w.write('\n')
w.lineStarted = false
continue
}
if w.started && !w.lineStarted {
w.write([]rune(w.indent)...)
}
w.write(r)
w.started = true
w.lineStarted = true
}
return len(p), w.err
}
func (w *indentWriter) Flush() error {
if w.err != nil {
return w.err
}
w.err = w.out.Flush()
return w.err
}

View File

@ -4,8 +4,8 @@ import (
"bytes" "bytes"
"code.squareroundforest.org/arpio/html" "code.squareroundforest.org/arpio/html"
. "code.squareroundforest.org/arpio/html/tags" . "code.squareroundforest.org/arpio/html/tags"
"testing"
"code.squareroundforest.org/arpio/notation" "code.squareroundforest.org/arpio/notation"
"testing"
) )
func TestLib(t *testing.T) { func TestLib(t *testing.T) {

View File

@ -1,8 +1,8 @@
package html_test package html_test
import ( import (
"fmt"
"code.squareroundforest.org/arpio/notation" "code.squareroundforest.org/arpio/notation"
"fmt"
) )
func printBytes(a ...any) { func printBytes(a ...any) {

143
render.go
View File

@ -3,7 +3,6 @@ package html
import ( import (
"fmt" "fmt"
"io" "io"
"strings"
) )
const defaultPWidth = 112 const defaultPWidth = 112
@ -38,15 +37,6 @@ func mergeRenderingGuides(rgs []renderGuide) renderGuide {
return rg return rg
} }
func indentLines(indent string, s string) string {
l := strings.Split(s, "\n")
for i := range l {
l[i] = fmt.Sprintf("%s%s", indent, l[i])
}
return strings.Join(l, "\n")
}
func (r *renderer) getPrintf(tagName string) func(f string, a ...any) { func (r *renderer) getPrintf(tagName string) func(f string, a ...any) {
return func(f string, a ...any) { return func(f string, a ...any) {
if r.err != nil { if r.err != nil {
@ -93,6 +83,22 @@ func (r *renderer) renderUnindented(name string, rg renderGuide, a []Attributes,
continue continue
} }
if rd, ok := c.(io.Reader); ok {
if rg.verbatim || rg.script {
_, r.err = io.Copy(r.out, rd)
continue
}
ew := newEscapeWriter(r.out)
_, r.err = io.Copy(ew, rd)
if r.err != nil {
ew.Flush()
r.err = ew.err
}
continue
}
s := fmt.Sprint(c) s := fmt.Sprint(c)
if s == "" { if s == "" {
continue continue
@ -100,6 +106,7 @@ func (r *renderer) renderUnindented(name string, rg renderGuide, a []Attributes,
if rg.verbatim || rg.script { if rg.verbatim || rg.script {
printf(s) printf(s)
continue
} }
if r.err == nil { if r.err == nil {
@ -152,6 +159,54 @@ func (r *renderer) renderInline(name string, rg renderGuide, a []Attributes, chi
var lastBlock bool var lastBlock bool
for _, c := range children { for _, c := range children {
rd, isReader := c.(io.Reader)
if isReader && rg.verbatim {
r.clearWrapper()
if r.err == nil {
iw := newIndentWriter(r.out, r.currentIndent+r.indent.Indent)
iw.Write([]byte{'\n'})
iw.Write([]byte(r.currentIndent + r.indent.Indent))
_, r.err = io.Copy(iw, rd)
if r.err == nil {
iw.Flush()
r.err = iw.err
}
}
lastBlock = true
continue
}
if isReader && rg.script {
r.clearWrapper()
printf("\n")
_, r.err = io.Copy(r.out, rd)
lastBlock = true
continue
}
if isReader {
if lastBlock {
printf("\n%s", r.currentIndent)
}
if r.ensureWrapper() {
newWrapper = true
}
if r.err == nil {
ew := newEscapeWriter(r.out)
_, r.err = io.Copy(ew, rd)
if r.err == nil {
ew.Flush()
r.err = ew.err
}
}
lastBlock = false
continue
}
ct, isTag := c.(Tag) ct, isTag := c.(Tag)
if !isTag && rg.verbatim { if !isTag && rg.verbatim {
if c == nil { if c == nil {
@ -164,8 +219,15 @@ func (r *renderer) renderInline(name string, rg renderGuide, a []Attributes, chi
} }
r.clearWrapper() r.clearWrapper()
s = indentLines(r.currentIndent+r.indent.Indent, s) if r.err == nil {
printf("\n%s", s) iw := newIndentWriter(r.out, r.currentIndent+r.indent.Indent)
iw.Write([]byte{'\n'})
iw.Write([]byte(r.currentIndent + r.indent.Indent))
iw.Write([]byte(s))
iw.Flush()
r.err = iw.err
}
lastBlock = true lastBlock = true
continue continue
} }
@ -283,6 +345,52 @@ func (r *renderer) renderBlock(name string, rg renderGuide, a []Attributes, chil
} }
for _, c := range children { for _, c := range children {
rd, isReader := c.(io.Reader)
if isReader && rg.verbatim {
r.clearWrapper()
printf("\n")
if r.err == nil {
iw := newIndentWriter(r.out, r.currentIndent+r.indent.Indent)
iw.Write([]byte{'\n'})
iw.Write([]byte(r.currentIndent + r.indent.Indent))
_, r.err = io.Copy(iw, rd)
if r.err == nil {
iw.Flush()
r.err = iw.err
}
}
lastBlock = true
continue
}
if isReader && rg.script {
r.clearWrapper()
printf("\n")
_, r.err = io.Copy(r.out, rd)
lastBlock = true
continue
}
if isReader {
if lastBlock {
printf("\n%s", r.currentIndent)
}
r.ensureWrapper()
if r.err == nil {
ew := newEscapeWriter(r.out)
_, r.err = io.Copy(ew, rd)
if r.err == nil {
ew.Flush()
r.err = ew.err
}
}
lastBlock = false
continue
}
ct, isTag := c.(Tag) ct, isTag := c.(Tag)
if !isTag && rg.verbatim { if !isTag && rg.verbatim {
if c == nil { if c == nil {
@ -295,8 +403,15 @@ func (r *renderer) renderBlock(name string, rg renderGuide, a []Attributes, chil
} }
r.clearWrapper() r.clearWrapper()
s = indentLines(r.currentIndent, s) if r.err == nil {
printf("\n%s", s) iw := newIndentWriter(r.out, r.currentIndent)
iw.Write([]byte{'\n'})
iw.Write([]byte(r.currentIndent + r.indent.Indent))
iw.Write([]byte(s))
iw.Flush()
r.err = iw.err
}
lastBlock = true lastBlock = true
continue continue
} }

View File

@ -1,7 +1,9 @@
// generated by ../script/generate-tags.go // generated by ../script/generate-tags.go
package tags package tags
import "code.squareroundforest.org/arpio/html" import "code.squareroundforest.org/arpio/html"
var Address = html.Define("address") var Address = html.Define("address")
var Article = html.Define("article") var Article = html.Define("article")
var Audio = html.Define("audio") var Audio = html.Define("audio")

View File

@ -1,7 +1,9 @@
// generated by ../script/generate-tags.go // generated by ../script/generate-tags.go
package tags package tags
import "code.squareroundforest.org/arpio/html" import "code.squareroundforest.org/arpio/html"
var A = html.Inline(html.Define("a")) var A = html.Inline(html.Define("a"))
var Abbr = html.Inline(html.Define("abbr")) var Abbr = html.Inline(html.Define("abbr"))
var B = html.Inline(html.Define("b")) var B = html.Inline(html.Define("b"))

View File

@ -1,7 +1,9 @@
// generated by ../script/generate-tags.go // generated by ../script/generate-tags.go
package tags package tags
import "code.squareroundforest.org/arpio/html" import "code.squareroundforest.org/arpio/html"
var H1 = html.InlineChildren(html.Define("h1")) var H1 = html.InlineChildren(html.Define("h1"))
var H2 = html.InlineChildren(html.Define("h2")) var H2 = html.InlineChildren(html.Define("h2"))
var H3 = html.InlineChildren(html.Define("h3")) var H3 = html.InlineChildren(html.Define("h3"))

View File

@ -1,7 +1,9 @@
// generated by ../script/promote-to-tags.go // generated by ../script/promote-to-tags.go
package tags package tags
import "code.squareroundforest.org/arpio/html" import "code.squareroundforest.org/arpio/html"
var Attr = html.Attr var Attr = html.Attr
var Define = html.Define var Define = html.Define
var Doctype = html.Doctype var Doctype = html.Doctype

View File

@ -1,6 +1,8 @@
// generated by ../script/generate-tags.go // generated by ../script/generate-tags.go
package tags package tags
import "code.squareroundforest.org/arpio/html" import "code.squareroundforest.org/arpio/html"
var Script = html.ScriptContent(html.Define("script")) var Script = html.ScriptContent(html.Define("script"))
var Style = html.ScriptContent(html.Define("style")) var Style = html.ScriptContent(html.Define("style"))

View File

@ -1,7 +1,9 @@
// generated by ../script/generate-tags.go // generated by ../script/generate-tags.go
package tags package tags
import "code.squareroundforest.org/arpio/html" import "code.squareroundforest.org/arpio/html"
var Area = html.Void(html.Define("area")) var Area = html.Void(html.Define("area"))
var Base = html.Void(html.Define("base")) var Base = html.Void(html.Define("base"))
var Hr = html.Void(html.Define("hr")) var Hr = html.Void(html.Define("hr"))

View File

@ -1,7 +1,9 @@
// generated by ../script/generate-tags.go // generated by ../script/generate-tags.go
package tags package tags
import "code.squareroundforest.org/arpio/html" import "code.squareroundforest.org/arpio/html"
var Br = html.Inline(html.Void(html.Define("br"))) var Br = html.Inline(html.Void(html.Define("br")))
var Embed = html.Inline(html.Void(html.Define("embed"))) var Embed = html.Inline(html.Void(html.Define("embed")))
var Img = html.Inline(html.Void(html.Define("img"))) var Img = html.Inline(html.Void(html.Define("img")))

18
wrap.go
View File

@ -8,9 +8,8 @@ import (
) )
type wrapper struct { type wrapper struct {
out io.Writer out *indentWriter
width int width int
indent string
line, word *bytes.Buffer line, word *bytes.Buffer
inWord, inTag, inSingleQuote, inQuote, lastSpace, started bool inWord, inTag, inSingleQuote, inQuote, lastSpace, started bool
err error err error
@ -18,9 +17,8 @@ type wrapper struct {
func newWrapper(out io.Writer, width int, indent string) *wrapper { func newWrapper(out io.Writer, width int, indent string) *wrapper {
return &wrapper{ return &wrapper{
out: out, out: newIndentWriter(out, indent),
width: width, width: width,
indent: indent,
line: bytes.NewBuffer(nil), line: bytes.NewBuffer(nil),
word: bytes.NewBuffer(nil), word: bytes.NewBuffer(nil),
} }
@ -39,10 +37,6 @@ func (w *wrapper) feed() error {
if _, err := w.out.Write([]byte{'\n'}); err != nil { if _, err := w.out.Write([]byte{'\n'}); err != nil {
return err return err
} }
if _, err := w.out.Write([]byte(w.indent)); err != nil {
return err
}
} }
if _, err := io.Copy(w.out, w.line); err != nil { if _, err := io.Copy(w.out, w.line); err != nil {
@ -79,6 +73,11 @@ func (w *wrapper) Write(p []byte) (int, error) {
return len(p), nil return len(p), nil
} }
if err != nil {
w.err = err
return len(p), w.err
}
if r == unicode.ReplacementChar { if r == unicode.ReplacementChar {
continue continue
} }
@ -165,5 +164,6 @@ func (w *wrapper) Flush() error {
return err return err
} }
return nil w.err = w.out.Flush()
return w.err
} }