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