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