package textfmt import ( "bytes" "errors" "fmt" "io" "strings" ) func ttyTextToString(text Txt) string { var b bytes.Buffer renderTTYText(&b, text) return b.String() } func ttyDefinitionNames(d []DefinitionItem) []string { var n []string for _, di := range d { n = append(n, ttyTextToString(di.name)) } return n } func renderTTYText(w io.Writer, text Txt) { if len(text.cat) > 0 { for i, tc := range text.cat { if i > 0 { write(w, " ") } renderTTYText(w, tc) } return } var f func() (io.Writer, error) w, f = writeWith(w, escapeTeletype(), singleLine()) if text.link == "" { write(w, text.text) f() return } if text.text == "" { write(w, text.link) f() return } write(w, text.text) write(w, " (") write(w, text.link) write(w, ")") f() } func renderTTYTitle(w io.Writer, e Entry) { write(w, timesn(" ", e.indent)) renderTTYText(w, e.text) } func renderTTYParagraph(w io.Writer, e Entry) { var indentation wrapper indentFirst := e.indent + e.indentFirst if e.wrapWidth == 0 { indentation = indent(indentFirst, e.indent) } else { indentation = wrapIndent(indentFirst, e.indent, e.wrapWidth, e.wrapWidth) } w, f := writeWith(w, indentation) renderTTYText(w, e.text) f() } func renderTTYList(w io.Writer, e Entry) { var bullets []string for _, item := range e.items { itemStyle := mergeItemStyles(item.style) if itemStyle.noBullet { bullets = append(bullets, "") } else { bullet := "-" if itemStyle.bullet != "" { bullet = itemStyle.bullet } bullets = append(bullets, bullet) } } maxBulletLength := maxLength(bullets) for i := range bullets { bullets[i] = padRight(bullets[i], maxBulletLength) } for i, item := range e.items { if i > 0 { write(w, "\n") } p := itemToParagraph(e, item.text, bullets[i]) renderTTYParagraph(w, p) } } func renderTTYNumberedList(w io.Writer, e Entry) { var items []ListItem for i, item := range e.items { items = append( items, Item( item.text, append(item.style, Bullet(fmt.Sprintf("%d.", i+1)))..., ), ) } e.typ = list e.items = items renderTTYList(w, e) } func renderTTYDefinitions(w io.Writer, e Entry) { itemStyles := make([]ItemStyle, len(e.definitions)) for i := range e.definitions { itemStyles[i] = mergeItemStyles(e.definitions[i].style) } bullets := make([]string, len(e.definitions)) for i := range e.definitions { if itemStyles[i].noBullet { continue } if itemStyles[i].bullet == "" { bullets[i] = "-" continue } bullets[i] = itemStyles[i].bullet } maxBulletLength := maxLength(bullets) items := make([]ListItem, len(e.definitions)) for i := range e.definitions { var bullet string if maxBulletLength == 0 { bullet = fmt.Sprintf("%s:", ttyTextToString(e.definitions[i].name)) } else { bullet = fmt.Sprintf( "%s %s:", padRight(bullets[i], maxBulletLength), ttyTextToString(e.definitions[i].name), ) } items[i] = Item(e.definitions[i].value, Bullet(bullet)) } e.typ = list e.items = items renderTTYList(w, e) } func renderTTYNumberedDefinitions(w io.Writer, e Entry) { var defs []DefinitionItem for i, definition := range e.definitions { defs = append( defs, Definition( definition.name, definition.value, append(definition.style, Bullet(fmt.Sprintf("%d.", i+1)))..., ), ) } e.typ = definitions e.definitions = defs renderTTYDefinitions(w, e) } func ttyCellTexts(rows []TableRow) [][]string { var cellTexts [][]string for _, row := range rows { var c []string for _, cell := range row.cells { c = append(c, ttyTextToString(cell.text)) } cellTexts = append(cellTexts, c) } return cellTexts } func renderTTYTable(w io.Writer, e Entry) { if len(e.rows) == 0 { return } e.rows = normalizeTable(e.rows) if len(e.rows[0].cells) == 0 { return } cellTexts := ttyCellTexts(e.rows) totalSeparatorWidth := (len(cellTexts[0]) - 1) * 3 if e.wrapWidth > 0 { allocatedWidth := e.wrapWidth - e.indent - totalSeparatorWidth columnWeights := columnWeights(cellTexts) targetColumnWidths := targetColumnWidths(allocatedWidth, columnWeights) for i := range cellTexts { for j := range cellTexts[i] { width := targetColumnWidths[j] cellTexts[i][j] = editString(cellTexts[i][j], wrap(width, width)) } } } columnWidths := columnWidths(cellTexts) totalWidth := totalSeparatorWidth for i := range columnWidths { totalWidth += columnWidths[i] } hasHeader := e.rows[0].header w, f := writeWith(w, indent(e.indent, e.indent)) for i := range cellTexts { if i > 0 { sep := "-" if hasHeader && i == 1 { sep = "=" } write(w, "\n") write(w, timesn(sep, totalWidth)) write(w, "\n") } lines := make([][]string, len(cellTexts[i])) for j := range cellTexts[i] { lines[j] = strings.Split(cellTexts[i][j], "\n") } var maxLines int for j := range lines { if len(lines[j]) > maxLines { maxLines = len(lines[j]) } } for k := 0; k < maxLines; k++ { if k > 0 { write(w, "\n") } for j := range lines { if j > 0 { write(w, " | ") } var l string if k < len(lines[j]) { l = lines[j][k] } write(w, padRight(l, columnWidths[j])) } } } if hasHeader && len(cellTexts) == 1 { write(w, "\n", timesn("=", totalWidth)) } f() } func renderTTYCode(w io.Writer, e Entry) { w, f := writeWith(w, escapeTeletype(), indent(e.indent, e.indent)) write(w, e.text.text) f() } func renderTTYMultiple(w io.Writer, s SyntaxItem) { s.topLevel = false s.multiple = false renderTTYSyntaxItem(w, s) write(w, "...") } func renderTTYRequired(w io.Writer, s SyntaxItem) { s.delimited = true s.topLevel = false s.required = false write(w, "<") renderTTYSyntaxItem(w, s) write(w, ">") } func renderTTYOptional(w io.Writer, s SyntaxItem) { s.delimited = true s.topLevel = false s.optional = false write(w, "[") renderTTYSyntaxItem(w, s) write(w, "]") } func renderTTYSequence(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 renderTTYSyntaxItem(w, item) } if !s.delimited && !s.topLevel { write(w, ")") } } func renderTTYChoice(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 renderTTYSyntaxItem(w, item) } if !s.delimited && !s.topLevel { write(w, ")") } } func renderTTYSymbol(w io.Writer, s SyntaxItem) { write(w, s.symbol) } func renderTTYSyntaxItem(w io.Writer, s SyntaxItem) { switch { // foo... case s.multiple: renderTTYMultiple(w, s) // case s.required: renderTTYRequired(w, s) // [foo] case s.optional: renderTTYOptional(w, s) // foo bar baz or (foo bar baz) case len(s.sequence) > 0: renderTTYSequence(w, s) // foo|bar|baz or (foo|bar|baz) case len(s.choice) > 0: renderTTYChoice(w, s) // foo default: renderTTYSymbol(w, s) } } func renderTTYSyntax(w io.Writer, e Entry) { w, f := writeWith(w, escapeTeletype(), indent(e.indent, e.indent)) s := e.syntax s.topLevel = true renderTTYSyntaxItem(w, s) f() } func renderTeletype(out io.Writer, d Document) error { w, f := writeWith(out, ttyNBSP(), errorHandler) for i, e := range d.entries { if i > 0 { write(w, "\n\n") } switch e.typ { case title: renderTTYTitle(w, e) case paragraph: renderTTYParagraph(w, e) case list: renderTTYList(w, e) case numberedList: renderTTYNumberedList(w, e) case definitions: renderTTYDefinitions(w, e) case numberedDefinitions: renderTTYNumberedDefinitions(w, e) case table: renderTTYTable(w, e) case code: renderTTYCode(w, e) case syntax: renderTTYSyntax(w, e) default: return errors.New("invalid entry") } } if len(d.entries) > 0 { write(w, "\n") } _, err := f() return err }