diff --git a/notes.txt b/notes.txt index 6e021bb..47addf5 100644 --- a/notes.txt +++ b/notes.txt @@ -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 right audience test wrapped templates -test empty block review which tags should be of type inline-children diff --git a/render.go b/render.go index 9aa206c..382cd1d 100644 --- a/render.go +++ b/render.go @@ -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) { printf := r.getPrintf(tagName) isDeclaration := strings.HasPrefix(tagName, "!") @@ -89,31 +172,29 @@ func (r *renderer) renderUnindented(name string, rg renderGuide, a []Attributes, } for _, c := range children { - if ct, ok := c.(Tag); ok { - ct(r) - continue - } - if c == nil { 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) + rd, isReader := c.(io.Reader) + if isReader && (rg.verbatim || rg.script) { if r.err == nil { - ew.Flush() - r.err = ew.err + _, r.err = io.Copy(r.out, rd) } continue } + if isReader { + r.copyEscaped(rd) + continue + } + + if ct, ok := c.(Tag); ok { + ct(r) + continue + } + s := fmt.Sprint(c) if s == "" { continue @@ -124,42 +205,128 @@ func (r *renderer) renderUnindented(name string, rg renderGuide, a []Attributes, continue } - if r.err == nil { - ew := newEscapeWriter(r.out) - ew.Write([]byte(s)) - ew.Flush() - r.err = ew.err - } + r.writeEscaped(s) } printf("", name) } -func (r *renderer) ensureWrapper() bool { - if _, ok := r.out.(*wrapper); ok { +func (r *renderer) renderReaderChild(tagName string, rg renderGuide, block, lastBlock bool, rd io.Reader) bool { + printf := r.getPrintf(tagName) + if rg.verbatim { + r.clearWrapper() + indent := r.currentIndent + if !block { + indent += r.indent.Indent + } + + r.copyIndented(indent, rd) return false } - r.originalOut = r.out - r.out = newWrapper(r.originalOut, r.pwidth, r.currentIndent) - return true + if rg.script { + r.clearWrapper() + 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() { - w, ok := r.out.(*wrapper) - if !ok { +func (r *renderer) renderChildTag(tagName string, block, lastBlock bool, ct Tag) (bool, bool) { + printf := r.getPrintf(tagName) + + 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 } - if err := w.Flush(); err != nil { - r.err = err + r.clearWrapper() + 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() + 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("<%s", name) r.renderAttributes(name, a) @@ -172,333 +339,87 @@ func (r *renderer) renderInline(name string, rg renderGuide, a []Attributes, chi return } - var lastBlock bool - for _, c := range children { - rd, isReader := c.(io.Reader) - if isReader && rg.verbatim { + if len(children) == 0 { + printf("", name) + if newWrapper { 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 { - 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 + return } - 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("", name) - if newWrapper { + if newWrapper && !block { 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("", 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", r.currentIndent, name) -} - func (r *renderer) render(name string, children []any) { if r.err != nil { return @@ -511,10 +432,5 @@ func (r *renderer) render(name string, children []any) { return } - if rg.inline || rg.inlineChildren { - r.renderInline(name, rg, a, c) - return - } - - r.renderBlock(name, rg, a, c) + r.renderIndented(name, rg, a, c) }