1package md2man 2 3import ( 4 "bytes" 5 "fmt" 6 "html" 7 "strings" 8 9 "github.com/russross/blackfriday" 10) 11 12type roffRenderer struct { 13 ListCounters []int 14} 15 16// RoffRenderer creates a new blackfriday Renderer for generating roff documents 17// from markdown 18func RoffRenderer(flags int) blackfriday.Renderer { 19 return &roffRenderer{} 20} 21 22func (r *roffRenderer) GetFlags() int { 23 return 0 24} 25 26func (r *roffRenderer) TitleBlock(out *bytes.Buffer, text []byte) { 27 out.WriteString(".TH ") 28 29 splitText := bytes.Split(text, []byte("\n")) 30 for i, line := range splitText { 31 line = bytes.TrimPrefix(line, []byte("% ")) 32 if i == 0 { 33 line = bytes.Replace(line, []byte("("), []byte("\" \""), 1) 34 line = bytes.Replace(line, []byte(")"), []byte("\" \""), 1) 35 } 36 line = append([]byte("\""), line...) 37 line = append(line, []byte("\" ")...) 38 out.Write(line) 39 } 40 out.WriteString("\n") 41 42 // disable hyphenation 43 out.WriteString(".nh\n") 44 // disable justification (adjust text to left margin only) 45 out.WriteString(".ad l\n") 46} 47 48func (r *roffRenderer) BlockCode(out *bytes.Buffer, text []byte, lang string) { 49 out.WriteString("\n.PP\n.RS\n\n.nf\n") 50 escapeSpecialChars(out, text) 51 out.WriteString("\n.fi\n.RE\n") 52} 53 54func (r *roffRenderer) BlockQuote(out *bytes.Buffer, text []byte) { 55 out.WriteString("\n.PP\n.RS\n") 56 out.Write(text) 57 out.WriteString("\n.RE\n") 58} 59 60func (r *roffRenderer) BlockHtml(out *bytes.Buffer, text []byte) { // nolint: golint 61 out.Write(text) 62} 63 64func (r *roffRenderer) Header(out *bytes.Buffer, text func() bool, level int, id string) { 65 marker := out.Len() 66 67 switch { 68 case marker == 0: 69 // This is the doc header 70 out.WriteString(".TH ") 71 case level == 1: 72 out.WriteString("\n\n.SH ") 73 case level == 2: 74 out.WriteString("\n.SH ") 75 default: 76 out.WriteString("\n.SS ") 77 } 78 79 if !text() { 80 out.Truncate(marker) 81 return 82 } 83} 84 85func (r *roffRenderer) HRule(out *bytes.Buffer) { 86 out.WriteString("\n.ti 0\n\\l'\\n(.lu'\n") 87} 88 89func (r *roffRenderer) List(out *bytes.Buffer, text func() bool, flags int) { 90 marker := out.Len() 91 r.ListCounters = append(r.ListCounters, 1) 92 out.WriteString("\n.RS\n") 93 if !text() { 94 out.Truncate(marker) 95 return 96 } 97 r.ListCounters = r.ListCounters[:len(r.ListCounters)-1] 98 out.WriteString("\n.RE\n") 99} 100 101func (r *roffRenderer) ListItem(out *bytes.Buffer, text []byte, flags int) { 102 if flags&blackfriday.LIST_TYPE_ORDERED != 0 { 103 out.WriteString(fmt.Sprintf(".IP \"%3d.\" 5\n", r.ListCounters[len(r.ListCounters)-1])) 104 r.ListCounters[len(r.ListCounters)-1]++ 105 } else { 106 out.WriteString(".IP \\(bu 2\n") 107 } 108 out.Write(text) 109 out.WriteString("\n") 110} 111 112func (r *roffRenderer) Paragraph(out *bytes.Buffer, text func() bool) { 113 marker := out.Len() 114 out.WriteString("\n.PP\n") 115 if !text() { 116 out.Truncate(marker) 117 return 118 } 119 if marker != 0 { 120 out.WriteString("\n") 121 } 122} 123 124func (r *roffRenderer) Table(out *bytes.Buffer, header []byte, body []byte, columnData []int) { 125 out.WriteString("\n.TS\nallbox;\n") 126 127 maxDelims := 0 128 lines := strings.Split(strings.TrimRight(string(header), "\n")+"\n"+strings.TrimRight(string(body), "\n"), "\n") 129 for _, w := range lines { 130 curDelims := strings.Count(w, "\t") 131 if curDelims > maxDelims { 132 maxDelims = curDelims 133 } 134 } 135 out.Write([]byte(strings.Repeat("l ", maxDelims+1) + "\n")) 136 out.Write([]byte(strings.Repeat("l ", maxDelims+1) + ".\n")) 137 out.Write(header) 138 if len(header) > 0 { 139 out.Write([]byte("\n")) 140 } 141 142 out.Write(body) 143 out.WriteString("\n.TE\n") 144} 145 146func (r *roffRenderer) TableRow(out *bytes.Buffer, text []byte) { 147 if out.Len() > 0 { 148 out.WriteString("\n") 149 } 150 out.Write(text) 151} 152 153func (r *roffRenderer) TableHeaderCell(out *bytes.Buffer, text []byte, align int) { 154 if out.Len() > 0 { 155 out.WriteString("\t") 156 } 157 if len(text) == 0 { 158 text = []byte{' '} 159 } 160 out.Write([]byte("\\fB\\fC" + string(text) + "\\fR")) 161} 162 163func (r *roffRenderer) TableCell(out *bytes.Buffer, text []byte, align int) { 164 if out.Len() > 0 { 165 out.WriteString("\t") 166 } 167 if len(text) > 30 { 168 text = append([]byte("T{\n"), text...) 169 text = append(text, []byte("\nT}")...) 170 } 171 if len(text) == 0 { 172 text = []byte{' '} 173 } 174 out.Write(text) 175} 176 177func (r *roffRenderer) Footnotes(out *bytes.Buffer, text func() bool) { 178 179} 180 181func (r *roffRenderer) FootnoteItem(out *bytes.Buffer, name, text []byte, flags int) { 182 183} 184 185func (r *roffRenderer) AutoLink(out *bytes.Buffer, link []byte, kind int) { 186 out.WriteString("\n\\[la]") 187 out.Write(link) 188 out.WriteString("\\[ra]") 189} 190 191func (r *roffRenderer) CodeSpan(out *bytes.Buffer, text []byte) { 192 out.WriteString("\\fB\\fC") 193 escapeSpecialChars(out, text) 194 out.WriteString("\\fR") 195} 196 197func (r *roffRenderer) DoubleEmphasis(out *bytes.Buffer, text []byte) { 198 out.WriteString("\\fB") 199 out.Write(text) 200 out.WriteString("\\fP") 201} 202 203func (r *roffRenderer) Emphasis(out *bytes.Buffer, text []byte) { 204 out.WriteString("\\fI") 205 out.Write(text) 206 out.WriteString("\\fP") 207} 208 209func (r *roffRenderer) Image(out *bytes.Buffer, link []byte, title []byte, alt []byte) { 210} 211 212func (r *roffRenderer) LineBreak(out *bytes.Buffer) { 213 out.WriteString("\n.br\n") 214} 215 216func (r *roffRenderer) Link(out *bytes.Buffer, link []byte, title []byte, content []byte) { 217 out.Write(content) 218 r.AutoLink(out, link, 0) 219} 220 221func (r *roffRenderer) RawHtmlTag(out *bytes.Buffer, tag []byte) { // nolint: golint 222 out.Write(tag) 223} 224 225func (r *roffRenderer) TripleEmphasis(out *bytes.Buffer, text []byte) { 226 out.WriteString("\\s+2") 227 out.Write(text) 228 out.WriteString("\\s-2") 229} 230 231func (r *roffRenderer) StrikeThrough(out *bytes.Buffer, text []byte) { 232} 233 234func (r *roffRenderer) FootnoteRef(out *bytes.Buffer, ref []byte, id int) { 235 236} 237 238func (r *roffRenderer) Entity(out *bytes.Buffer, entity []byte) { 239 out.WriteString(html.UnescapeString(string(entity))) 240} 241 242func (r *roffRenderer) NormalText(out *bytes.Buffer, text []byte) { 243 escapeSpecialChars(out, text) 244} 245 246func (r *roffRenderer) DocumentHeader(out *bytes.Buffer) { 247} 248 249func (r *roffRenderer) DocumentFooter(out *bytes.Buffer) { 250} 251 252func needsBackslash(c byte) bool { 253 for _, r := range []byte("-_&\\~") { 254 if c == r { 255 return true 256 } 257 } 258 return false 259} 260 261func escapeSpecialChars(out *bytes.Buffer, text []byte) { 262 for i := 0; i < len(text); i++ { 263 // escape initial apostrophe or period 264 if len(text) >= 1 && (text[0] == '\'' || text[0] == '.') { 265 out.WriteString("\\&") 266 } 267 268 // directly copy normal characters 269 org := i 270 271 for i < len(text) && !needsBackslash(text[i]) { 272 i++ 273 } 274 if i > org { 275 out.Write(text[org:i]) 276 } 277 278 // escape a character 279 if i >= len(text) { 280 break 281 } 282 out.WriteByte('\\') 283 out.WriteByte(text[i]) 284 } 285} 286