highlight: extract HTML stuff from highlight into htmlight
[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 htmlight
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 # Filter mentities based on the config view filters
74 fun filter_mentities(req: HttpRequest, mentities: Array[MEntity]): Array[MEntity] do
75 var res = new Array[MEntity]
76 for mentity in mentities do
77 if config.view.filter.accept_mentity(mentity) then res.add mentity
78 end
79 return res
80 end
81
82 # Sort mentities by lexicographic order
83 #
84 # TODO choose order from request
85 fun sort_mentities(req: HttpRequest, mentities: Array[MEntity]): Array[MEntity] do
86 var sorted = mentities.to_a
87 var sorter = new MEntityNameSorter
88 sorter.sort(sorted)
89 return sorted
90 end
91
92 # Limit mentities depending on the `n` parameter.
93 fun limit_mentities(req: HttpRequest, mentities: Array[MEntity]): Array[MEntity] do
94 var n = req.int_arg("n")
95 if n != null then
96 return mentities.sub(0, n)
97 end
98 return mentities
99 end
100
101 redef fun get(req, res) do
102 var mentities = list_mentities(req)
103 mentities = sort_mentities(req, mentities)
104 mentities = limit_mentities(req, mentities)
105 res.json new JsonArray.from(mentities)
106 end
107 end
108
109 # Search mentities from a query string.
110 #
111 # Example: `GET /search?q=Arr`
112 class APISearch
113 super APIList
114
115 redef fun get(req, res) do
116 var query = req.string_arg("q")
117 if query == null then
118 res.api_error(400, "Missing search string")
119 return
120 end
121 var page = req.int_arg("p")
122 var limit = req.int_arg("n")
123 var response = new JsonArray.from(search(query, limit))
124 res.json paginate(response, response.length, page, limit)
125 end
126
127 fun search(query: String, limit: nullable Int): Array[MEntity] do
128 return config.view.find(query)
129 end
130 end
131
132 # Return a random list of MEntities.
133 #
134 # Example: `GET /random?n=10&k=module`
135 class APIRandom
136 super APIList
137
138 # Randomize mentities order.
139 fun randomize_mentities(req: HttpRequest, mentities: Array[MEntity]): Array[MEntity] do
140 var res = mentities.to_a
141 res.shuffle
142 return res
143 end
144
145 redef fun get(req, res) do
146 var mentities = list_mentities(req)
147 mentities = filter_mentities(req, mentities)
148 mentities = randomize_mentities(req, mentities)
149 mentities = limit_mentities(req, mentities)
150 res.json new JsonArray.from(mentities)
151 end
152 end
153
154 # Return the JSON representation of a MEntity.
155 #
156 # Example: `GET /entity/core::Array`
157 class APIEntity
158 super APIHandler
159
160 redef fun get(req, res) do
161 var mentity = mentity_from_uri(req, res)
162 if mentity == null then return
163 res.raw_json mentity.to_full_json(config.view.mainmodule)
164 end
165 end
166
167 # Return the full MDoc of a MEntity.
168 #
169 # Example: `GET /entity/core::Array/doc`
170 class APIEntityDoc
171 super APIHandler
172
173 redef fun get(req, res) do
174 var mentity = mentity_from_uri(req, res)
175 if mentity == null then return
176
177 var obj = new JsonObject
178 var mdoc = mentity.mdoc_or_fallback
179 if mdoc != null then
180 obj["documentation"] = mdoc.html_documentation.write_to_string
181 obj["location"] = mdoc.location
182 end
183 res.json obj
184 end
185 end
186
187 # List ancestors, parents, child and descendants of MEntity
188 #
189 # Example: `GET /entity/core::Array/inheritance`
190 class APIEntityInheritance
191 super APIHandler
192
193 redef fun get(req, res) do
194 var mentity = mentity_from_uri(req, res)
195 if mentity == null then return
196 res.json mentity.hierarchy_poset(config.view)[mentity]
197 end
198 end
199
200 # Linearize super definitions of a MClassDef or a MPropDef if any.
201 #
202 # Example: `GET /entity/core::Array/linearization`
203 class APIEntityLinearization
204 super APIHandler
205
206 redef fun get(req, res) do
207 var mentity = mentity_from_uri(req, res)
208 if mentity == null then return
209 var lin = mentity.collect_linearization(config.mainmodule)
210 if lin == null then
211 res.api_error(404, "No linearization for mentity `{mentity.full_name}`")
212 return
213 end
214 var mentities = new JsonArray
215 for e in lin do mentities.add e
216 res.json mentities
217 end
218 end
219
220 # List definitions of a MEntity.
221 #
222 # Example: `GET /defs/core::Array`
223 class APIEntityDefs
224 super APIList
225
226 redef fun get(req, res) do
227 var mentity = mentity_from_uri(req, res)
228 if mentity == null then return
229 var mentities = new Array[MEntity]
230 if mentity isa MPackage then
231 mentities.add_all mentity.collect_mgroups(config.view)
232 mentities.add_all mentity.collect_mmodules(config.view)
233 else if mentity isa MGroup then
234 mentities.add_all mentity.collect_mgroups(config.view)
235 mentities.add_all mentity.collect_mmodules(config.view)
236 else if mentity isa MModule then
237 mentities.add_all mentity.collect_local_mclassdefs(config.view)
238 else if mentity isa MClass then
239 mentities.add_all mentity.collect_mclassdefs(config.view)
240 else if mentity isa MClassDef then
241 mentities.add_all mentity.collect_mpropdefs(config.view)
242 else if mentity isa MProperty then
243 mentities.add_all mentity.collect_mpropdefs(config.view)
244 else
245 res.api_error(404, "No definition list for mentity `{mentity.full_name}`")
246 return
247 end
248 mentities = filter_mentities(req, mentities)
249 mentities = sort_mentities(req, mentities)
250 mentities = limit_mentities(req, mentities)
251 res.json new JsonArray.from(mentities)
252 end
253 end
254
255 abstract class SVGHandler
256 super APIHandler
257
258 # Render a `dot` string as a svg image.
259 fun render_dot(dot: Text): String do
260 var proc = new ProcessDuplex("dot", "-Tsvg")
261 var svg = proc.write_and_read(dot)
262 proc.close
263 proc.wait
264 return svg
265 end
266 end
267
268 # Return a UML representation of MEntity.
269 #
270 # Example: `GET /entity/core::Array/uml`
271 class APIEntityUML
272 super SVGHandler
273
274 redef fun get(req, res) do
275 var mentity = mentity_from_uri(req, res)
276 if mentity == null then return
277 var dot
278 if mentity isa MClassDef then mentity = mentity.mclass
279 if mentity isa MClass then
280 var uml = new UMLModel(config.view, config.mainmodule)
281 dot = uml.generate_class_uml.write_to_string
282 else if mentity isa MModule then
283 var uml = new UMLModel(config.view, mentity)
284 dot = uml.generate_package_uml.write_to_string
285 else
286 res.api_error(404, "No UML for mentity `{mentity.full_name}`")
287 return
288 end
289 res.send render_dot(dot)
290 end
291 end
292
293 # Return the source code of MEntity.
294 #
295 # Example: `GET /entity/core::Array/code`
296 class APIEntityCode
297 super APIHandler
298
299 redef fun get(req, res) do
300 var mentity = mentity_from_uri(req, res)
301 if mentity == null then return
302 var source = render_source(mentity)
303 if source == null then
304 res.api_error(404, "No code for mentity `{mentity.full_name}`")
305 return
306 end
307 res.send source
308 end
309
310 # Highlight `mentity` source code.
311 private fun render_source(mentity: MEntity): nullable HTMLTag do
312 var node = config.modelbuilder.mentity2node(mentity)
313 if node == null then return null
314 var hl = new HtmlightVisitor
315 hl.highlight_node node
316 return hl.html
317 end
318 end