Merge: src/doc/commands: clean commands hierarchy
[nit.git] / src / doc / commands / commands_base.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 # Documentation commands
16 #
17 # A DocCommand returns data about a model, an entity or a piece of documentation.
18 #
19 # Each command assumes a different goal like getting the comment of an entity,
20 # getting a list of packages, getting an UML class diagram etc.
21 #
22 # Commands are used by documentation tools to build up documentation ressources
23 # like Nitweb, Nitx, Nitdoc or documentation cards within READMEs.
24 module commands_base
25
26 import model::model_index
27
28 # Documentation command
29 #
30 # An abstract command that works on a Model.
31 #
32 # Since they are used by a wide variety of clients, initialization of DocCommands
33 # works in two steps.
34 #
35 # First, you pass the data you already have to the command at init:
36 # ~~~nitish
37 # var c1 = new CmdEntity(view, mentity_name = "Array")
38 # var c2 = new CmdEntity(view, mentity = my_entity)
39 # ~~~
40 #
41 # Then, you call `init_command` to initialize the missing field from the stub data:
42 # ~~~nitish
43 # var r1 = c1.init_command
44 # assert c1.mentity != null
45 # assert r1 isa CmdSuccess
46 #
47 # var r2 = c2.init_command
48 # assert c2.mentity_name != null
49 # assert r2 isa CmdSuccess
50 # ~~~
51 #
52 # See `init_command` for more details about the returned statuses.
53 abstract class DocCommand
54
55 # Model to retrieve data for
56 var model: Model
57
58 # ModelFilter to apply if any
59 var filter: nullable ModelFilter
60
61 # Initialize the command
62 #
63 # Returns a command message that gives the status of the command initialization.
64 #
65 # There is 3 categories of messages:
66 # * `CmdSuccess`: when the command that initialized correctly;
67 # * `CmdError`: when the command cannot be initialized;
68 # * `CmdWarning`: when something is wrong with the command but a result still can be produced.
69 #
70 # Warnings are generally used to distinguish empty list or mdoc from no data at all.
71 fun init_command: CmdMessage do return new CmdSuccess
72
73 # Return a new filter for that command execution.
74 fun cmd_filter: ModelFilter do
75 var filter = self.filter
76 if filter == null then return new ModelFilter
77 return new ModelFilter.from(filter)
78 end
79 end
80
81 # Command message
82 #
83 # A message returned by a command.
84 # Messages are used to inform the client of the command initialization status and results.
85 # Mostly, messages are used to check if a command is in an error state.
86 abstract class CmdMessage
87 end
88
89 # Command Success
90 #
91 # Returned when the command was performed without any error or warning.
92 class CmdSuccess
93 super CmdMessage
94 end
95
96 # Command Error
97 #
98 # Command errors are returned when the command cannot provide results because
99 # of a problem on the user-end (i.e. Bad command name, MEntity not found etc.).
100 abstract class CmdError
101 super CmdMessage
102 end
103
104 # Command Warning
105 #
106 # Command warnings are returned when the command cannot provide results because
107 # of a problem on the model-end (i.e. No documentation for a MEntity, no code etc.)
108 abstract class CmdWarning
109 super CmdMessage
110 end
111
112 # Basic commands
113
114 # A command about a MEntity
115 class CmdEntity
116 super DocCommand
117
118 # MEntity this command is about
119 #
120 # Alternatively you can provide a `mentity_name`.
121 var mentity: nullable MEntity = null is optional, writable
122
123 # Name of the mentity this command is about
124 #
125 # Alternatively you can directly provide the `mentity`.
126 var mentity_name: nullable String = null is optional, writable
127
128 # Initialize the command mentity.
129 #
130 # If not already set, tries to find the `mentity` from the `mentity_name`.
131 #
132 # This function try to match `mentity_name` both as a `full_name` and
133 # `name`.
134 #
135 # Return states:
136 # * `CmdSuccess`: everything was ok;
137 # * `ErrorMEntityNoName`: no `mentity` and no `mentity_name` provided;
138 # * `ErrorMEntityNotFound`: no mentity for `mentity_name`;
139 # * `ErrorMEntityConflict`: `mentity_name` was a non-qualified name that
140 # returns more than one MEntity.
141 fun init_mentity: CmdMessage do
142 if mentity != null then
143 if mentity_name == null then mentity_name = mentity.as(not null).full_name
144 return new CmdSuccess
145 end
146
147 var mentity_name = self.mentity_name
148 if mentity_name == null or mentity_name.is_empty then return new ErrorMEntityNoName
149
150 mentity = model.mentity_by_full_name(mentity_name)
151 if mentity == null then
152 var mentities = model.mentities_by_name(mentity_name)
153 if mentities.is_empty then
154 var suggest = model.find(mentity_name, 3)
155 return new ErrorMEntityNotFound(mentity_name, suggest)
156 else if mentities.length > 1 then
157 return new ErrorMEntityConflict(mentity_name, mentities)
158 end
159 mentity = mentities.first
160 end
161 return new CmdSuccess
162 end
163
164 # See `init_mentity`.
165 redef fun init_command do return init_mentity
166 end
167
168 # No MEntity name provided
169 class ErrorMEntityNoName
170 super CmdError
171 redef fun to_s do return "No entity name provided"
172 end
173
174 # No MEntity matching `mentity_name`
175 class ErrorMEntityNotFound
176 super CmdError
177
178 # MEntity name provided
179 var mentity_name: String
180
181 # Suggestions matching the `mentity_name`.
182 var suggestions: Array[MEntity]
183
184 redef fun to_s do
185 var res = new Buffer
186 res.append "No entity for `{mentity_name}`.\n"
187 res.append "Did you mean: "
188 for mentity in suggestions do
189 res.append " `{mentity.full_name}`"
190 if mentity != suggestions.last then res.append ","
191 end
192 return res.write_to_string
193 end
194 end
195
196 # Multiple MEntities matching `mentity_name`
197 class ErrorMEntityConflict
198 super CmdError
199
200 # MEntity name provided
201 var mentity_name: String
202
203 # Conflicts for `mentity_name`
204 var conflicts: Array[MEntity]
205
206 redef fun to_s do
207 var res = new Buffer
208 res.append "Multiple entities for `{mentity_name}`:"
209 for mentity in conflicts do
210 res.append " `{mentity.full_name}`"
211 if mentity != conflicts.last then res.append ","
212 end
213 return res.write_to_string
214 end
215 end
216
217 # A command that returns a list of results
218 abstract class CmdList
219 super DocCommand
220
221 # Type of result
222 type ITEM: Object
223
224 # Limit the items in the list
225 var limit: nullable Int = null is optional, writable
226
227 # Page to display
228 var page: nullable Int = null is optional, writable
229
230 # Total number of ret
231 var count: nullable Int = null is optional, writable
232
233 # Total number of pages
234 var max: nullable Int = null is optional, writable
235
236 # Comparator used to sort the list
237 var sorter: nullable Comparator = null is writable
238
239 # Items in the list
240 var results: nullable Array[ITEM] = null is writable
241
242 # `init_command` is used to factorize the sorting and pagination of results
243 #
244 # See `init_results` for the result list initialization.
245 redef fun init_command do
246 var res = super
247 if not res isa CmdSuccess then return res
248 res = init_results
249 if not res isa CmdSuccess then return res
250 sort
251 paginate
252 return res
253 end
254
255 # Initialize the `results` list
256 #
257 # This method must be redefined by CmdList subclasses.
258 fun init_results: CmdMessage do return new CmdSuccess
259
260 # Sort `mentities` with `sorter`
261 fun sort do
262 var results = self.results
263 if results == null then return
264 var sorter = self.sorter
265 if sorter == null then return
266 sorter.sort(results)
267 end
268
269 # Paginate the results
270 #
271 # This methods keeps only a subset of `results` depending on the current `page` and the
272 # number of elements to return set by `limit`.
273 #
274 # The `count` can be specified when `results` does not contain all the results.
275 # For example when the results are already limited from a DB statement.
276 fun paginate do
277 var results = self.results
278 if results == null then return
279
280 var limit = self.limit
281 if limit == null then return
282
283 var page = self.page
284 if page == null or page <= 0 then page = 1
285
286 var count = self.count
287 if count == null then count = results.length
288
289 var max = count / limit
290 if max == 0 then
291 page = 1
292 max = 1
293 else if page > max then
294 page = max
295 end
296
297 var lstart = (page - 1) * limit
298 var lend = limit
299 if lstart + lend > count then lend = count - lstart
300 self.results = results.subarray(lstart, lend)
301 self.max = max
302 self.limit = limit
303 self.page = page
304 self.count = count
305 end
306 end
307
308 # A list of mentities
309 abstract class CmdEntities
310 super CmdList
311
312 redef type ITEM: MEntity
313
314 redef var sorter = new MEntityNameSorter
315 end
316
317 # A command about a MEntity that returns a list of mentities
318 abstract class CmdEntityList
319 super CmdEntity
320 super CmdEntities
321
322 autoinit(model, filter, mentity, mentity_name, limit, page, count, max)
323
324 redef fun init_command do
325 var res = init_mentity
326 if not res isa CmdSuccess then return res
327 res = init_results
328 if not res isa CmdSuccess then return res
329 sort
330 paginate
331 return res
332 end
333 end