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