1
0

refactor render

This commit is contained in:
Arpad Ryszka 2025-10-06 02:14:18 +02:00
parent 73250375c9
commit b4b05bfab4
2 changed files with 269 additions and 354 deletions

View File

@ -2,5 +2,4 @@ explain the immutability guarantee in the Go docs: for children yes, for childre
recommendation is not to mutate children. Ofc, creatively breaking the rules is always well appreciated by the recommendation is not to mutate children. Ofc, creatively breaking the rules is always well appreciated by the
right audience right audience
test wrapped templates test wrapped templates
test empty block
review which tags should be of type inline-children review which tags should be of type inline-children

622
render.go
View File

@ -51,6 +51,89 @@ func (r *renderer) getPrintf(tagName string) func(f string, a ...any) {
} }
} }
func (r *renderer) ensureWrapper() bool {
if _, ok := r.out.(*wrapper); ok {
return false
}
r.originalOut = r.out
r.out = newWrapper(r.originalOut, r.pwidth, r.currentIndent)
return true
}
func (r *renderer) clearWrapper() {
w, ok := r.out.(*wrapper)
if !ok {
return
}
if err := w.Flush(); err != nil {
r.err = err
}
r.out = r.originalOut
}
func (r *renderer) writeEscaped(s string) {
if r.err != nil {
return
}
ew := newEscapeWriter(r.out)
if _, r.err = ew.Write([]byte(s)); r.err != nil {
return
}
r.err = ew.Flush()
}
func (r *renderer) copyEscaped(rd io.Reader) {
if r.err != nil {
return
}
ew := newEscapeWriter(r.out)
if _, r.err = io.Copy(ew, rd); r.err != nil {
return
}
r.err = ew.Flush()
}
func (r *renderer) writeIndented(indent, s string) {
if r.err != nil {
return
}
iw := newIndentWriter(r.out, indent)
if _, r.err = iw.Write([]byte{'\n'}); r.err != nil {
return
}
if _, r.err = iw.Write([]byte(s)); r.err != nil {
return
}
r.err = iw.Flush()
}
func (r *renderer) copyIndented(indent string, rd io.Reader) {
if r.err != nil {
return
}
iw := newIndentWriter(r.out, indent)
if _, r.err = iw.Write([]byte{'\n'}); r.err != nil {
return
}
if _, r.err = io.Copy(iw, rd); r.err != nil {
return
}
r.err = iw.Flush()
}
func (r *renderer) renderAttributes(tagName string, a []Attributes) { func (r *renderer) renderAttributes(tagName string, a []Attributes) {
printf := r.getPrintf(tagName) printf := r.getPrintf(tagName)
isDeclaration := strings.HasPrefix(tagName, "!") isDeclaration := strings.HasPrefix(tagName, "!")
@ -89,31 +172,29 @@ func (r *renderer) renderUnindented(name string, rg renderGuide, a []Attributes,
} }
for _, c := range children { for _, c := range children {
if ct, ok := c.(Tag); ok {
ct(r)
continue
}
if c == nil { if c == nil {
continue continue
} }
if rd, ok := c.(io.Reader); ok { rd, isReader := c.(io.Reader)
if rg.verbatim || rg.script { if isReader && (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 { if r.err == nil {
ew.Flush() _, r.err = io.Copy(r.out, rd)
r.err = ew.err
} }
continue continue
} }
if isReader {
r.copyEscaped(rd)
continue
}
if ct, ok := c.(Tag); ok {
ct(r)
continue
}
s := fmt.Sprint(c) s := fmt.Sprint(c)
if s == "" { if s == "" {
continue continue
@ -124,42 +205,128 @@ func (r *renderer) renderUnindented(name string, rg renderGuide, a []Attributes,
continue continue
} }
if r.err == nil { r.writeEscaped(s)
ew := newEscapeWriter(r.out)
ew.Write([]byte(s))
ew.Flush()
r.err = ew.err
}
} }
printf("</%s>", name) printf("</%s>", name)
} }
func (r *renderer) ensureWrapper() bool { func (r *renderer) renderReaderChild(tagName string, rg renderGuide, block, lastBlock bool, rd io.Reader) bool {
if _, ok := r.out.(*wrapper); ok { printf := r.getPrintf(tagName)
if rg.verbatim {
r.clearWrapper()
indent := r.currentIndent
if !block {
indent += r.indent.Indent
}
r.copyIndented(indent, rd)
return false return false
} }
r.originalOut = r.out if rg.script {
r.out = newWrapper(r.originalOut, r.pwidth, r.currentIndent) r.clearWrapper()
return true printf("\n")
if r.err == nil {
_, r.err = io.Copy(r.out, rd)
}
return false
}
if lastBlock {
printf("\n%s", r.currentIndent)
}
newWrapper := r.ensureWrapper()
r.copyEscaped(rd)
return newWrapper
} }
func (r *renderer) clearWrapper() { func (r *renderer) renderChildTag(tagName string, block, lastBlock bool, ct Tag) (bool, bool) {
w, ok := r.out.(*wrapper) printf := r.getPrintf(tagName)
if !ok {
var rgq renderGuidesQuery
ct(&rgq)
crg := mergeRenderingGuides(rgq.value)
if crg.inline {
if lastBlock {
printf("\n%s", r.currentIndent)
}
newWrapper := r.ensureWrapper()
ct(r)
return false, newWrapper
}
r.clearWrapper()
cr := new(renderer)
*cr = *r
if !block {
cr.currentIndent += cr.indent.Indent
cr.pwidth -= indentLen(cr.indent.Indent)
if cr.pwidth < cr.indent.MinPWidth {
cr.pwidth = cr.indent.MinPWidth
}
}
printf("\n%s", cr.currentIndent)
ct(cr)
if cr.err != nil {
r.err = cr.err
}
return true, false
}
func (r *renderer) renderVerbatimChild(block bool, c any) {
s := fmt.Sprint(c)
if s == "" {
return return
} }
if err := w.Flush(); err != nil { r.clearWrapper()
r.err = err indent := r.currentIndent
if !block {
indent += r.indent.Indent
} }
r.out = r.originalOut r.writeIndented(indent, s)
} }
func (r *renderer) renderInline(name string, rg renderGuide, a []Attributes, children []any) { func (r *renderer) renderChildScript(tagName string, c any) {
s := fmt.Sprint(c)
if s == "" {
return
}
r.clearWrapper()
printf := r.getPrintf(tagName)
printf("\n%s", s)
}
func (r *renderer) renderChildContent(tagName string, lastBlock bool, c any) bool {
s := fmt.Sprint(c)
if s == "" {
return false
}
if lastBlock {
printf := r.getPrintf(tagName)
printf("\n%s", r.currentIndent)
}
newWrapper := r.ensureWrapper() newWrapper := r.ensureWrapper()
r.writeEscaped(s)
return newWrapper
}
func (r *renderer) renderIndented(name string, rg renderGuide, a []Attributes, children []any) {
var newWrapper bool
if rg.inline || rg.inlineChildren {
newWrapper = r.ensureWrapper()
}
printf := r.getPrintf(name) printf := r.getPrintf(name)
printf("<%s", name) printf("<%s", name)
r.renderAttributes(name, a) r.renderAttributes(name, a)
@ -172,333 +339,87 @@ func (r *renderer) renderInline(name string, rg renderGuide, a []Attributes, chi
return return
} }
var lastBlock bool if len(children) == 0 {
for _, c := range children { printf("</%s>", name)
rd, isReader := c.(io.Reader) if newWrapper {
if isReader && rg.verbatim {
r.clearWrapper() r.clearWrapper()
if r.err == nil {
iw := newIndentWriter(r.out, r.currentIndent+r.indent.Indent)
iw.Write([]byte{'\n'})
_, r.err = io.Copy(iw, rd)
if r.err == nil {
iw.Flush()
r.err = iw.err
}
}
lastBlock = true
continue
} }
if isReader && rg.script { return
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 {
continue
}
s := fmt.Sprint(c)
if s == "" {
continue
}
r.clearWrapper()
if r.err == nil {
iw := newIndentWriter(r.out, r.currentIndent+r.indent.Indent)
iw.Write([]byte{'\n'})
iw.Write([]byte(s))
iw.Flush()
r.err = iw.err
}
lastBlock = true
continue
}
if !isTag && rg.script {
if c == nil {
continue
}
s := fmt.Sprint(c)
if s == "" {
continue
}
r.clearWrapper()
printf("\n%s", s)
lastBlock = true
continue
}
if !isTag {
if c == nil {
continue
}
s := fmt.Sprint(c)
if s == "" {
continue
}
if lastBlock {
printf("\n%s", r.currentIndent)
}
if r.ensureWrapper() {
newWrapper = true
}
if r.err == nil {
ew := newEscapeWriter(r.out)
ew.Write([]byte(s))
ew.Flush()
r.err = ew.err
}
lastBlock = false
continue
}
var rgq renderGuidesQuery
ct(&rgq)
crg := mergeRenderingGuides(rgq.value)
if crg.inline {
if lastBlock {
printf("\n%s", r.currentIndent)
}
if r.ensureWrapper() {
newWrapper = true
}
ct(r)
lastBlock = false
continue
}
r.clearWrapper()
cr := new(renderer)
*cr = *r
cr.currentIndent += cr.indent.Indent
cr.pwidth -= indentLen(cr.indent.Indent)
if cr.pwidth < cr.indent.MinPWidth {
cr.pwidth = cr.indent.MinPWidth
}
printf("\n%s", cr.currentIndent)
ct(cr)
if cr.err != nil {
r.err = cr.err
}
lastBlock = true
} }
if lastBlock { block := !rg.inline && !rg.inlineChildren
lastBlock := block
originalIndent, originalWidth := r.currentIndent, r.pwidth
if block {
r.currentIndent += r.indent.Indent
r.pwidth -= indentLen(r.indent.Indent)
if r.pwidth < r.indent.MinPWidth {
r.pwidth = r.indent.MinPWidth
}
}
for _, c := range children {
if c == nil {
continue
}
if ct, isTag := c.(Tag); isTag {
var nw bool
lastBlock, nw = r.renderChildTag(name, block, lastBlock, ct)
if nw {
newWrapper = true
}
continue
}
if rd, isReader := c.(io.Reader); isReader {
if r.renderReaderChild(name, rg, block, lastBlock, rd) {
newWrapper = true
}
lastBlock = rg.verbatim || rg.script
continue
}
if rg.verbatim {
r.renderVerbatimChild(block, c)
lastBlock = true
continue
}
if rg.script {
r.renderChildScript(name, c)
lastBlock = true
continue
}
if r.renderChildContent(name, lastBlock, c) {
newWrapper = true
}
lastBlock = false
}
if block {
r.currentIndent, r.pwidth = originalIndent, originalWidth
}
if block {
r.clearWrapper()
}
if lastBlock || block {
printf("\n%s", r.currentIndent) printf("\n%s", r.currentIndent)
} }
printf("</%s>", name) printf("</%s>", name)
if newWrapper { if newWrapper && !block {
r.clearWrapper() r.clearWrapper()
} }
} }
func (r *renderer) renderBlock(name string, rg renderGuide, a []Attributes, children []any) {
printf := r.getPrintf(name)
printf("<%s", name)
r.renderAttributes(name, a)
printf(">")
if rg.void {
return
}
if len(children) == 0 {
printf("</%s>", name)
return
}
lastBlock := true
originalIndent, originalWidth := r.currentIndent, r.pwidth
r.currentIndent += r.indent.Indent
r.pwidth -= indentLen(r.indent.Indent)
if r.pwidth < r.indent.MinPWidth {
r.pwidth = r.indent.MinPWidth
}
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)
iw.Write([]byte{'\n'})
_, 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 {
continue
}
s := fmt.Sprint(c)
if s == "" {
continue
}
r.clearWrapper()
if r.err == nil {
iw := newIndentWriter(r.out, r.currentIndent)
iw.Write([]byte{'\n'})
iw.Write([]byte(s))
iw.Flush()
r.err = iw.err
}
lastBlock = true
continue
}
if !isTag && rg.script {
if c == nil {
continue
}
s := fmt.Sprint(c)
if s == "" {
continue
}
r.clearWrapper()
printf("\n%s", s)
lastBlock = true
continue
}
if !isTag {
if c == nil {
continue
}
s := fmt.Sprint(c)
if s == "" {
continue
}
if lastBlock {
printf("\n%s", r.currentIndent)
}
r.ensureWrapper()
if r.err == nil {
ew := newEscapeWriter(r.out)
ew.Write([]byte(s))
ew.Flush()
r.err = ew.err
}
lastBlock = false
continue
}
var rgq renderGuidesQuery
ct(&rgq)
crg := mergeRenderingGuides(rgq.value)
if crg.inline {
if lastBlock {
printf("\n%s", r.currentIndent)
}
r.ensureWrapper()
ct(r)
lastBlock = false
continue
}
r.clearWrapper()
cr := new(renderer)
*cr = *r
printf("\n%s", cr.currentIndent)
ct(cr)
if cr.err != nil {
r.err = cr.err
}
lastBlock = true
}
r.clearWrapper()
r.currentIndent, r.pwidth = originalIndent, originalWidth
printf("\n%s</%s>", r.currentIndent, name)
}
func (r *renderer) render(name string, children []any) { func (r *renderer) render(name string, children []any) {
if r.err != nil { if r.err != nil {
return return
@ -511,10 +432,5 @@ func (r *renderer) render(name string, children []any) {
return return
} }
if rg.inline || rg.inlineChildren { r.renderIndented(name, rg, a, c)
r.renderInline(name, rg, a, c)
return
}
r.renderBlock(name, rg, a, c)
} }