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