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