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