2025-09-11 21:16:09 +02:00
|
|
|
package textfmt
|
|
|
|
|
|
2025-10-14 20:46:32 +02:00
|
|
|
import (
|
|
|
|
|
"io"
|
|
|
|
|
"time"
|
|
|
|
|
)
|
2025-09-11 21:16:09 +02:00
|
|
|
|
|
|
|
|
const (
|
|
|
|
|
invalid = iota
|
|
|
|
|
title
|
|
|
|
|
paragraph
|
|
|
|
|
list
|
|
|
|
|
numberedList
|
|
|
|
|
definitions
|
|
|
|
|
numberedDefinitions
|
|
|
|
|
table
|
|
|
|
|
code
|
|
|
|
|
syntax
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type Txt struct {
|
|
|
|
|
text string
|
|
|
|
|
link string
|
|
|
|
|
bold, italic bool
|
|
|
|
|
cat []Txt
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type ListItem struct {
|
|
|
|
|
text Txt
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type DefinitionItem struct {
|
|
|
|
|
name, value Txt
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type TableCell struct {
|
|
|
|
|
text Txt
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type TableRow struct {
|
|
|
|
|
cells []TableCell
|
|
|
|
|
header bool
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type SyntaxItem struct {
|
|
|
|
|
symbol string
|
|
|
|
|
multiple bool
|
|
|
|
|
required bool
|
|
|
|
|
optional bool
|
|
|
|
|
sequence []SyntaxItem
|
|
|
|
|
choice []SyntaxItem
|
|
|
|
|
topLevel bool
|
|
|
|
|
delimited bool
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type Entry struct {
|
|
|
|
|
typ int
|
|
|
|
|
text Txt
|
|
|
|
|
titleLevel int
|
|
|
|
|
items []ListItem
|
|
|
|
|
definitions []DefinitionItem
|
|
|
|
|
rows []TableRow
|
|
|
|
|
syntax SyntaxItem
|
|
|
|
|
wrapWidth int
|
2025-10-14 20:46:32 +02:00
|
|
|
indent int
|
|
|
|
|
indentFirst int
|
2025-10-23 02:55:49 +02:00
|
|
|
man struct {
|
|
|
|
|
section int
|
|
|
|
|
date time.Time
|
|
|
|
|
version string
|
2025-10-14 20:46:32 +02:00
|
|
|
category string
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type TitleInfo struct {
|
2025-10-23 02:55:49 +02:00
|
|
|
section int
|
|
|
|
|
date time.Time
|
|
|
|
|
version string
|
2025-10-14 20:46:32 +02:00
|
|
|
category string
|
2025-09-11 21:16:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type Document struct {
|
|
|
|
|
entries []Entry
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func Text(text string) Txt {
|
|
|
|
|
return Txt{text: text}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-10 15:47:36 +02:00
|
|
|
func Link(label, uri string) Txt {
|
|
|
|
|
return Txt{text: label, link: uri}
|
2025-09-11 21:16:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func Bold(t Txt) Txt {
|
|
|
|
|
t.bold = true
|
|
|
|
|
return t
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func Italic(t Txt) Txt {
|
|
|
|
|
t.italic = true
|
|
|
|
|
return t
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func Cat(t ...Txt) Txt {
|
|
|
|
|
return Txt{cat: t}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-14 20:46:32 +02:00
|
|
|
func Title(level int, text string, manInfo ...TitleInfo) Entry {
|
|
|
|
|
if level != 0 {
|
|
|
|
|
return Entry{
|
|
|
|
|
typ: title,
|
|
|
|
|
titleLevel: level,
|
|
|
|
|
text: Text(text),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
e := Entry{
|
2025-09-11 21:16:09 +02:00
|
|
|
typ: title,
|
|
|
|
|
titleLevel: level,
|
|
|
|
|
text: Text(text),
|
|
|
|
|
}
|
2025-10-14 20:46:32 +02:00
|
|
|
|
|
|
|
|
for _, m := range manInfo {
|
|
|
|
|
if m.section != 0 {
|
|
|
|
|
e.man.section = m.section
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !m.date.IsZero() {
|
|
|
|
|
e.man.date = m.date
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if m.version != "" {
|
|
|
|
|
e.man.version = m.version
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if m.category != "" {
|
|
|
|
|
e.man.category = m.category
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return e
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-23 02:55:38 +02:00
|
|
|
func ManSection(s int) TitleInfo {
|
2025-10-14 20:46:32 +02:00
|
|
|
return TitleInfo{section: s}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func ReleaseDate(d time.Time) TitleInfo {
|
|
|
|
|
return TitleInfo{date: d}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func ReleaseVersion(v string) TitleInfo {
|
|
|
|
|
return TitleInfo{version: v}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-23 02:55:38 +02:00
|
|
|
func ManCategory(c string) TitleInfo {
|
2025-10-14 20:46:32 +02:00
|
|
|
return TitleInfo{category: c}
|
2025-09-11 21:16:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func Paragraph(t Txt) Entry {
|
|
|
|
|
return Entry{typ: paragraph, text: t}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func Item(text Txt) ListItem {
|
|
|
|
|
return ListItem{text: text}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func List(items ...ListItem) Entry {
|
|
|
|
|
return Entry{typ: list, items: items}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func NumberedList(items ...ListItem) Entry {
|
|
|
|
|
return Entry{typ: numberedList, items: items}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func Definition(name, value Txt) DefinitionItem {
|
|
|
|
|
return DefinitionItem{name: name, value: value}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func DefinitionList(items ...DefinitionItem) Entry {
|
|
|
|
|
return Entry{typ: definitions, definitions: items}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func NumberedDefinitionList(items ...DefinitionItem) Entry {
|
|
|
|
|
return Entry{typ: numberedDefinitions, definitions: items}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func Cell(text Txt) TableCell {
|
|
|
|
|
return TableCell{text: text}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func Header(cells ...TableCell) TableRow {
|
|
|
|
|
return TableRow{cells: cells, header: true}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func Row(cells ...TableCell) TableRow {
|
|
|
|
|
return TableRow{cells: cells}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-28 00:47:41 +01:00
|
|
|
// when no header and markdown, header automatic
|
2025-09-11 21:16:09 +02:00
|
|
|
func Table(rows ...TableRow) Entry {
|
|
|
|
|
return Entry{typ: table, rows: rows}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func CodeBlock(codeBlock string) Entry {
|
|
|
|
|
return Entry{typ: code, text: Text(codeBlock)}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func Symbol(text string) SyntaxItem {
|
|
|
|
|
return SyntaxItem{symbol: text}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func OneOrMore(item SyntaxItem) SyntaxItem {
|
|
|
|
|
item.required = true
|
|
|
|
|
item.optional = false
|
|
|
|
|
item.multiple = true
|
|
|
|
|
return item
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func ZeroOrMore(item SyntaxItem) SyntaxItem {
|
|
|
|
|
item.required = false
|
|
|
|
|
item.optional = true
|
|
|
|
|
item.multiple = true
|
|
|
|
|
return item
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func Required(item SyntaxItem) SyntaxItem {
|
|
|
|
|
item.required = true
|
|
|
|
|
item.optional = false
|
|
|
|
|
return item
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func Optional(item SyntaxItem) SyntaxItem {
|
|
|
|
|
item.required = false
|
|
|
|
|
item.optional = true
|
|
|
|
|
return item
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func Sequence(items ...SyntaxItem) SyntaxItem {
|
|
|
|
|
return SyntaxItem{sequence: items}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func Choice(items ...SyntaxItem) SyntaxItem {
|
|
|
|
|
return SyntaxItem{choice: items}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func Syntax(items ...SyntaxItem) Entry {
|
2025-10-10 15:47:36 +02:00
|
|
|
if len(items) == 1 {
|
|
|
|
|
return Entry{typ: syntax, syntax: items[0]}
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-11 21:16:09 +02:00
|
|
|
return Entry{typ: syntax, syntax: Sequence(items...)}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-14 20:46:32 +02:00
|
|
|
func Wrap(e Entry, width int) Entry {
|
|
|
|
|
e.wrapWidth = width
|
2025-09-11 21:16:09 +02:00
|
|
|
return e
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-14 20:46:32 +02:00
|
|
|
// indentFirst is relative to indent
|
|
|
|
|
func Indent(e Entry, indent, indentFirst int) Entry {
|
|
|
|
|
e.indent, e.indentFirst = indent, indentFirst
|
2025-09-11 21:16:09 +02:00
|
|
|
return e
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func Doc(e ...Entry) Document {
|
|
|
|
|
return Document{entries: e}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func Teletype(out io.Writer, d Document) error {
|
|
|
|
|
return renderTeletype(out, d)
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-14 20:46:32 +02:00
|
|
|
// Runoff is an attempt to render roff format. It is primarily targeting man pages. While it may be possible to
|
|
|
|
|
// use for other purposes, the man macro will likely be required to render the output.
|
|
|
|
|
//
|
|
|
|
|
// Text is always wrapped, or as controlled by the roff processor, except for tables. The Wrap instrunction has
|
|
|
|
|
// no effect, except for tables.
|
2025-10-23 02:55:38 +02:00
|
|
|
func Runoff(out io.Writer, d Document) error {
|
|
|
|
|
return renderRoff(out, d)
|
2025-09-11 21:16:09 +02:00
|
|
|
}
|
|
|
|
|
|
2025-10-28 00:47:41 +01:00
|
|
|
func Markdown(out io.Writer, d Document) error {
|
|
|
|
|
return renderMarkdown(out, d)
|
2025-09-11 21:16:09 +02:00
|
|
|
}
|
|
|
|
|
|
2025-10-28 02:48:55 +01:00
|
|
|
func HTMLFragment(out io.Writer, doc Document) error {
|
2025-09-11 21:16:09 +02:00
|
|
|
// with the won HTML library
|
2025-10-28 02:48:55 +01:00
|
|
|
return renderHTMLFragment(out, doc)
|
2025-09-11 21:16:09 +02:00
|
|
|
}
|
|
|
|
|
|
2025-10-28 02:48:55 +01:00
|
|
|
// if lang is empty, the lang attribute will be omitted
|
|
|
|
|
func HTML(out io.Writer, doc Document, lang string) error {
|
2025-09-11 21:16:09 +02:00
|
|
|
// with the won HTML library
|
2025-10-28 02:48:55 +01:00
|
|
|
return renderHTML(out, doc, lang)
|
2025-09-11 21:16:09 +02:00
|
|
|
}
|