abf5d7d04f9264ae129b9230d763299f188cf489
[nit.git] / src / doc / commands / commands_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 # Doc commands about a Model or a MEntity
16 #
17 # This module defines several commands to retrieve data about a Model and MEntities.
18 module commands_model
19
20 import commands_base
21
22 import model::model_collect
23 import modelize
24 import modelbuilder
25 import htmlight
26 import doc_down
27
28 # Retrieve the MDoc related to a MEntity
29 class CmdComment
30 super CmdEntity
31
32 # Allow fallback
33 #
34 # If `true`, the command uses `mdoc_or_fallback`.
35 # Default is `true`.
36 var fallback = true is optional, writable
37
38 # Retrieve the full documentation
39 #
40 # If `true`, retrieves the full documentation.
41 # If `false`, retrieves only the synopsis.
42 # Default is `true`.
43 #
44 # Since the rendering the final string (md, html...) depends on the kind of
45 # client, the handling of this option is delegated to submodules.
46 var full_doc = true is optional, writable
47
48 # Format to render the comment
49 #
50 # Can be one of `raw` or `html`.
51 # Default is `raw`.
52 var format = "raw" is optional, writable
53
54 # MDoc to return
55 var mdoc: nullable MDoc = null is optional, writable
56
57 # Same states than `CmdEntity::init_mentity`
58 #
59 # Plus returns `WarningNoMDoc` if no MDoc was found for the MEntity.
60 redef fun init_command do
61 var res = super
62 if not res isa CmdSuccess then return res
63 var mentity = self.mentity.as(not null)
64
65 if mdoc == null then
66 mdoc = if fallback then mentity.mdoc_or_fallback else mentity.mdoc
67 end
68 if mdoc == null then return new WarningNoMDoc(mentity)
69 return res
70 end
71
72 # Render `mdoc` depending on `full_doc` and `format`
73 fun render_comment: nullable Writable do
74 var mdoc = self.mdoc
75 if mdoc == null then return null
76
77 if format == "html" then
78 if full_doc then return mdoc.html_documentation
79 return mdoc.html_synopsis
80 end
81 if full_doc then return mdoc.documentation
82 return mdoc.synopsis
83 end
84 end
85
86 # No MDoc for `mentity`
87 class WarningNoMDoc
88 super CmdWarning
89
90 # MEntity provided
91 var mentity: MEntity
92
93 redef fun to_s do return "No documentation for `{mentity.full_name}`."
94 end
95
96 # Get the link to a MEntity API documentation
97 class CmdEntityLink
98 super CmdEntity
99
100 # The link text to display
101 var text: nullable String = null is optional, writable
102
103 # The link title to display when the link is hovered
104 var title: nullable String = null is optional, writable
105
106 redef fun init_command do
107 var res = super
108 if not res isa CmdSuccess then return res
109 var mentity = self.mentity.as(not null)
110
111 if text == null then
112 text = mentity.name
113 end
114 if title == null then
115 var mdoc = mentity.mdoc_or_fallback
116 if mdoc != null then
117 title = mdoc.synopsis
118 end
119 end
120 return res
121 end
122 end
123
124 # An abstract inheritance command
125 #
126 # For things like ancestors, parents, children and descendants.
127 abstract class CmdInheritance
128 super CmdEntityList
129
130 autoinit(model, mainmodule, filter, mentity, mentity_name, limit, page, count, max)
131
132 # Mainmodule for class linearization
133 var mainmodule: MModule
134 end
135
136 # MEntity ancestors command
137 #
138 # Retrieve all the ancestors (direct and indirect) of a MEntity.
139 class CmdAncestors
140 super CmdInheritance
141
142 # Include direct parents in the ancestors list
143 #
144 # Default is `true`.
145 var parents = true is optional, writable
146
147 redef fun init_results do
148 if results != null then return new CmdSuccess
149
150 var res = super
151 if not res isa CmdSuccess then return res
152 var mentity = self.mentity.as(not null)
153
154 var ancestors = mentity.collect_ancestors(mainmodule, filter).to_a
155 if parents then
156 results = ancestors
157 return res
158 end
159
160 var parents = mentity.collect_parents(mainmodule, filter)
161 var mentities = new HashSet[MEntity]
162 for ancestor in ancestors do
163 if not parents.has(ancestor) then mentities.add ancestor
164 end
165 results = mentities.to_a
166 return res
167 end
168 end
169
170 # MEntity parents command
171 class CmdParents
172 super CmdInheritance
173
174 redef fun init_results do
175 if results != null then return new CmdSuccess
176
177 var res = super
178 if not res isa CmdSuccess then return res
179 var mentity = self.mentity.as(not null)
180
181 results = mentity.collect_parents(mainmodule, filter).to_a
182 return res
183 end
184 end
185
186 # MEntity children command
187 class CmdChildren
188 super CmdInheritance
189
190 redef fun init_results do
191 if results != null then return new CmdSuccess
192
193 var res = super
194 if not res isa CmdSuccess then return res
195 var mentity = self.mentity.as(not null)
196
197 results = mentity.collect_children(mainmodule, filter).to_a
198 return res
199 end
200 end
201
202 # MEntity descendants command
203 class CmdDescendants
204 super CmdInheritance
205
206 # Include direct children in the descendants list
207 #
208 # Default is `true`.
209 var children = true is optional, writable
210
211 redef fun init_results do
212 if results != null then return new CmdSuccess
213
214 var res = super
215 if not res isa CmdSuccess then return res
216 var mentity = self.mentity.as(not null)
217
218 var descendants = mentity.collect_descendants(mainmodule, filter).to_a
219 if children then
220 results = descendants
221 return res
222 end
223
224 var children = mentity.collect_children(mainmodule, filter)
225 var mentities = new HashSet[MEntity]
226 for descendant in descendants do
227 if not children.has(descendant) then mentities.add descendant
228 end
229 results = mentities.to_a
230 return res
231 end
232 end
233
234 # Linearization command
235 #
236 # Collects and linearizes definitions about an MEntity.
237 class CmdLinearization
238 super CmdInheritance
239
240 # Same states than `CmdEntity::init_mentity`
241 #
242 # Plus returns `WarningNoLinearization` if no linearization can be computed
243 # from the mentity.
244 redef fun init_results do
245 if results != null then return new CmdSuccess
246
247 var res = super
248 if not res isa CmdSuccess then return res
249 var mentity = self.mentity.as(not null)
250
251 sorter = null
252 results = mentity.collect_linearization(mainmodule)
253 if results == null then return new WarningNoLinearization(mentity)
254 return res
255 end
256 end
257
258 # No linearization computed for `mentity`.
259 class WarningNoLinearization
260 super CmdWarning
261
262 # MEntity provided
263 var mentity: MEntity
264
265 redef fun to_s do return "No linearization for `{mentity.full_name}`"
266 end
267
268 # A free text search command
269 class CmdSearch
270 super CmdEntities
271
272 # Free text command string
273 var query: nullable String = null is optional, writable
274
275 # Return states:
276 # * `CmdSuccess`: everything was ok;
277 # * `ErrorNoQuery`: no `query` provided.
278 redef fun init_results do
279 if results != null then return new CmdSuccess
280
281 var res = super
282 if not res isa CmdSuccess then return res
283
284 var query = self.query
285 if query == null then return new ErrorNoQuery
286 sorter = null
287 results = model.find(query)
288 return res
289 end
290 end
291
292 # No query string given
293 class ErrorNoQuery
294 super CmdError
295
296 redef fun to_s do return "Missing search string"
297 end
298
299 # MEntity feature list
300 #
301 # Mostly a list of mentities defined in `mentity`.
302 class CmdFeatures
303 super CmdEntityList
304
305 # Same as `CmdEntity::init_mentity`
306 #
307 # Plus `WarningNoFeatures` if no features are found for `mentity`.
308 redef fun init_results do
309 if results != null then return new CmdSuccess
310
311 var res = super
312 if not res isa CmdSuccess then return res
313 var mentity = self.mentity.as(not null)
314
315 var mentities = new Array[MEntity]
316 if mentity isa MPackage then
317 mentities.add_all mentity.collect_mgroups(filter)
318 mentities.add_all mentity.collect_mmodules(filter)
319 else if mentity isa MGroup then
320 mentities.add_all mentity.collect_mgroups(filter)
321 mentities.add_all mentity.collect_mmodules(filter)
322 else if mentity isa MModule then
323 mentities.add_all mentity.collect_local_mclassdefs(filter)
324 else if mentity isa MClass then
325 mentities.add_all mentity.collect_intro_mproperties(filter)
326 mentities.add_all mentity.collect_redef_mpropdefs(filter)
327 else if mentity isa MClassDef then
328 mentities.add_all mentity.collect_intro_mpropdefs(filter)
329 mentities.add_all mentity.collect_redef_mpropdefs(filter)
330 else if mentity isa MProperty then
331 mentities.add_all mentity.collect_mpropdefs(filter)
332 else
333 return new WarningNoFeatures(mentity)
334 end
335 self.results = mentities
336 return res
337 end
338 end
339
340 # TODO remove once the filters/sorters are merged
341 class CmdIntros
342 super CmdInheritance
343
344 redef fun init_results do
345 if results != null then return new CmdSuccess
346
347 var res = super
348 if not res isa CmdSuccess then return res
349 var mentity = self.mentity.as(not null)
350
351 if mentity isa MModule then
352 var mentities = mentity.collect_intro_mclasses(filter).to_a
353 self.results = mentities
354 else if mentity isa MClass then
355 var mentities = mentity.collect_intro_mproperties(filter).to_a
356 self.results = mentities
357 else if mentity isa MClassDef then
358 var mentities = mentity.collect_intro_mpropdefs(filter).to_a
359 mainmodule.linearize_mpropdefs(mentities)
360 self.results = mentities
361 else
362 return new WarningNoFeatures(mentity)
363 end
364 return res
365 end
366 end
367
368 # TODO remove once the filters/sorters are merged
369 class CmdRedefs
370 super CmdInheritance
371
372 redef fun init_results do
373 if results != null then return new CmdSuccess
374
375 var res = super
376 if not res isa CmdSuccess then return res
377 var mentity = self.mentity.as(not null)
378
379 if mentity isa MModule then
380 var mentities = mentity.collect_redef_mclassdefs(filter).to_a
381 self.results = mentities
382 else if mentity isa MClass then
383 var mentities = mentity.collect_redef_mpropdefs(filter).to_a
384 self.results = mentities
385 else if mentity isa MClassDef then
386 var mentities = mentity.collect_redef_mpropdefs(filter).to_a
387 mainmodule.linearize_mpropdefs(mentities)
388 self.results = mentities
389 else
390 return new WarningNoFeatures(mentity)
391 end
392 return res
393 end
394 end
395
396 # TODO remove once the filters/sorters are merged
397 class CmdAllProps
398 super CmdInheritance
399
400 redef fun init_results do
401 if results != null then return new CmdSuccess
402
403 var res = super
404 if not res isa CmdSuccess then return res
405 var mentity = self.mentity.as(not null)
406
407 if mentity isa MClass then
408 results = mentity.collect_accessible_mproperties(mainmodule, filter).to_a
409 else
410 return new WarningNoFeatures(mentity)
411 end
412 return res
413 end
414 end
415
416 # No feature list for `mentity`
417 class WarningNoFeatures
418 super CmdWarning
419
420 # MEntity provided
421 var mentity: MEntity
422
423 redef fun to_s do return "No features for `{mentity.full_name}`"
424 end
425
426 # Abstract command that returns source-code pieces
427 abstract class CmdCode
428 super DocCommand
429
430 autoinit(model, modelbuilder, filter, format)
431
432 # ModelBuilder used to get AST nodes
433 var modelbuilder: ModelBuilder
434
435 # Rendering format
436 #
437 # Set the output format for this piece of code.
438 # Can be "raw", "html" or "ansi".
439 # Default is "raw".
440 #
441 # This format can be different than the format used in the command response.
442 # For example you can choose to render code as HTML inside a JSON object response.
443 # Another example is to render raw format to put into a HTML code tag.
444 var format = "raw" is optional, writable
445
446 # Render `node` depending on the selected `format`
447 fun render_code(node: nullable ANode): nullable Writable do
448 if node == null then return null
449 if format == "html" then
450 var hl = new CmdHtmlightVisitor
451 hl.show_infobox = false
452 hl.highlight_node node
453 return hl.html
454 else if format == "ansi" then
455 var hl = new AnsiHighlightVisitor
456 hl.highlight_node node
457 return hl.result
458 end
459 return node.location.text
460 end
461 end
462
463 # Custom HtmlightVisitor for commands
464 #
465 # We create a new subclass so its behavior can be refined in clients without
466 # breaking the main implementation.
467 class CmdHtmlightVisitor
468 super HtmlightVisitor
469 end
470
471 # Cmd that finds the source code related to an `mentity`
472 class CmdEntityCode
473 super CmdEntity
474 super CmdCode
475
476 autoinit(model, modelbuilder, filter, mentity, mentity_name, format)
477
478 # AST node to return
479 var node: nullable ANode = null is optional, writable
480
481 # Same as `CmdEntity::init_mentity`
482 #
483 # Plus `WarningNoCode` if no code/AST node is found for `mentity`.
484 redef fun init_command do
485 if node != null then return new CmdSuccess
486
487 var res = super
488 if not res isa CmdSuccess then return res
489 var mentity = self.mentity.as(not null)
490
491 if mentity isa MClass then mentity = mentity.intro
492 if mentity isa MProperty then mentity = mentity.intro
493 node = modelbuilder.mentity2node(mentity)
494 if node == null then return new WarningNoCode(mentity)
495 return res
496 end
497 end
498
499 # No code for `mentity`
500 class WarningNoCode
501 super CmdWarning
502
503 # MEntity provided
504 var mentity: MEntity
505
506 redef fun to_s do return "No code for `{mentity.full_name}`"
507 end
508
509 # Model commands
510
511 # A command that returns a list of all mentities in a model
512 class CmdModelEntities
513 super CmdEntities
514
515 # Kind of mentities to be returned.
516 #
517 # Value must be one of "packages", "groups", "modules", "classes", "classdefs",
518 # "properties", "propdefs" or "all".
519 #
520 # Default is "all".
521 var kind = "all" is optional, writable
522
523 # Default limit is `10`
524 redef var limit = 10
525
526 redef fun init_results do
527 if results != null then return new CmdSuccess
528
529 var res = super
530 if not res isa CmdSuccess then return res
531
532 var mentities = new Array[MEntity]
533 if kind == "packages" then
534 mentities = model.collect_mpackages(filter).to_a
535 else if kind == "groups" then
536 mentities = model.collect_mgroups(filter).to_a
537 else if kind == "modules" then
538 mentities = model.collect_mmodules(filter).to_a
539 else if kind == "classes" then
540 mentities = model.collect_mclasses(filter).to_a
541 else if kind == "classdefs" then
542 mentities = model.collect_mclassdefs(filter).to_a
543 else if kind == "properties" then
544 mentities = model.collect_mproperties(filter).to_a
545 else if kind == "propdefs" then
546 mentities = model.collect_mpropdefs(filter).to_a
547 else
548 mentities = model.collect_mentities(filter).to_a
549 end
550 results = mentities
551 return res
552 end
553 end
554
555 # A command that returns a random list of mentities from a model
556 class CmdRandomEntities
557 super CmdModelEntities
558
559 # Always return `CmdSuccess`
560 redef fun init_results do
561 if results != null then return new CmdSuccess
562 var res = super
563 if not res isa CmdSuccess then return res
564 randomize
565 return res
566 end
567
568 # Randomize mentities order
569 fun randomize do
570 var results = self.results
571 if results == null then return
572 results.shuffle
573 end
574 end