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