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