package textfmt import ( "bytes" "errors" "fmt" "io" "strings" ) func mdTextToString(text Txt) string { var b bytes.Buffer renderMDText(&b, text) return b.String() } func mdCellTexts(rows []TableRow) [][]string { var texts [][]string for _, row := range rows { var rowTexts []string for _, cell := range row.cells { rowTexts = append(rowTexts, mdTextToString(cell.text)) } texts = append(texts, rowTexts) } return texts } func mdEnsureHeaderTexts(h []string) []string { var hh []string for _, t := range h { if strings.TrimSpace(t) == "" { t = "\\-" } hh = append(hh, t) } return hh } func renderMDText(w io.Writer, text Txt) { if len(text.cat) > 0 { for i, tc := range text.cat { if i > 0 { write(w, " ") } renderMDText(w, tc) } return } if text.bold { write(w, "**") } if text.italic { write(w, "_") } defer func() { if text.italic { write(w, "_") } if text.bold { write(w, "**") } }() if text.link == "" { w, f := writeWith(w, singleLine(), escapeMarkdown()) write(w, text.text) w, _ = f() return } if text.text == "" { w, f := writeWith(w, singleLine(), escapeMarkdown()) write(w, text.link) w, _ = f() return } write(w, "[") w, f := writeWith(w, singleLine(), escapeMarkdown()) write(w, text.text) w, _ = f() write(w, "](") w, f = writeWith(w, singleLine(), escapeMarkdown()) write(w, text.link) w, _ = f() write(w, ")") w, _ = f() } func renderMDTitle(w io.Writer, e Entry) { hashes := e.titleLevel + 1 if hashes > 6 { hashes = 6 } write(w, timesn("#", hashes), " ") renderMDText(w, e.text) } func renderMDParagraphIndent(w io.Writer, e Entry) { var f func() (io.Writer, error) if e.wrapWidth > 0 { indentFirst := e.indent + e.indentFirst w, f = writeWith(w, wrapIndent(indentFirst, e.indent, e.wrapWidth, e.wrapWidth)) } renderMDText(w, e.text) if f != nil { f() } } func renderMDParagraph(w io.Writer, e Entry) { e.indent = 0 e.indentFirst = 0 renderMDParagraphIndent(w, e) } func renderMDList(w io.Writer, e Entry) { e.indent = 2 e.indentFirst = -2 if e.wrapWidth > 2 { e.wrapWidth -= 2 } for i, item := range e.items { if i > 0 { write(w, "\n") } write(w, "- ") p := itemToParagraph(e, item.text) renderMDParagraphIndent(w, p) } } func renderMDNumberedList(w io.Writer, e Entry) { maxDigits := numDigits(len(e.items)) e.indent = maxDigits + 2 e.indentFirst = 0 - maxDigits - 2 if e.wrapWidth > maxDigits+2 { e.wrapWidth -= maxDigits + 2 } for i, item := range e.items { if i > 0 { write(w, "\n") } write(w, padRight(fmt.Sprintf("%d.", i+1), maxDigits+2)) p := itemToParagraph(e, item.text) renderMDParagraphIndent(w, p) } } func renderMDDefinitions(w io.Writer, e Entry) { for _, d := range e.definitions { e.items = append( e.items, Item(Cat(Text(fmt.Sprintf("%s:", d.name.text)), d.value)), ) } renderMDList(w, e) } func renderMDNumberedDefinitions(w io.Writer, e Entry) { for _, d := range e.definitions { e.items = append( e.items, Item(Cat(Text(fmt.Sprintf("%s:", d.name.text)), d.value)), ) } renderMDNumberedList(w, e) } func renderMDTable(w io.Writer, e Entry) { e.rows = normalizeTable(e.rows) e.rows = ensureHeader(e.rows) if len(e.rows) == 0 || len(e.rows[0].cells) == 0 { return } headerTexts := mdCellTexts(e.rows[:1]) cellTexts := mdCellTexts(e.rows[1:]) headerTexts[0] = mdEnsureHeaderTexts(headerTexts[0]) columns := columnWidths(headerTexts) cellColumns := columnWidths(cellTexts) if len(cellColumns) > 0 { for i := range columns { if cellColumns[i] > columns[i] { columns[i] = cellColumns[i] } } } write(w, "|") for i, h := range headerTexts[0] { write(w, " ", padRight(h, columns[i])) write(w, " |") } write(w, "\n|") for _, c := range columns { write(w, timesn("-", c+1)) write(w, "-|") } for _, row := range cellTexts { write(w, "\n|") for i, cell := range row { write(w, " ", padRight(cell, columns[i])) write(w, " |") } } } func renderMDCode(w io.Writer, e Entry) { write(w, "```\n") write(w, e.text.text) write(w, "\n```") } func renderMDMultiple(w io.Writer, s SyntaxItem) { s.topLevel = false s.multiple = false renderMDSyntaxItem(w, s) write(w, "...") } func renderMDRequired(w io.Writer, s SyntaxItem) { s.delimited = true s.topLevel = false s.required = false write(w, "<") renderMDSyntaxItem(w, s) write(w, ">") } func renderMDOptional(w io.Writer, s SyntaxItem) { s.delimited = true s.topLevel = false s.optional = false write(w, "[") renderMDSyntaxItem(w, s) write(w, "]") } func renderMDSequence(w io.Writer, s SyntaxItem) { if !s.delimited && !s.topLevel { write(w, "(") } for i, item := range s.sequence { if i > 0 { write(w, " ") } item.delimited = false renderMDSyntaxItem(w, item) } if !s.delimited && !s.topLevel { write(w, ")") } } func renderMDChoice(w io.Writer, s SyntaxItem) { if !s.delimited && !s.topLevel { write(w, "(") } for i, item := range s.choice { if i > 0 { separator := "|" if s.topLevel { separator = "\n" } write(w, separator) } item.delimited = false item.topLevel = s.topLevel renderMDSyntaxItem(w, item) } if !s.delimited && !s.topLevel { write(w, ")") } } func renderMDSymbol(w io.Writer, s SyntaxItem) { w, f := writeWith(w, escapeMarkdown()) write(w, s.symbol) f() } func renderMDSyntaxItem(w io.Writer, s SyntaxItem) { switch { // foo... case s.multiple: renderMDMultiple(w, s) // case s.required: renderMDRequired(w, s) // [foo] case s.optional: renderMDOptional(w, s) // foo bar baz or (foo bar baz) case len(s.sequence) > 0: renderMDSequence(w, s) // foo|bar|baz or (foo|bar|baz) case len(s.choice) > 0: renderMDChoice(w, s) // foo default: renderMDSymbol(w, s) } } func renderMDSyntax(w io.Writer, e Entry) { s := e.syntax s.topLevel = true write(w, "```\n") renderMDSyntaxItem(w, s) write(w, "\n```") } func renderMarkdown(out io.Writer, d Document) error { w, f := writeWith(out, mdNBSP(), errorHandler) for i, e := range d.entries { if i > 0 { write(w, "\n\n") } switch e.typ { case title: renderMDTitle(w, e) case paragraph: renderMDParagraph(w, e) case list: renderMDList(w, e) case numberedList: renderMDNumberedList(w, e) case definitions: renderMDDefinitions(w, e) case numberedDefinitions: renderMDNumberedDefinitions(w, e) case table: renderMDTable(w, e) case code: renderMDCode(w, e) case syntax: renderMDSyntax(w, e) default: return errors.New("invalid entry") } } if len(d.entries) > 0 { write(w, "\n") } _, err := f() return err }