02439e1dcbcfcc86a40c772c41b5cc8fbaff0f75
[nit.git] / lib / markdown2 / markdown_md_rendering.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14
15 # Markdown rendering of Markdown documents
16 module markdown_md_rendering
17
18 import markdown_rendering
19
20 # Markdown document renderer to Markdown
21 class MarkdownRenderer
22 super MdRenderer
23
24 # Markdown output under construction
25 private var md: Buffer is noinit
26
27 # Render `node` as Markdown
28 redef fun render(node) do
29 reset
30 enter_visit(node)
31 return md.write_to_string
32 end
33
34 redef fun visit(node) do node.render_md(self)
35
36 # Reset internal state
37 fun reset do
38 md = new Buffer
39 end
40
41 # Current indentation level
42 private var indent = 0
43
44 # Are we currently in a blockquote?
45 var in_quote = 0
46
47 # Add a `md` string to the output
48 fun add_raw(md: String) do self.md.append(md)
49
50 # Add a blank line to the output
51 fun add_line do add_raw "\n"
52
53 # Add an indentation depending on `ident` level
54 fun add_indent do
55 add_raw " " * indent
56 end
57 end
58
59 private class TextLengthVisitor
60 super MdVisitor
61
62 var length = 0
63
64 redef fun visit(node) do node.process_len(self)
65 end
66
67 redef class MdNode
68
69 # Render `self` as Markdown
70 fun render_md(v: MarkdownRenderer) do visit_all(v)
71
72 private fun process_len(v: TextLengthVisitor) do visit_all(v)
73 end
74
75 redef class MdDocument
76 redef fun render_md(v) do
77 var node = first_child
78 while node != null do
79 v.enter_visit(node)
80 node = node.next
81 if node != null then
82 v.add_line
83 end
84 end
85 end
86 end
87
88 # Blocks
89
90 redef class MdBlockQuote
91 redef fun render_md(v) do
92 v.in_quote += 1
93 var node = first_child
94 while node != null do
95 v.add_indent
96 v.add_raw "> "
97 v.enter_visit(node)
98 node = node.next
99 end
100 v.in_quote -= 1
101 end
102 end
103
104 redef class MdIndentedCodeBlock
105 redef fun render_md(v) do
106 var literal = self.literal
107 if literal == null then return
108
109 var lines = literal.split("\n")
110 for i in [0..lines.length[ do
111 if i == lines.length - 1 then continue
112 var line = lines[i]
113 if line.is_empty then
114 v.add_raw "\n"
115 else
116 v.add_indent
117 if use_tabs then
118 v.add_raw "\t"
119 else
120 v.add_raw " " * 4
121 end
122 v.add_raw line
123 v.add_line
124 end
125 end
126 end
127 end
128
129 redef class MdFencedCodeBlock
130 redef fun render_md(v) do
131 var info = self.info
132 v.add_indent
133 v.add_raw fence_char.to_s * fence_length
134 v.add_raw info or else ""
135 for line in (literal or else "").split("\n") do
136 v.add_line
137 if not line.is_empty then
138 v.add_indent
139 end
140 v.add_raw line
141 end
142 v.add_indent
143 v.add_raw fence_char.to_s * fence_length
144 v.add_line
145 end
146 end
147
148 redef class MdHeading
149 redef fun render_md(v) do
150 if is_setext then
151 visit_all(v)
152 var length_visitor = new TextLengthVisitor
153 length_visitor.enter_visit(self)
154 v.add_line
155 if level == 1 then
156 v.add_raw "=" * length_visitor.length
157 else
158 v.add_raw "-" * length_visitor.length
159 end
160 else
161 v.add_raw "#" * level
162 v.add_raw " "
163 visit_all(v)
164 if has_atx_trailing then
165 v.add_raw " "
166 v.add_raw "#" * level
167 end
168 end
169 v.add_line
170 end
171 end
172
173 redef class MdOrderedList
174 # Children numbering
175 private var md_numbering: Int = start_number is lazy
176 end
177
178 redef class MdListItem
179 redef fun render_md(v) do
180 var parent = self.parent
181 var is_tight = parent.as(MdListBlock).is_tight
182
183 v.add_indent
184 if parent isa MdUnorderedList then
185 v.add_raw parent.bullet_marker.to_s
186 v.indent += 2
187 else if parent isa MdOrderedList then
188 v.add_raw "{parent.md_numbering}{parent.delimiter.to_s}"
189 v.indent += 3
190 end
191
192 var node = first_child
193 if node != null then
194 v.add_raw " "
195 else
196 v.add_line
197 end
198 while node != null do
199 v.enter_visit(node)
200 node = node.next
201 if node != null and not is_tight then
202 v.add_line
203 end
204 end
205
206 if next != null and not is_tight then
207 v.add_line
208 end
209
210 if parent isa MdUnorderedList then
211 v.indent -= 2
212 else if parent isa MdOrderedList then
213 parent.md_numbering += 1
214 v.indent -= 3
215 end
216 end
217 end
218
219 redef class MdParagraph
220 redef fun render_md(v) do
221 if not parent isa MdBlockQuote and not parent isa MdListItem or prev != null then
222 v.add_indent
223 end
224 # if parent isa MdBlockQuote then
225 # v.add_raw "> "
226 # var node = first_child
227 # while node != null do
228 # v.enter_visit(node)
229 # if node isa MdSoftLineBreak or node isa MdHardLineBreak then
230 # v.add_raw "> "
231 # end
232 # node = node.next
233 # end
234 # v.add_line
235 # return
236 # end
237 visit_all(v)
238 v.add_line
239 end
240 end
241
242 redef class MdThematicBreak
243 redef fun render_md(v) do
244 v.add_raw original_pattern
245 v.add_line
246 end
247 end
248
249 redef class MdHtmlBlock
250 redef fun render_md(v) do
251 v.add_raw literal or else ""
252 v.add_line
253 end
254 end
255
256 # Inlines
257
258 redef class MdHardLineBreak
259 redef fun render_md(v) do
260 if has_backslash then
261 v.add_raw "\\"
262 else
263 v.add_raw " "
264 end
265 v.add_line
266 v.add_indent
267 v.add_raw "> " * v.in_quote
268 end
269
270 redef fun process_len(v) do
271 super
272 v.length += 1
273 end
274 end
275
276 redef class MdSoftLineBreak
277 redef fun render_md(v) do
278 v.add_line
279 v.add_indent
280 v.add_raw "> " * v.in_quote
281 end
282
283 redef fun process_len(v) do
284 super
285 v.length += 1
286 end
287 end
288
289 redef class MdCode
290 redef fun render_md(v) do
291 v.add_raw delimiter
292 v.add_raw literal
293 v.add_raw delimiter
294 end
295
296 redef fun process_len(v) do
297 super
298 v.length += delimiter.length
299 end
300 end
301
302 redef class MdDelimited
303 redef fun render_md(v) do
304 v.add_raw delimiter
305 visit_all(v)
306 v.add_raw delimiter
307 end
308
309 redef fun process_len(v) do
310 super
311 v.length += delimiter.length * 2
312 end
313 end
314
315 redef class MdHtmlInline
316 redef fun render_md(v) do
317 v.add_raw literal
318 end
319
320 redef fun process_len(v) do
321 v.length += literal.length
322 end
323 end
324
325 redef class MdLinkOrImage
326 redef fun render_md(v) do
327 var title = self.title
328 v.add_raw "["
329 visit_all(v)
330 v.add_raw "]"
331 v.add_raw "("
332 if has_brackets then
333 v.add_raw "<"
334 end
335 v.add_raw destination
336 if has_brackets then
337 v.add_raw ">"
338 end
339 if title != null and not title.is_empty then
340 v.add_raw " \""
341 v.add_raw title.replace("\"", "\\\"")
342 v.add_raw "\""
343 end
344 v.add_raw ")"
345 end
346 end
347
348
349 redef class MdImage
350 redef fun render_md(v) do
351 v.add_raw "!"
352 super
353 end
354 end
355
356 redef class MdLink
357 redef fun render_md(v) do
358 if is_autolink then
359 v.add_raw "<"
360 v.add_raw destination
361 v.add_raw ">"
362 return
363 end
364 super
365 end
366 end
367
368 redef class MdText
369 redef fun render_md(v) do
370 v.add_raw literal
371 end
372
373 redef fun process_len(v) do
374 v.length += literal.length
375 end
376 end