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
|
||||
|
||||
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/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)
|
||||
}
|
||||
|
||||
func HTML(io.Writer, Document) error {
|
||||
func HTMLFragment(out io.Writer, doc Document) error {
|
||||
// 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
|
||||
return nil
|
||||
return renderHTML(out, doc, lang)
|
||||
}
|
||||
|
||||
@ -429,8 +429,6 @@ func renderMarkdown(out io.Writer, d Document) error {
|
||||
}
|
||||
|
||||
switch e.typ {
|
||||
case invalid:
|
||||
return errors.New("invalid entry")
|
||||
case title:
|
||||
renderMDTitle(&w, e)
|
||||
case paragraph:
|
||||
@ -449,6 +447,8 @@ func renderMarkdown(out io.Writer, d Document) error {
|
||||
renderMDCode(&w, e)
|
||||
case syntax:
|
||||
renderMDSyntax(&w, e)
|
||||
default:
|
||||
return errors.New("invalid entry")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -463,8 +463,6 @@ func renderRoff(out io.Writer, d Document) error {
|
||||
}
|
||||
|
||||
switch e.typ {
|
||||
case invalid:
|
||||
return errors.New("invalid entry")
|
||||
case title:
|
||||
renderRoffTitle(&w, e)
|
||||
case paragraph:
|
||||
@ -483,6 +481,8 @@ func renderRoff(out io.Writer, d Document) error {
|
||||
renderRoffCode(&w, e)
|
||||
case syntax:
|
||||
renderRoffSyntax(&w, e)
|
||||
default:
|
||||
return errors.New("invalid entry")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -402,8 +402,6 @@ func renderTeletype(out io.Writer, d Document) error {
|
||||
}
|
||||
|
||||
switch e.typ {
|
||||
case invalid:
|
||||
return errors.New("invalid entry")
|
||||
case title:
|
||||
renderTTYTitle(&w, e)
|
||||
case paragraph:
|
||||
@ -422,6 +420,8 @@ func renderTeletype(out io.Writer, d Document) error {
|
||||
renderTTYCode(&w, e)
|
||||
case syntax:
|
||||
renderTTYSyntax(&w, e)
|
||||
default:
|
||||
return errors.New("invalid entry")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user