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