40 codeCloseTag = "\n.fi\n.RE\n" |
40 codeCloseTag = "\n.fi\n.RE\n" |
41 quoteTag = "\n.PP\n.RS\n" |
41 quoteTag = "\n.PP\n.RS\n" |
42 quoteCloseTag = "\n.RE\n" |
42 quoteCloseTag = "\n.RE\n" |
43 listTag = "\n.RS\n" |
43 listTag = "\n.RS\n" |
44 listCloseTag = "\n.RE\n" |
44 listCloseTag = "\n.RE\n" |
45 arglistTag = "\n.TP\n" |
45 dtTag = "\n.TP\n" |
|
46 dd2Tag = "\n" |
46 tableStart = "\n.TS\nallbox;\n" |
47 tableStart = "\n.TS\nallbox;\n" |
47 tableEnd = ".TE\n" |
48 tableEnd = ".TE\n" |
48 tableCellStart = "T{\n" |
49 tableCellStart = "T{\n" |
49 tableCellEnd = "\nT}\n" |
50 tableCellEnd = "\nT}\n" |
50 ) |
51 ) |
88 |
89 |
89 var walkAction = blackfriday.GoToNext |
90 var walkAction = blackfriday.GoToNext |
90 |
91 |
91 switch node.Type { |
92 switch node.Type { |
92 case blackfriday.Text: |
93 case blackfriday.Text: |
93 r.handleText(w, node, entering) |
94 escapeSpecialChars(w, node.Literal) |
94 case blackfriday.Softbreak: |
95 case blackfriday.Softbreak: |
95 out(w, crTag) |
96 out(w, crTag) |
96 case blackfriday.Hardbreak: |
97 case blackfriday.Hardbreak: |
97 out(w, breakTag) |
98 out(w, breakTag) |
98 case blackfriday.Emph: |
99 case blackfriday.Emph: |
148 out(w, codeTag) |
149 out(w, codeTag) |
149 escapeSpecialChars(w, node.Literal) |
150 escapeSpecialChars(w, node.Literal) |
150 out(w, codeCloseTag) |
151 out(w, codeCloseTag) |
151 case blackfriday.Table: |
152 case blackfriday.Table: |
152 r.handleTable(w, node, entering) |
153 r.handleTable(w, node, entering) |
153 case blackfriday.TableCell: |
|
154 r.handleTableCell(w, node, entering) |
|
155 case blackfriday.TableHead: |
154 case blackfriday.TableHead: |
156 case blackfriday.TableBody: |
155 case blackfriday.TableBody: |
157 case blackfriday.TableRow: |
156 case blackfriday.TableRow: |
158 // no action as cell entries do all the nroff formatting |
157 // no action as cell entries do all the nroff formatting |
159 return blackfriday.GoToNext |
158 return blackfriday.GoToNext |
|
159 case blackfriday.TableCell: |
|
160 r.handleTableCell(w, node, entering) |
|
161 case blackfriday.HTMLSpan: |
|
162 // ignore other HTML tags |
160 default: |
163 default: |
161 fmt.Fprintln(os.Stderr, "WARNING: go-md2man does not handle node type "+node.Type.String()) |
164 fmt.Fprintln(os.Stderr, "WARNING: go-md2man does not handle node type "+node.Type.String()) |
162 } |
165 } |
163 return walkAction |
166 return walkAction |
164 } |
|
165 |
|
166 func (r *roffRenderer) handleText(w io.Writer, node *blackfriday.Node, entering bool) { |
|
167 var ( |
|
168 start, end string |
|
169 ) |
|
170 // handle special roff table cell text encapsulation |
|
171 if node.Parent.Type == blackfriday.TableCell { |
|
172 if len(node.Literal) > 30 { |
|
173 start = tableCellStart |
|
174 end = tableCellEnd |
|
175 } else { |
|
176 // end rows that aren't terminated by "tableCellEnd" with a cr if end of row |
|
177 if node.Parent.Next == nil && !node.Parent.IsHeader { |
|
178 end = crTag |
|
179 } |
|
180 } |
|
181 } |
|
182 out(w, start) |
|
183 escapeSpecialChars(w, node.Literal) |
|
184 out(w, end) |
|
185 } |
167 } |
186 |
168 |
187 func (r *roffRenderer) handleHeading(w io.Writer, node *blackfriday.Node, entering bool) { |
169 func (r *roffRenderer) handleHeading(w io.Writer, node *blackfriday.Node, entering bool) { |
188 if entering { |
170 if entering { |
189 switch node.Level { |
171 switch node.Level { |
228 func (r *roffRenderer) handleItem(w io.Writer, node *blackfriday.Node, entering bool) { |
210 func (r *roffRenderer) handleItem(w io.Writer, node *blackfriday.Node, entering bool) { |
229 if entering { |
211 if entering { |
230 if node.ListFlags&blackfriday.ListTypeOrdered != 0 { |
212 if node.ListFlags&blackfriday.ListTypeOrdered != 0 { |
231 out(w, fmt.Sprintf(".IP \"%3d.\" 5\n", r.listCounters[len(r.listCounters)-1])) |
213 out(w, fmt.Sprintf(".IP \"%3d.\" 5\n", r.listCounters[len(r.listCounters)-1])) |
232 r.listCounters[len(r.listCounters)-1]++ |
214 r.listCounters[len(r.listCounters)-1]++ |
|
215 } else if node.ListFlags&blackfriday.ListTypeTerm != 0 { |
|
216 // DT (definition term): line just before DD (see below). |
|
217 out(w, dtTag) |
|
218 r.firstDD = true |
233 } else if node.ListFlags&blackfriday.ListTypeDefinition != 0 { |
219 } else if node.ListFlags&blackfriday.ListTypeDefinition != 0 { |
234 // state machine for handling terms and following definitions |
220 // DD (definition description): line that starts with ": ". |
235 // since blackfriday does not distinguish them properly, nor |
221 // |
236 // does it seperate them into separate lists as it should |
222 // We have to distinguish between the first DD and the |
237 if !r.defineTerm { |
223 // subsequent ones, as there should be no vertical |
238 out(w, arglistTag) |
224 // whitespace between the DT and the first DD. |
239 r.defineTerm = true |
225 if r.firstDD { |
|
226 r.firstDD = false |
240 } else { |
227 } else { |
241 r.defineTerm = false |
228 out(w, dd2Tag) |
242 } |
229 } |
243 } else { |
230 } else { |
244 out(w, ".IP \\(bu 2\n") |
231 out(w, ".IP \\(bu 2\n") |
245 } |
232 } |
246 } else { |
233 } else { |
249 } |
236 } |
250 |
237 |
251 func (r *roffRenderer) handleTable(w io.Writer, node *blackfriday.Node, entering bool) { |
238 func (r *roffRenderer) handleTable(w io.Writer, node *blackfriday.Node, entering bool) { |
252 if entering { |
239 if entering { |
253 out(w, tableStart) |
240 out(w, tableStart) |
254 //call walker to count cells (and rows?) so format section can be produced |
241 // call walker to count cells (and rows?) so format section can be produced |
255 columns := countColumns(node) |
242 columns := countColumns(node) |
256 out(w, strings.Repeat("l ", columns)+"\n") |
243 out(w, strings.Repeat("l ", columns)+"\n") |
257 out(w, strings.Repeat("l ", columns)+".\n") |
244 out(w, strings.Repeat("l ", columns)+".\n") |
258 } else { |
245 } else { |
259 out(w, tableEnd) |
246 out(w, tableEnd) |
260 } |
247 } |
261 } |
248 } |
262 |
249 |
263 func (r *roffRenderer) handleTableCell(w io.Writer, node *blackfriday.Node, entering bool) { |
250 func (r *roffRenderer) handleTableCell(w io.Writer, node *blackfriday.Node, entering bool) { |
264 var ( |
251 if entering { |
265 start, end string |
252 var start string |
266 ) |
|
267 if node.IsHeader { |
|
268 start = codespanTag |
|
269 end = codespanCloseTag |
|
270 } |
|
271 if entering { |
|
272 if node.Prev != nil && node.Prev.Type == blackfriday.TableCell { |
253 if node.Prev != nil && node.Prev.Type == blackfriday.TableCell { |
273 out(w, "\t"+start) |
254 start = "\t" |
274 } else { |
255 } |
275 out(w, start) |
256 if node.IsHeader { |
276 } |
257 start += codespanTag |
|
258 } else if nodeLiteralSize(node) > 30 { |
|
259 start += tableCellStart |
|
260 } |
|
261 out(w, start) |
277 } else { |
262 } else { |
278 // need to carriage return if we are at the end of the header row |
263 var end string |
279 if node.IsHeader && node.Next == nil { |
264 if node.IsHeader { |
280 end = end + crTag |
265 end = codespanCloseTag |
|
266 } else if nodeLiteralSize(node) > 30 { |
|
267 end = tableCellEnd |
|
268 } |
|
269 if node.Next == nil && end != tableCellEnd { |
|
270 // Last cell: need to carriage return if we are at the end of the |
|
271 // header row and content isn't wrapped in a "tablecell" |
|
272 end += crTag |
281 } |
273 } |
282 out(w, end) |
274 out(w, end) |
283 } |
275 } |
|
276 } |
|
277 |
|
278 func nodeLiteralSize(node *blackfriday.Node) int { |
|
279 total := 0 |
|
280 for n := node.FirstChild; n != nil; n = n.FirstChild { |
|
281 total += len(n.Literal) |
|
282 } |
|
283 return total |
284 } |
284 } |
285 |
285 |
286 // because roff format requires knowing the column count before outputting any table |
286 // because roff format requires knowing the column count before outputting any table |
287 // data we need to walk a table tree and count the columns |
287 // data we need to walk a table tree and count the columns |
288 func countColumns(node *blackfriday.Node) int { |
288 func countColumns(node *blackfriday.Node) int { |
307 |
307 |
308 func out(w io.Writer, output string) { |
308 func out(w io.Writer, output string) { |
309 io.WriteString(w, output) // nolint: errcheck |
309 io.WriteString(w, output) // nolint: errcheck |
310 } |
310 } |
311 |
311 |
312 func needsBackslash(c byte) bool { |
|
313 for _, r := range []byte("-_&\\~") { |
|
314 if c == r { |
|
315 return true |
|
316 } |
|
317 } |
|
318 return false |
|
319 } |
|
320 |
|
321 func escapeSpecialChars(w io.Writer, text []byte) { |
312 func escapeSpecialChars(w io.Writer, text []byte) { |
322 for i := 0; i < len(text); i++ { |
313 for i := 0; i < len(text); i++ { |
323 // escape initial apostrophe or period |
314 // escape initial apostrophe or period |
324 if len(text) >= 1 && (text[0] == '\'' || text[0] == '.') { |
315 if len(text) >= 1 && (text[0] == '\'' || text[0] == '.') { |
325 out(w, "\\&") |
316 out(w, "\\&") |
326 } |
317 } |
327 |
318 |
328 // directly copy normal characters |
319 // directly copy normal characters |
329 org := i |
320 org := i |
330 |
321 |
331 for i < len(text) && !needsBackslash(text[i]) { |
322 for i < len(text) && text[i] != '\\' { |
332 i++ |
323 i++ |
333 } |
324 } |
334 if i > org { |
325 if i > org { |
335 w.Write(text[org:i]) // nolint: errcheck |
326 w.Write(text[org:i]) // nolint: errcheck |
336 } |
327 } |