nitweb: use model_collect for definitions lists
[nit.git] / src / web / api_model.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 module api_model
16
17 import web_base
18 import highlight
19 import uml
20 import model::model_index
21
22 redef class APIRouter
23 redef init do
24 super
25 use("/list", new APIList(config))
26 use("/search", new APISearch(config))
27 use("/random", new APIRandom(config))
28 use("/entity/:id", new APIEntity(config))
29 use("/entity/:id/doc", new APIEntityDoc(config))
30 use("/code/:id", new APIEntityCode(config))
31 use("/uml/:id", new APIEntityUML(config))
32 use("/linearization/:id", new APIEntityLinearization(config))
33 use("/defs/:id", new APIEntityDefs(config))
34 use("/inheritance/:id", new APIEntityInheritance(config))
35 end
36 end
37
38 # List all mentities.
39 #
40 # MEntities can be filtered on their kind using the `k` parameter.
41 # Allowed kinds are `package`, `group`, `module`, `class`, `classdef`, `property`, `propdef`.
42 #
43 # List size can be limited with the `n` parameter.
44 #
45 # Example: `GET /list?k=module?n=10`
46 class APIList
47 super APIHandler
48
49 # List mentities depending on the `k` kind parameter.
50 fun list_mentities(req: HttpRequest): Array[MEntity] do
51 var k = req.string_arg("k")
52 var mentities = new Array[MEntity]
53 if k == "package" then
54 for mentity in config.view.mpackages do mentities.add mentity
55 else if k == "group" then
56 for mentity in config.view.mgroups do mentities.add mentity
57 else if k == "module" then
58 for mentity in config.view.mmodules do mentities.add mentity
59 else if k == "class" then
60 for mentity in config.view.mclasses do mentities.add mentity
61 else if k == "classdef" then
62 for mentity in config.view.mclassdefs do mentities.add mentity
63 else if k == "property" then
64 for mentity in config.view.mproperties do mentities.add mentity
65 else if k == "propdef" then
66 for mentity in config.view.mpropdefs do mentities.add mentity
67 else
68 for mentity in config.view.mentities do mentities.add mentity
69 end
70 return mentities
71 end
72
73 # Sort mentities by lexicographic order
74 #
75 # TODO choose order from request
76 fun sort_mentities(req: HttpRequest, mentities: Array[MEntity]) : Array[MEntity] do
77 var sorted = mentities.to_a
78 var sorter = new MEntityNameSorter
79 sorter.sort(sorted)
80 return sorted
81 end
82
83 # Limit mentities depending on the `n` parameter.
84 fun limit_mentities(req: HttpRequest, mentities: Array[MEntity]): Array[MEntity] do
85 var n = req.int_arg("n")
86 if n != null then
87 return mentities.sub(0, n)
88 end
89 return mentities
90 end
91
92 redef fun get(req, res) do
93 var mentities = list_mentities(req)
94 mentities = sort_mentities(req, mentities)
95 mentities = limit_mentities(req, mentities)
96 res.json new JsonArray.from(mentities)
97 end
98 end
99
100 # Search mentities from a query string.
101 #
102 # Example: `GET /search?q=Arr`
103 class APISearch
104 super APIList
105
106 redef fun get(req, res) do
107 var query = req.string_arg("q")
108 if query == null then
109 res.api_error(400, "Missing search string")
110 return
111 end
112 var page = req.int_arg("p")
113 var limit = req.int_arg("n")
114 var response = new JsonArray.from(search(query, limit))
115 res.json paginate(response, response.length, page, limit)
116 end
117
118 fun search(query: String, limit: nullable Int): Array[MEntity] do
119 return config.view.find(query)
120 end
121 end
122
123 # Return a random list of MEntities.
124 #
125 # Example: `GET /random?n=10&k=module`
126 class APIRandom
127 super APIList
128
129 # Randomize mentities order.
130 fun randomize_mentities(req: HttpRequest, mentities: Array[MEntity]): Array[MEntity] do
131 var res = mentities.to_a
132 res.shuffle
133 return res
134 end
135
136 redef fun get(req, res) do
137 var mentities = list_mentities(req)
138 mentities = randomize_mentities(req, mentities)
139 mentities = limit_mentities(req, mentities)
140 res.json new JsonArray.from(mentities)
141 end
142 end
143
144 # Return the JSON representation of a MEntity.
145 #
146 # Example: `GET /entity/core::Array`
147 class APIEntity
148 super APIHandler
149
150 redef fun get(req, res) do
151 var mentity = mentity_from_uri(req, res)
152 if mentity == null then return
153 res.raw_json mentity.to_full_json
154 end
155 end
156
157 # Return the full MDoc of a MEntity.
158 #
159 # Example: `GET /entity/core::Array/doc`
160 class APIEntityDoc
161 super APIHandler
162
163 redef fun get(req, res) do
164 var mentity = mentity_from_uri(req, res)
165 if mentity == null then return
166
167 var obj = new JsonObject
168 var mdoc = mentity.mdoc_or_fallback
169 if mdoc != null then
170 obj["documentation"] = mdoc.html_documentation.write_to_string
171 obj["location"] = mdoc.location
172 end
173 res.json obj
174 end
175 end
176
177 # List ancestors, parents, child and descendants of MEntity
178 #
179 # Example: `GET /entity/core::Array/inheritance`
180 class APIEntityInheritance
181 super APIHandler
182
183 redef fun get(req, res) do
184 var mentity = mentity_from_uri(req, res)
185 if mentity == null then return
186 res.json mentity.hierarchy_poset(config.view)[mentity]
187 end
188 end
189
190 # Linearize super definitions of a MClassDef or a MPropDef if any.
191 #
192 # Example: `GET /entity/core::Array/linearization`
193 class APIEntityLinearization
194 super APIHandler
195
196 redef fun get(req, res) do
197 var mentity = mentity_from_uri(req, res)
198 if mentity == null then return
199 var lin = mentity.collect_linearization(config.mainmodule)
200 if lin == null then
201 res.api_error(404, "No linearization for mentity `{mentity.full_name}`")
202 return
203 end
204 var mentities = new JsonArray
205 for e in lin do mentities.add e
206 res.json mentities
207 end
208 end
209
210 # List definitions of a MEntity.
211 #
212 # Example: `GET /defs/core::Array`
213 class APIEntityDefs
214 super APIList
215
216 redef fun get(req, res) do
217 var mentity = mentity_from_uri(req, res)
218 if mentity == null then return
219 var mentities = new Array[MEntity]
220 if mentity isa MPackage then
221 mentities.add_all mentity.collect_mgroups(config.view)
222 mentities.add_all mentity.collect_mmodules(config.view)
223 else if mentity isa MGroup then
224 mentities.add_all mentity.collect_mgroups(config.view)
225 mentities.add_all mentity.collect_mmodules(config.view)
226 else if mentity isa MModule then
227 mentities.add_all mentity.collect_local_mclassdefs(config.view)
228 else if mentity isa MClass then
229 mentities.add_all mentity.collect_mclassdefs(config.view)
230 else if mentity isa MClassDef then
231 mentities.add_all mentity.collect_mpropdefs(config.view)
232 else if mentity isa MProperty then
233 mentities.add_all mentity.collect_mpropdefs(config.view)
234 else
235 res.api_error(404, "No definition list for mentity `{mentity.full_name}`")
236 return
237 end
238 mentities = sort_mentities(req, mentities)
239 mentities = limit_mentities(req, mentities)
240 res.json new JsonArray.from(mentities)
241 end
242 end
243
244 abstract class SVGHandler
245 super APIHandler
246
247 # Render a `dot` string as a svg image.
248 fun render_dot(dot: Text): String do
249 var proc = new ProcessDuplex("dot", "-Tsvg")
250 var svg = proc.write_and_read(dot)
251 proc.close
252 proc.wait
253 return svg
254 end
255 end
256
257 # Return a UML representation of MEntity.
258 #
259 # Example: `GET /entity/core::Array/uml`
260 class APIEntityUML
261 super SVGHandler
262
263 redef fun get(req, res) do
264 var mentity = mentity_from_uri(req, res)
265 if mentity == null then return
266 var dot
267 if mentity isa MClassDef then mentity = mentity.mclass
268 if mentity isa MClass then
269 var uml = new UMLModel(config.view, config.mainmodule)
270 dot = uml.generate_class_uml.write_to_string
271 else if mentity isa MModule then
272 var uml = new UMLModel(config.view, mentity)
273 dot = uml.generate_package_uml.write_to_string
274 else
275 res.api_error(404, "No UML for mentity `{mentity.full_name}`")
276 return
277 end
278 res.send render_dot(dot)
279 end
280 end
281
282 # Return the source code of MEntity.
283 #
284 # Example: `GET /entity/core::Array/code`
285 class APIEntityCode
286 super APIHandler
287
288 redef fun get(req, res) do
289 var mentity = mentity_from_uri(req, res)
290 if mentity == null then return
291 var source = render_source(mentity)
292 if source == null then
293 res.api_error(404, "No code for mentity `{mentity.full_name}`")
294 return
295 end
296 res.send source
297 end
298
299 # Highlight `mentity` source code.
300 private fun render_source(mentity: MEntity): nullable HTMLTag do
301 var node = config.modelbuilder.mentity2node(mentity)
302 if node == null then return null
303 var hl = new HighlightVisitor
304 hl.enter_visit node
305 return hl.html
306 end
307 end