148 lines
2.6 KiB
Go
148 lines
2.6 KiB
Go
package html
|
|
|
|
import (
|
|
"code.squareroundforest.org/arpio/textedit"
|
|
"io"
|
|
"unicode"
|
|
)
|
|
|
|
type wrapState struct {
|
|
line, word []rune
|
|
inWord, inTag, inSingleQuote, inQuote bool
|
|
lastSpace, started bool
|
|
}
|
|
|
|
type wrapper struct {
|
|
indent *textedit.Writer
|
|
wrap *textedit.Writer
|
|
}
|
|
|
|
func wrapFeed(width int, s wrapState) ([]rune, wrapState) {
|
|
var ret []rune
|
|
withSpace := s.lastSpace && len(s.line) > 0
|
|
l := len(s.line) + len(s.word)
|
|
if withSpace && len(s.word) > 0 {
|
|
l++
|
|
}
|
|
|
|
feedLine := l > width && len(s.line) > 0
|
|
if feedLine {
|
|
if s.started {
|
|
ret = append(ret, '\n')
|
|
}
|
|
|
|
ret = append(ret, s.line...)
|
|
s.line = nil
|
|
s.started = true
|
|
}
|
|
|
|
if !feedLine && withSpace {
|
|
s.line = append(s.line, ' ')
|
|
}
|
|
|
|
s.line = append(s.line, s.word...)
|
|
s.word = nil
|
|
return ret, s
|
|
}
|
|
|
|
func wrapEdit(width int) func(rune, wrapState) ([]rune, wrapState) {
|
|
return func(r rune, s wrapState) ([]rune, wrapState) {
|
|
var ret []rune
|
|
if s.inSingleQuote {
|
|
s.inSingleQuote = r != '\''
|
|
s.word = append(s.word, r)
|
|
return ret, s
|
|
}
|
|
|
|
if s.inQuote {
|
|
s.inQuote = r != '"'
|
|
s.word = append(s.word, r)
|
|
return ret, s
|
|
}
|
|
|
|
if s.inTag {
|
|
s.inSingleQuote = r == '\''
|
|
s.inQuote = r == '"'
|
|
s.inTag = r != '>'
|
|
s.inWord = !s.inTag && !unicode.IsSpace(r)
|
|
if s.inTag || !unicode.IsSpace(r) {
|
|
s.word = append(s.word, r)
|
|
}
|
|
|
|
if !s.inTag {
|
|
ret, s = wrapFeed(width, s)
|
|
s.lastSpace = unicode.IsSpace(r)
|
|
}
|
|
|
|
return ret, s
|
|
}
|
|
|
|
if s.inWord {
|
|
s.inTag = r == '<'
|
|
s.inWord = !s.inTag && !unicode.IsSpace(r)
|
|
if !s.inWord {
|
|
ret, s = wrapFeed(width, s)
|
|
s.lastSpace = unicode.IsSpace(r)
|
|
}
|
|
|
|
if s.inWord || s.inTag {
|
|
s.word = append(s.word, r)
|
|
}
|
|
|
|
return ret, s
|
|
}
|
|
|
|
if unicode.IsSpace(r) {
|
|
s.lastSpace = true
|
|
return ret, s
|
|
}
|
|
|
|
s.word = append(s.word, r)
|
|
s.inTag = r == '<'
|
|
s.inWord = !s.inTag
|
|
return ret, s
|
|
}
|
|
}
|
|
|
|
func wrapReleaseState(width int) func(wrapState) []rune {
|
|
return func(s wrapState) []rune {
|
|
var ret []rune
|
|
if s.inTag || s.inWord {
|
|
ret, s = wrapFeed(width, s)
|
|
}
|
|
|
|
ret1, _ := wrapFeed(0, s)
|
|
return append(ret, ret1...)
|
|
}
|
|
}
|
|
|
|
func newWrapper(out io.Writer, width int, indent string) *wrapper {
|
|
indentWriter := newIndentWriter(out, indent)
|
|
return &wrapper{
|
|
indent: indentWriter,
|
|
wrap: textedit.New(
|
|
indentWriter,
|
|
textedit.Func(
|
|
wrapEdit(width),
|
|
wrapReleaseState(width),
|
|
),
|
|
),
|
|
}
|
|
}
|
|
|
|
func (w *wrapper) Write(p []byte) (int, error) {
|
|
return w.wrap.Write(p)
|
|
}
|
|
|
|
func (w *wrapper) Flush() error {
|
|
if err := w.wrap.Flush(); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := w.indent.Flush(); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|