package textfmt import ( "bytes" "errors" "fmt" "io" "strings" ) func mdTextToString(text Txt) (string, error) { var b bytes.Buffer w := newMDWriter(&b, true) renderMDText(w, text) w.flush() if w.err != nil { return "", w.err } return b.String(), nil } func mdCellTexts(rows []TableRow) ([][]string, error) { var texts [][]string for _, row := range rows { var rowTexts []string for _, cell := range row.cells { txt, err := mdTextToString(cell.text) if err != nil { return nil, err } rowTexts = append(rowTexts, txt) } texts = append(texts, rowTexts) } return texts, nil } 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 writer, text Txt) { if len(text.cat) > 0 { for i, tc := range text.cat { if i > 0 { w.write(" ") } renderMDText(w, tc) } return } text.text = editString(text.text, singleLine()) text.text = editString(text.text, escapeMarkdown()) text.link = editString(text.link, singleLine()) text.link = editString(text.link, escapeMarkdown()) if text.bold { w.write("**") } if text.italic { w.write("_") } defer func() { if text.italic { w.write("_") } if text.bold { w.write("**") } }() if text.link != "" { if text.text != "" { w.write("[") w.write(text.text) w.write("](") w.write(text.link) w.write(")") return } w.write(text.link) return } w.write(text.text) } func renderMDTitle(w writer, e Entry) { hashes := e.titleLevel + 1 if hashes > 6 { hashes = 6 } w.write(timesn("#", hashes), " ") renderMDText(w, e.text) } func renderMDParagraphIndent(w writer, e Entry) { txt, err := mdTextToString(e.text) if err != nil { w.setErr(err) } indentFirst := e.indent + e.indentFirst if e.wrapWidth > 0 { txt = editString(txt, wrapIndent(indentFirst, e.indent, e.wrapWidth, e.wrapWidth)) } else { // txt = editString(txt, indent(indentFirst, e.indent)) } w.write(txt) } func renderMDParagraph(w writer, e Entry) { e.indent = 0 e.indentFirst = 0 renderMDParagraphIndent(w, e) } func renderMDList(w 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 { w.write("\n") } w.write("- ") p := itemToParagraph(e, item.text) renderMDParagraphIndent(w, p) } } func renderMDNumberedList(w 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 { w.write("\n") } w.write(padRight(fmt.Sprintf("%d.", i+1), maxDigits+2)) p := itemToParagraph(e, item.text) renderMDParagraphIndent(w, p) } } func renderMDDefinitions(w 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 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 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, err := mdCellTexts(e.rows[:1]) if err != nil { w.setErr(err) return } cellTexts, err := mdCellTexts(e.rows[1:]) if err != nil { w.setErr(err) return } 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] } } } w.write("|") for i, h := range headerTexts[0] { w.write(" ", padRight(h, columns[i])) w.write(" |") } w.write("\n|") for _, c := range columns { w.write(timesn("-", c+1)) w.write("-|") } for _, row := range cellTexts { w.write("\n|") for i, cell := range row { w.write(" ", padRight(cell, columns[i])) w.write(" |") } } } func renderMDCode(w writer, e Entry) { w.write("```\n") w.write(e.text.text) w.write("\n```") } func renderMDMultiple(w writer, s SyntaxItem) { s.topLevel = false s.multiple = false renderMDSyntaxItem(w, s) w.write("...") } func renderMDRequired(w writer, s SyntaxItem) { s.delimited = true s.topLevel = false s.required = false w.write("<") renderMDSyntaxItem(w, s) w.write(">") } func renderMDOptional(w writer, s SyntaxItem) { s.delimited = true s.topLevel = false s.optional = false w.write("[") renderMDSyntaxItem(w, s) w.write("]") } func renderMDSequence(w writer, s SyntaxItem) { if !s.delimited && !s.topLevel { w.write("(") } for i, item := range s.sequence { if i > 0 { w.write(" ") } item.delimited = false renderMDSyntaxItem(w, item) } if !s.delimited && !s.topLevel { w.write(")") } } func renderMDChoice(w writer, s SyntaxItem) { if !s.delimited && !s.topLevel { w.write("(") } for i, item := range s.choice { if i > 0 { separator := "|" if s.topLevel { separator = "\n" } w.write(separator) } item.delimited = false renderMDSyntaxItem(w, item) } if !s.delimited && !s.topLevel { w.write(")") } } func renderMDSymbol(w writer, s SyntaxItem) { w.write(editString(s.symbol, escapeMarkdown())) } func renderMDSyntaxItem(w 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 writer, e Entry) { s := e.syntax s.topLevel = true w.write("```\n") renderMDSyntaxItem(w, s) w.write("\n```") } func renderMarkdown(out io.Writer, d Document) error { w := newMDWriter(out, false) for i, e := range d.entries { if err := w.error(); err != nil { return err } if i > 0 { w.write("\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 { w.write("\n") } w.flush() return w.err }