3510b64e1fef70e466e7e903a622581c730dc95b
[nit.git] / src / web / api_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 # Nitdoc specific Markdown format handling for Nitweb
16 module api_docdown
17
18 import api_graph
19 intrude import doc_down
20 intrude import markdown::wikilinks
21 import doc_commands
22
23 # Docdown handler accept docdown as POST data and render it as HTML
24 class APIDocdown
25 super APIHandler
26
27 # Modelbuilder used by the commands
28 var modelbuilder: ModelBuilder
29
30 # Specific Markdown processor to use within Nitweb
31 var md_processor: MarkdownProcessor is lazy do
32 var proc = new MarkdownProcessor
33 proc.emitter.decorator = new NitwebDecorator(view, modelbuilder)
34 return proc
35 end
36
37 redef fun post(req, res) do
38 res.html md_processor.process(req.body)
39 end
40 end
41
42 # Specific Markdown decorator for Nitweb
43 #
44 # We reuse all the implementation of the NitdocDecorator and add the wikilinks handling.
45 class NitwebDecorator
46 super NitdocDecorator
47
48 # View used by wikilink commands to find model entities
49 var view: ModelView
50
51 # Modelbuilder used to access code
52 var modelbuilder: ModelBuilder
53
54 redef fun add_wikilink(v, token) do
55 var link = token.link
56 if link == null then return
57 var cmd = new DocCommand(link.write_to_string)
58 cmd.render(v, token, view)
59 end
60 end
61
62 # Same as `InlineDecorator` but with wikilink commands handling
63 class NitwebInlineDecorator
64 super InlineDecorator
65
66 # View used by wikilink commands to find model entities
67 var view: ModelView
68
69 # Modelbuilder used to access code
70 var modelbuilder: ModelBuilder
71
72 redef fun add_wikilink(v, token) do
73 var link = token.link
74 if link == null then return
75 var cmd = new DocCommand(link.write_to_string)
76 cmd.render(v, token, view)
77 end
78 end
79
80 redef interface DocCommand
81
82 # Emit the HTML related to the execution of this doc command
83 fun render(v: MarkdownEmitter, token: TokenWikiLink, model: ModelView) do
84 write_error(v, "Not yet implemented command `{token.link or else "null"}`")
85 end
86
87 # Find the MEntity ` with `full_name`.
88 fun find_mentity(model: ModelView, full_name: nullable String): nullable MEntity do
89 if full_name == null then return null
90 return model.mentity_by_full_name(full_name.from_percent_encoding)
91 end
92
93 # Write a warning in the output
94 fun write_warning(v: MarkdownEmitter, text: String) do
95 v.emit_text "<p class='text-warning'>Warning: {text}</p>"
96 end
97
98 # Write an error in the output
99 fun write_error(v: MarkdownEmitter, text: String) do
100 v.emit_text "<p class='text-danger'>Error: {text}</p>"
101 end
102
103 # Write a link to a mentity in the output
104 fun write_mentity_link(v: MarkdownEmitter, mentity: MEntity) do
105 var link = mentity.web_url
106 var name = mentity.name
107 var mdoc = mentity.mdoc_or_fallback
108 var comment = null
109 if mdoc != null then comment = mdoc.synopsis
110 v.decorator.add_link(v, link, name, comment)
111 end
112 end
113
114 redef class UnknownCommand
115 redef fun render(v, token, model) do
116 var link = token.link
117 if link == null then
118 write_error(v, "Empty command")
119 return
120 end
121 var full_name = link.write_to_string
122 var mentity = find_mentity(model, full_name)
123 if mentity == null then
124 write_error(v, "Unknown command `{link}`")
125 return
126 end
127 write_mentity_link(v, mentity)
128 end
129 end
130
131 redef class ArticleCommand
132 redef fun render(v, token, model) do
133 if args.is_empty then
134 write_error(v, "Expected one arg: the MEntity name")
135 return
136 end
137 var name = args.first
138 var mentity = find_mentity(model, name)
139 if mentity == null then
140 write_error(v, "No MEntity found for name `{name}`")
141 return
142 end
143 var mdoc = mentity.mdoc_or_fallback
144 if mdoc == null then
145 write_warning(v, "No MDoc for mentity `{name}`")
146 return
147 end
148 v.add "<h3>"
149 write_mentity_link(v, mentity)
150 v.add " - "
151 v.emit_text mdoc.synopsis
152 v.add "</h3>"
153 v.add v.processor.process(mdoc.comment).write_to_string
154 end
155 end
156
157 redef class CommentCommand
158 redef fun render(v, token, model) do
159 if args.is_empty then
160 write_error(v, "Expected one arg: the MEntity name")
161 return
162 end
163 var name = args.first
164 var mentity = find_mentity(model, name)
165 if mentity == null then
166 write_error(v, "No MEntity found for name `{name}`")
167 return
168 end
169 var mdoc = mentity.mdoc_or_fallback
170 if mdoc == null then
171 write_warning(v, "No MDoc for mentity `{name}`")
172 return
173 end
174 v.add v.processor.process(mdoc.comment).write_to_string
175 end
176 end
177
178 redef class ListCommand
179 redef fun render(v, token, model) do
180 if args.is_empty then
181 write_error(v, "Expected one arg: the MEntity name")
182 return
183 end
184 var name = args.first
185 var mentity = find_mentity(model, name)
186 if mentity isa MPackage then
187 write_list(v, mentity.mgroups)
188 else if mentity isa MGroup then
189 var res = new Array[MEntity]
190 res.add_all mentity.in_nesting.smallers
191 res.add_all mentity.mmodules
192 write_list(v, res)
193 else if mentity isa MModule then
194 write_list(v, mentity.mclassdefs)
195 else if mentity isa MClass then
196 write_list(v, mentity.collect_intro_mproperties(model))
197 else if mentity isa MClassDef then
198 write_list(v, mentity.mpropdefs)
199 else if mentity isa MProperty then
200 write_list(v, mentity.mpropdefs)
201 else
202 write_error(v, "No list found for name `{name}`")
203 end
204 end
205
206 # Write a mentity list in the output
207 fun write_list(v: MarkdownEmitter, mentities: Collection[MEntity]) do
208 v.add "<ul>"
209 for mentity in mentities do
210 var mdoc = mentity.mdoc_or_fallback
211 v.add "<li>"
212 write_mentity_link(v, mentity)
213 if mdoc != null then
214 v.add " - "
215 v.emit_text mdoc.synopsis
216 end
217 v.add "</li>"
218 end
219 v.add "</ul>"
220 end
221 end
222
223 redef class CodeCommand
224 redef fun render(v, token, model) do
225 if args.is_empty then
226 write_error(v, "Expected one arg: the MEntity name")
227 return
228 end
229 var name = args.first
230 var mentity = find_mentity(model, name)
231 if mentity == null then
232 write_error(v, "No MEntity found for name `{name}`")
233 return
234 end
235 if mentity isa MClass then mentity = mentity.intro
236 if mentity isa MProperty then mentity = mentity.intro
237 var source = render_source(mentity, v.decorator.as(NitwebDecorator).modelbuilder)
238 if source == null then
239 write_error(v, "No source for MEntity `{name}`")
240 return
241 end
242 v.add "<pre>"
243 v.add source
244 v.add "</pre>"
245 end
246
247 # Highlight `mentity` source code.
248 private fun render_source(mentity: MEntity, modelbuilder: ModelBuilder): nullable HTMLTag do
249 var node = modelbuilder.mentity2node(mentity)
250 if node == null then return null
251 var hl = new HighlightVisitor
252 hl.enter_visit node
253 return hl.html
254 end
255 end
256
257 redef class GraphCommand
258 redef fun render(v, token, model) do
259 if args.is_empty then
260 write_error(v, "Expected one arg: the MEntity name")
261 return
262 end
263 var name = args.first
264 var mentity = find_mentity(model, name)
265 if mentity == null then
266 write_error(v, "No MEntity found for name `{name}`")
267 return
268 end
269 var g = new InheritanceGraph(mentity, model)
270 v.add g.draw(3, 3).to_svg
271 end
272 end