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,19 +1,19 @@
package html
import (
"io"
"bufio"
"bytes"
"errors"
"io"
"unicode"
)
const unicodeNBSP = 0xa0
const unicodeNBSP = 0xa0
type escapeWriter struct {
out *bufio.Writer
out *bufio.Writer
spaceStarted, lastSpace bool
err error
err error
}
func attributeEscape(value string) string {
@ -67,6 +67,11 @@ func (w *escapeWriter) Write(p []byte) (int, error) {
return len(p), nil
}
if err != nil {
w.err = err
return len(p), w.err
}
if r == unicode.ReplacementChar {
continue
}

View File

@ -1,10 +1,10 @@
package html_test
import (
"bytes"
"code.squareroundforest.org/arpio/html"
. "code.squareroundforest.org/arpio/html/tags"
"testing"
"bytes"
)
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"
"code.squareroundforest.org/arpio/html"
. "code.squareroundforest.org/arpio/html/tags"
"testing"
"code.squareroundforest.org/arpio/notation"
"testing"
)
func TestLib(t *testing.T) {

View File

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

143
render.go
View File

@ -3,7 +3,6 @@ package html
import (
"fmt"
"io"
"strings"
)
const defaultPWidth = 112
@ -38,15 +37,6 @@ func mergeRenderingGuides(rgs []renderGuide) renderGuide {
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) {
return func(f string, a ...any) {
if r.err != nil {
@ -93,6 +83,22 @@ func (r *renderer) renderUnindented(name string, rg renderGuide, a []Attributes,
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)
if s == "" {
continue
@ -100,6 +106,7 @@ func (r *renderer) renderUnindented(name string, rg renderGuide, a []Attributes,
if rg.verbatim || rg.script {
printf(s)
continue
}
if r.err == nil {
@ -152,6 +159,54 @@ func (r *renderer) renderInline(name string, rg renderGuide, a []Attributes, chi
var lastBlock bool
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)
if !isTag && rg.verbatim {
if c == nil {
@ -164,8 +219,15 @@ func (r *renderer) renderInline(name string, rg renderGuide, a []Attributes, chi
}
r.clearWrapper()
s = indentLines(r.currentIndent+r.indent.Indent, s)
printf("\n%s", s)
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))
iw.Write([]byte(s))
iw.Flush()
r.err = iw.err
}
lastBlock = true
continue
}
@ -283,6 +345,52 @@ func (r *renderer) renderBlock(name string, rg renderGuide, a []Attributes, chil
}
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)
if !isTag && rg.verbatim {
if c == nil {
@ -295,8 +403,15 @@ func (r *renderer) renderBlock(name string, rg renderGuide, a []Attributes, chil
}
r.clearWrapper()
s = indentLines(r.currentIndent, s)
printf("\n%s", s)
if r.err == nil {
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
continue
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

24
wrap.go
View File

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

View File

@ -5,12 +5,12 @@ import (
"io"
)
type errorWriter struct{
out io.Writer
type errorWriter struct {
out io.Writer
failAfter int
}
func(ew *errorWriter) Write(p []byte) (int, error) {
func (ew *errorWriter) Write(p []byte) (int, error) {
wp := p
if len(wp) > ew.failAfter {
wp = wp[:ew.failAfter]