initial html implementation
This commit is contained in:
parent
ad3f63822a
commit
ecb46a4b09
2
go.mod
2
go.mod
@ -3,3 +3,5 @@ module code.squareroundforest.org/arpio/textfmt
|
|||||||
go 1.25.0
|
go 1.25.0
|
||||||
|
|
||||||
require code.squareroundforest.org/arpio/notation v0.0.0-20250826181910-5140794b16b2
|
require code.squareroundforest.org/arpio/notation v0.0.0-20250826181910-5140794b16b2
|
||||||
|
|
||||||
|
require code.squareroundforest.org/arpio/html v0.0.0-20251011102613-70f77954001f // indirect
|
||||||
|
|||||||
2
go.sum
2
go.sum
@ -1,2 +1,4 @@
|
|||||||
|
code.squareroundforest.org/arpio/html v0.0.0-20251011102613-70f77954001f h1:Ep/POhkmvOfSkQklPIpeA4n2FTD2SoFxthjF0SJbsCU=
|
||||||
|
code.squareroundforest.org/arpio/html v0.0.0-20251011102613-70f77954001f/go.mod h1:LX+Fwqu/a7nDayuDNhXA56cVb+BNrkz4M/WCqvw9YFQ=
|
||||||
code.squareroundforest.org/arpio/notation v0.0.0-20250826181910-5140794b16b2 h1:S4mjQHL70CuzFg1AGkr0o0d+4M+ZWM0sbnlYq6f0b3I=
|
code.squareroundforest.org/arpio/notation v0.0.0-20250826181910-5140794b16b2 h1:S4mjQHL70CuzFg1AGkr0o0d+4M+ZWM0sbnlYq6f0b3I=
|
||||||
code.squareroundforest.org/arpio/notation v0.0.0-20250826181910-5140794b16b2/go.mod h1:ait4Fvg9o0+bq5hlxi9dAcPL5a+/sr33qsZPNpToMLY=
|
code.squareroundforest.org/arpio/notation v0.0.0-20250826181910-5140794b16b2/go.mod h1:ait4Fvg9o0+bq5hlxi9dAcPL5a+/sr33qsZPNpToMLY=
|
||||||
|
|||||||
306
html.go
Normal file
306
html.go
Normal file
@ -0,0 +1,306 @@
|
|||||||
|
package textfmt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"code.squareroundforest.org/arpio/html"
|
||||||
|
"code.squareroundforest.org/arpio/html/tag"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func htmlText(t Txt) []any {
|
||||||
|
if len(t.cat) > 0 {
|
||||||
|
var c []any
|
||||||
|
for _, ti := range t.cat {
|
||||||
|
c = append(c, htmlText(ti)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
if t.link != "" {
|
||||||
|
return []any{tag.A(html.Attr("href", t.link), t.text)}
|
||||||
|
}
|
||||||
|
|
||||||
|
return []any{t.text}
|
||||||
|
}
|
||||||
|
|
||||||
|
func htmlTitle(e Entry) html.Tag {
|
||||||
|
h := tag.H6
|
||||||
|
switch e.titleLevel {
|
||||||
|
case 0:
|
||||||
|
h = tag.H1
|
||||||
|
case 1:
|
||||||
|
h = tag.H2
|
||||||
|
case 2:
|
||||||
|
h = tag.H3
|
||||||
|
case 3:
|
||||||
|
h = tag.H4
|
||||||
|
case 4:
|
||||||
|
h = tag.H5
|
||||||
|
}
|
||||||
|
|
||||||
|
return h(htmlText(e.text)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func htmlParagraph(e Entry) html.Tag {
|
||||||
|
return tag.P(htmlText(e.text)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func htmlList(e Entry) html.Tag {
|
||||||
|
list := tag.Ul
|
||||||
|
for _, item := range e.items {
|
||||||
|
list = list(tag.Li(htmlText(item.text)...))
|
||||||
|
}
|
||||||
|
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
|
||||||
|
func htmlNumberedList(e Entry) html.Tag {
|
||||||
|
list := tag.Ol
|
||||||
|
for _, item := range e.items {
|
||||||
|
list = list(tag.Li(htmlText(item.text)...))
|
||||||
|
}
|
||||||
|
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
|
||||||
|
func htmlDefinitions(e Entry) html.Tag {
|
||||||
|
list := tag.Dl
|
||||||
|
for _, definition := range e.definitions {
|
||||||
|
list = list(
|
||||||
|
tag.Dt(htmlText(definition.name)...),
|
||||||
|
tag.Dd(htmlText(definition.value)...),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
|
||||||
|
func htmlNumberedDefinitions(e Entry) html.Tag {
|
||||||
|
list := tag.Dl
|
||||||
|
for i, definition := range e.definitions {
|
||||||
|
list = list(
|
||||||
|
tag.Dt(append([]any{fmt.Sprintf("%d. ", i+1)}, htmlText(definition.name)...)...),
|
||||||
|
tag.Dd(htmlText(definition.value)...),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
|
||||||
|
func htmlTable(e Entry) html.Tag {
|
||||||
|
table := tag.Table
|
||||||
|
for _, r := range e.rows {
|
||||||
|
row := tag.Tr
|
||||||
|
cell := tag.Td
|
||||||
|
if r.header {
|
||||||
|
cell = tag.Th
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, c := range r.cells {
|
||||||
|
row = row(cell(htmlText(c.text)...))
|
||||||
|
}
|
||||||
|
|
||||||
|
table = table(row)
|
||||||
|
}
|
||||||
|
|
||||||
|
return table
|
||||||
|
}
|
||||||
|
|
||||||
|
func htmlCode(e Entry) html.Tag {
|
||||||
|
return tag.Pre(tag.Code(htmlText(e.text)...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func htmlMultiple(s SyntaxItem) string {
|
||||||
|
s.topLevel = false
|
||||||
|
s.multiple = false
|
||||||
|
return fmt.Sprintf("%s...", htmlSyntaxItem(s))
|
||||||
|
}
|
||||||
|
|
||||||
|
func htmlRequired(s SyntaxItem) string {
|
||||||
|
s.delimited = true
|
||||||
|
s.topLevel = false
|
||||||
|
s.required = false
|
||||||
|
return fmt.Sprintf("<%s>", htmlSyntaxItem(s))
|
||||||
|
}
|
||||||
|
|
||||||
|
func htmlOptional(s SyntaxItem) string {
|
||||||
|
s.delimited = true
|
||||||
|
s.topLevel = false
|
||||||
|
s.optional = false
|
||||||
|
return fmt.Sprintf("[%s]", htmlSyntaxItem(s))
|
||||||
|
}
|
||||||
|
|
||||||
|
func htmlSequence(s SyntaxItem) string {
|
||||||
|
ss := htmlSyntaxItems(s.sequence)
|
||||||
|
if s.delimited || s.topLevel {
|
||||||
|
return strings.Join(ss, " ")
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("(%s)", strings.Join(ss, " "))
|
||||||
|
}
|
||||||
|
|
||||||
|
func htmlChoice(s SyntaxItem) string {
|
||||||
|
ss := htmlSyntaxItems(s.sequence)
|
||||||
|
if s.topLevel {
|
||||||
|
return strings.Join(ss, "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.delimited {
|
||||||
|
return strings.Join(ss, "|")
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("(%s)", strings.Join(ss, "|"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func htmlSymbol(s SyntaxItem) string {
|
||||||
|
return s.symbol
|
||||||
|
}
|
||||||
|
|
||||||
|
func htmlSyntaxItem(s SyntaxItem) string {
|
||||||
|
switch {
|
||||||
|
|
||||||
|
// foo...
|
||||||
|
case s.multiple:
|
||||||
|
return htmlMultiple(s)
|
||||||
|
|
||||||
|
// <foo>
|
||||||
|
case s.required:
|
||||||
|
return htmlRequired(s)
|
||||||
|
|
||||||
|
// [foo]
|
||||||
|
case s.optional:
|
||||||
|
return htmlOptional(s)
|
||||||
|
|
||||||
|
// foo bar baz or (foo bar baz)
|
||||||
|
case len(s.sequence) > 0:
|
||||||
|
return htmlSequence(s)
|
||||||
|
|
||||||
|
// foo|bar|baz or (foo|bar|baz)
|
||||||
|
case len(s.choice) > 0:
|
||||||
|
return htmlChoice(s)
|
||||||
|
|
||||||
|
// foo
|
||||||
|
default:
|
||||||
|
return htmlSymbol(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func htmlSyntaxItems(s []SyntaxItem) []string {
|
||||||
|
var ss []string
|
||||||
|
for _, si := range s {
|
||||||
|
si.delimited = false
|
||||||
|
ss = append(ss, htmlSyntaxItem(si))
|
||||||
|
}
|
||||||
|
|
||||||
|
return ss
|
||||||
|
}
|
||||||
|
|
||||||
|
func htmlSyntax(e Entry) html.Tag {
|
||||||
|
s := e.syntax
|
||||||
|
s.topLevel = true
|
||||||
|
return tag.Pre(htmlSyntaxItem(s))
|
||||||
|
}
|
||||||
|
|
||||||
|
func htmlTag(e Entry) (html.Tag, error) {
|
||||||
|
switch e.typ {
|
||||||
|
case title:
|
||||||
|
return htmlTitle(e), nil
|
||||||
|
case paragraph:
|
||||||
|
return htmlParagraph(e), nil
|
||||||
|
case list:
|
||||||
|
return htmlList(e), nil
|
||||||
|
case numberedList:
|
||||||
|
return htmlNumberedList(e), nil
|
||||||
|
case definitions:
|
||||||
|
return htmlDefinitions(e), nil
|
||||||
|
case numberedDefinitions:
|
||||||
|
return htmlNumberedDefinitions(e), nil
|
||||||
|
case table:
|
||||||
|
return htmlTable(e), nil
|
||||||
|
case code:
|
||||||
|
return htmlCode(e), nil
|
||||||
|
case syntax:
|
||||||
|
return htmlSyntax(e), nil
|
||||||
|
default:
|
||||||
|
return nil, errors.New("invalid entry")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func htmlTags(e []Entry) ([]html.Tag, error) {
|
||||||
|
var tags []html.Tag
|
||||||
|
for _, ei := range e {
|
||||||
|
tag, err := htmlTag(ei)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if tag != nil {
|
||||||
|
tags = append(tags, tag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return tags, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func renderHTMLFragment(out io.Writer, doc Document) error {
|
||||||
|
tags, err := htmlTags(doc.entries)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tag := range tags {
|
||||||
|
indent := html.Indentation{
|
||||||
|
Indent: "\t",
|
||||||
|
PWidth: 120,
|
||||||
|
MinPWidth: 60,
|
||||||
|
}
|
||||||
|
|
||||||
|
if doc.entries[i].wrapWidth != 0 {
|
||||||
|
indent.PWidth = doc.entries[i].wrapWidth
|
||||||
|
indent.MinPWidth = indent.PWidth / 2
|
||||||
|
}
|
||||||
|
|
||||||
|
if doc.entries[i].indent != 0 {
|
||||||
|
indent.Indent = timesn(" ", doc.entries[i].indent)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := html.RenderIndent(out, indent, tag); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func renderHTML(out io.Writer, doc Document, lang string) error {
|
||||||
|
tags, err := htmlTags(doc.entries)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
head := tag.Head(tag.Meta(html.Attr("charset", "utf-8")))
|
||||||
|
if len(doc.entries) > 0 && doc.entries[0].typ == title && doc.entries[0].titleLevel == 0 {
|
||||||
|
head = head(tag.Title(htmlText(doc.entries[0].text)...))
|
||||||
|
}
|
||||||
|
|
||||||
|
body := tag.Body
|
||||||
|
for _, tag := range tags {
|
||||||
|
body = body(tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
htmlDoc := tag.Html(head, body)
|
||||||
|
if lang != "" {
|
||||||
|
htmlDoc = htmlDoc(html.Attr("lang", lang))
|
||||||
|
}
|
||||||
|
|
||||||
|
indent := html.Indentation{
|
||||||
|
Indent: "\t",
|
||||||
|
PWidth: 120,
|
||||||
|
MinPWidth: 60,
|
||||||
|
}
|
||||||
|
|
||||||
|
return html.RenderIndent(out, indent, tag.Doctype("html"), htmlDoc)
|
||||||
|
}
|
||||||
1
html_test.go
Normal file
1
html_test.go
Normal file
@ -0,0 +1 @@
|
|||||||
|
package textfmt_test
|
||||||
9
lib.go
9
lib.go
@ -284,12 +284,13 @@ func Markdown(out io.Writer, d Document) error {
|
|||||||
return renderMarkdown(out, d)
|
return renderMarkdown(out, d)
|
||||||
}
|
}
|
||||||
|
|
||||||
func HTML(io.Writer, Document) error {
|
func HTMLFragment(out io.Writer, doc Document) error {
|
||||||
// with the won HTML library
|
// with the won HTML library
|
||||||
return nil
|
return renderHTMLFragment(out, doc)
|
||||||
}
|
}
|
||||||
|
|
||||||
func HTMLFragment(io.Writer, Document) error {
|
// if lang is empty, the lang attribute will be omitted
|
||||||
|
func HTML(out io.Writer, doc Document, lang string) error {
|
||||||
// with the won HTML library
|
// with the won HTML library
|
||||||
return nil
|
return renderHTML(out, doc, lang)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -429,8 +429,6 @@ func renderMarkdown(out io.Writer, d Document) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch e.typ {
|
switch e.typ {
|
||||||
case invalid:
|
|
||||||
return errors.New("invalid entry")
|
|
||||||
case title:
|
case title:
|
||||||
renderMDTitle(&w, e)
|
renderMDTitle(&w, e)
|
||||||
case paragraph:
|
case paragraph:
|
||||||
@ -449,6 +447,8 @@ func renderMarkdown(out io.Writer, d Document) error {
|
|||||||
renderMDCode(&w, e)
|
renderMDCode(&w, e)
|
||||||
case syntax:
|
case syntax:
|
||||||
renderMDSyntax(&w, e)
|
renderMDSyntax(&w, e)
|
||||||
|
default:
|
||||||
|
return errors.New("invalid entry")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -463,8 +463,6 @@ func renderRoff(out io.Writer, d Document) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch e.typ {
|
switch e.typ {
|
||||||
case invalid:
|
|
||||||
return errors.New("invalid entry")
|
|
||||||
case title:
|
case title:
|
||||||
renderRoffTitle(&w, e)
|
renderRoffTitle(&w, e)
|
||||||
case paragraph:
|
case paragraph:
|
||||||
@ -483,6 +481,8 @@ func renderRoff(out io.Writer, d Document) error {
|
|||||||
renderRoffCode(&w, e)
|
renderRoffCode(&w, e)
|
||||||
case syntax:
|
case syntax:
|
||||||
renderRoffSyntax(&w, e)
|
renderRoffSyntax(&w, e)
|
||||||
|
default:
|
||||||
|
return errors.New("invalid entry")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -402,8 +402,6 @@ func renderTeletype(out io.Writer, d Document) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch e.typ {
|
switch e.typ {
|
||||||
case invalid:
|
|
||||||
return errors.New("invalid entry")
|
|
||||||
case title:
|
case title:
|
||||||
renderTTYTitle(&w, e)
|
renderTTYTitle(&w, e)
|
||||||
case paragraph:
|
case paragraph:
|
||||||
@ -422,6 +420,8 @@ func renderTeletype(out io.Writer, d Document) error {
|
|||||||
renderTTYCode(&w, e)
|
renderTTYCode(&w, e)
|
||||||
case syntax:
|
case syntax:
|
||||||
renderTTYSyntax(&w, e)
|
renderTTYSyntax(&w, e)
|
||||||
|
default:
|
||||||
|
return errors.New("invalid entry")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user