src: rename old `markdown` module in `docdown` to avoid merge conflicts
[nit.git] / src / docdown.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 # Transform Nit verbatim documentation into HTML
16 module docdown
17
18 private import parser
19 import html
20 private import highlight
21 private import parser_util
22
23 # The class that does the convertion from a `ADoc` to HTML
24 private class Doc2Mdwn
25 var toolcontext: ToolContext
26
27 # The lines of the current code block, empty is no current code block
28 var curblock = new Array[String]
29
30 # Count empty lines between code blocks
31 var empty_lines = 0
32
33 fun work(mdoc: MDoc): HTMLTag
34 do
35 var root = new HTMLTag("div")
36 root.add_class("nitdoc")
37
38 # Indent level of the previous line
39 var lastindent = 0
40
41 # Indent level of the current line
42 var indent = 0
43
44 # Expected fencing closing tag (if any)
45 var in_fence: nullable String = null
46
47 # The current element (p, li, etc.) if any
48 var n: nullable HTMLTag = null
49
50 # The current ul element (if any)
51 var ul: nullable HTMLTag = null
52
53 var is_first_line = true
54 # Local variable to benefit adaptive typing
55 for text in mdoc.content do
56 # Count the number of spaces
57 lastindent = indent
58 indent = 0
59 while text.length > indent and text.chars[indent] == ' ' do indent += 1
60
61 # In a fence
62 if in_fence != null then
63 # fence closing
64 if text.substring(0,in_fence.length) == in_fence then
65 close_codeblock(n or else root)
66 in_fence = null
67 continue
68 end
69 # else fence content
70 curblock.add(text)
71 continue
72 end
73
74 # Is codeblock? Then just collect them
75 if indent >= 3 then
76 for i in [0..empty_lines[ do curblock.add("")
77 empty_lines = 0
78 # to allows 4 spaces including the one that follows the #
79 curblock.add(text)
80 continue
81 end
82
83 # fence opening
84 if text.substring(0,3) == "~~~" then
85 # Was a codblock just before the current line ?
86 close_codeblock(n or else root)
87
88 var l = 3
89 while l < text.length and text.chars[l] == '~' do l += 1
90 in_fence = text.substring(0, l)
91 continue
92 end
93
94 # Cleanup the string
95 text = text.trim
96
97 # The HTML node of the last line, so we know if we continue the same block
98 var old = n
99
100 # No line or loss of indentation: reset
101 if text.is_empty or indent < lastindent then
102 n = null
103 ul = null
104 if text.is_empty then
105 if not curblock.is_empty then empty_lines += 1
106 continue
107 end
108 end
109
110 # Was a codblock just before the current line ?
111 close_codeblock(n or else root)
112
113 # Special first word: new paragraph
114 if text.has_prefix("TODO") or text.has_prefix("FIXME") then
115 n = new HTMLTag("p")
116 root.add n
117 n.add_class("todo")
118 ul = null
119 else if text.has_prefix("REQUIRE") or text.has_prefix("Require") or text.has_prefix("ENSURE") or text.has_prefix("Ensure") then
120 n = new HTMLTag("p")
121 root.add n
122 n.add_class("contract")
123 ul = null
124 end
125
126 # List
127 if text.has_prefix("* ") or text.has_prefix("- ") then
128 text = text.substring_from(1).trim
129 if ul == null then
130 ul = new HTMLTag("ul")
131 root.add ul
132 end
133 n = new HTMLTag("li")
134 ul.add(n)
135 end
136
137 # Nothing? then paragraph
138 if n == null then
139 n = new HTMLTag("p")
140 root.add n
141 ul = null
142 end
143
144 if old == n then
145 # Because spaces and `\n` where trimmed
146 n.append("\n")
147 end
148
149 process_line(n, text)
150
151 # Special case, the fist line is the synopsys and is in its own paragraph
152 if is_first_line then
153 n.add_class("synopsys")
154 n = null
155 is_first_line = false
156 end
157 end
158
159 # If the codeblock was the last code sequence
160 close_codeblock(n or else root)
161
162 return root
163 end
164
165 fun short_work(mdoc: MDoc): HTMLTag
166 do
167 var text = mdoc.content.first
168 var n = new HTMLTag("span")
169 n.add_class("synopsys")
170 n.add_class("nitdoc")
171 process_line(n, text)
172 return n
173 end
174
175 fun process_line(n: HTMLTag, text: String)
176 do
177 # Loosly detect code parts
178 var parts = text.split("`")
179
180 # Process each code parts, thus alternate between text and code
181 var is_text = true
182 for part in parts do
183 if is_text then
184 # Text part
185 n.append part
186 else
187 # Code part
188 var n2 = new HTMLTag("code")
189 n.add(n2)
190 process_code(n2, part)
191 end
192 is_text = not is_text
193 end
194 end
195
196 fun close_codeblock(root: HTMLTag)
197 do
198 # Is there a codeblock to manage?
199 if not curblock.is_empty then
200 empty_lines = 0
201
202 # determine the smalest indent
203 var minindent = -1
204 for text in curblock do
205 var indent = 0
206 while indent < text.length and text.chars[indent] == ' ' do indent += 1
207 # skip white lines
208 if indent >= text.length then continue
209 if minindent == -1 or indent < minindent then
210 minindent = indent
211 end
212 end
213 if minindent < 0 then minindent = 0
214
215 # Generate the text
216 var btext = new FlatBuffer
217 for text in curblock do
218 btext.append text.substring_from(minindent)
219 btext.add '\n'
220 end
221
222 # add the node
223 var n = new HTMLTag("pre")
224 root.add(n)
225 process_code(n, btext.to_s)
226 curblock.clear
227 end
228 end
229
230 fun process_code(n: HTMLTag, text: String)
231 do
232 # Try to parse it
233 var ast = toolcontext.parse_something(text)
234
235 if ast isa AError then
236 n.append text
237 # n.attrs["title"] = ast.message
238 n.add_class("rawcode")
239 else
240 var v = new HighlightVisitor
241 v.enter_visit(ast)
242 n.add(v.html)
243 n.add_class("nitcode")
244 end
245 end
246 end
247
248 redef class MDoc
249 # Build a `<div>` element that contains the full documentation in HTML
250 fun full_markdown: HTMLTag
251 do
252 var res = full_markdown_cache
253 if res != null then return res
254 var tc = new ToolContext
255 var d2m = new Doc2Mdwn(tc)
256 res = d2m.work(self)
257 full_markdown_cache = res
258 return res
259 end
260
261 private var full_markdown_cache: nullable HTMLTag
262
263 # Build a `<span>` element that contains the synopsys in HTML
264 fun short_markdown: HTMLTag
265 do
266 var res = short_markdown_cache
267 if res != null then return res
268 var tc = new ToolContext
269 var d2m = new Doc2Mdwn(tc)
270 res = d2m.short_work(self)
271 short_markdown_cache = res
272 return res
273 end
274
275 private var short_markdown_cache: nullable HTMLTag
276 end