doc: Update to new INI api
[nit.git] / src / doc / commands / commands_graph.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 # Graph commands
16 #
17 # Commands that return graphical representations about a Model or a MEntity.
18 module commands_graph
19
20 import commands_model
21
22 import uml
23 import dot
24
25 # An abstract command that returns a dot graph
26 abstract class CmdGraph
27 super DocCommand
28
29 # Mainmodule for linearization
30 var mainmodule: MModule
31
32 # Rendering format
33 #
34 # Default is `dot`.
35 # See `allowed_formats`.
36 var format = "dot" is optional, writable
37
38 # Allowed rendering formats.
39 #
40 # Can be `dot` or `svg`.
41 var allowed_formats: Array[String] = ["dot", "svg"]
42
43 # Dot to render
44 var dot: nullable Writable = null is optional, writable
45
46 # Render `dot` depending on `format`
47 fun render: nullable Writable do
48 var dot = self.dot
49 if dot == null then return null
50 if format == "svg" then
51 var proc = new ProcessDuplex("dot", "-Tsvg")
52 var svg = proc.write_and_read(dot.write_to_string)
53 proc.close
54 proc.wait
55 return svg
56 end
57 return dot
58 end
59
60 redef fun init_command do
61 if not allowed_formats.has(format) then
62 return new ErrorBadGraphFormat(format, allowed_formats)
63 end
64 return super
65 end
66 end
67
68 # Bad graph format requested
69 class ErrorBadGraphFormat
70 super CmdError
71
72 # Provided format
73 var format: String
74
75 # Allowed formats
76 var allowed_formats: Array[String]
77
78 redef fun to_s do
79 var allowed_values = new Buffer
80 for allowed in allowed_formats do
81 allowed_values.append "`{allowed}`"
82 if allowed != allowed_formats.last then
83 allowed_values.append ", "
84 end
85 end
86 return "Bad format `{format}`. Allowed values are {allowed_values.write_to_string}."
87 end
88 end
89
90 # UML command
91 #
92 # Return an UML diagram about a `mentity`.
93 class CmdUML
94 super CmdEntity
95 super CmdGraph
96
97 autoinit(model, mainmodule, filter, mentity, mentity_name, format, uml)
98
99 # UML model to return
100 var uml: nullable UMLModel = null is optional, writable
101
102 redef fun init_command do
103 if uml != null then return new CmdSuccess
104
105 var res = super
106 if not res isa CmdSuccess then return res
107 var mentity = self.mentity.as(not null)
108
109 if mentity isa MClassDef then mentity = mentity.mclass
110 if mentity isa MClass or mentity isa MModule then
111 uml = new UMLModel(model, mainmodule, filter)
112 else
113 return new WarningNoUML(mentity)
114 end
115 return res
116 end
117
118 redef fun render do
119 var uml = self.uml
120 if uml == null then return null
121 if mentity isa MClass then
122 dot = uml.generate_class_uml.write_to_string
123 else if mentity isa MModule then
124 dot = uml.generate_package_uml.write_to_string
125 end
126 return super
127 end
128 end
129
130 # No UML model for `mentity`
131 class WarningNoUML
132 super CmdWarning
133
134 # MEntity provided
135 var mentity: MEntity
136
137 redef fun to_s do return "No UML for `{mentity.full_name}`"
138 end
139
140 # Render a hierarchy graph for `mentity` if any.
141 class CmdInheritanceGraph
142 super CmdEntity
143 super CmdGraph
144
145 autoinit(model, mainmodule, filter, mentity, mentity_name, pdepth, cdepth, format, graph)
146
147 # Parents depth to display
148 var pdepth: nullable Int = null is optional, writable
149
150 # Children depth to display
151 var cdepth: nullable Int = null is optional, writable
152
153 # Inheritance graph to return
154 var graph: nullable InheritanceGraph = null is optional, writable
155
156 redef fun init_command do
157 if graph != null then return new CmdSuccess
158
159 var res = super
160 if not res isa CmdSuccess then return res
161 var mentity = self.mentity.as(not null)
162
163 graph = new InheritanceGraph(mentity, model, mainmodule, filter)
164 return res
165 end
166
167 redef fun render do
168 var graph = self.graph
169 if graph == null then return ""
170 self.dot = graph.draw(pdepth, cdepth).to_dot
171 return super
172 end
173 end
174
175 # Graph for mentity hierarchies
176 #
177 # Recursively build parents and children list from a `center`.
178 class InheritanceGraph
179
180 # MEntity at the center of this graph
181 var center: MEntity
182
183 # Model used to build graph from
184 var model: Model
185
186 # Mainmodule for class linearization
187 var mainmodule: MModule
188
189 # Filter to apply on model if any
190 var filter: nullable ModelFilter
191
192 # Graph generated
193 var graph: DotGraph is lazy do
194 var graph = new DotGraph("package_diagram", "digraph")
195
196 graph["compound"] = "true"
197 graph["rankdir"] = "BT"
198 graph["ranksep"] = 0.3
199 graph["nodesep"] = 0.3
200
201 graph.nodes_attrs["margin"] = 0.1
202 graph.nodes_attrs["width"] = 0
203 graph.nodes_attrs["height"] = 0
204 graph.nodes_attrs["fontsize"] = 10
205 graph.nodes_attrs["fontname"] = "helvetica"
206
207 graph.edges_attrs["dir"] = "none"
208 graph.edges_attrs["color"] = "gray"
209
210 return graph
211 end
212
213 # Build the graph
214 fun draw(parents_depth, children_depth: nullable Int): DotGraph do
215 draw_node center
216 draw_parents(center, parents_depth)
217 draw_children(center, children_depth)
218 return graph
219 end
220
221 private var nodes = new HashMap[MEntity, DotElement]
222 private var done_parents = new HashSet[MEntity]
223 private var done_children = new HashSet[MEntity]
224
225 # Recursively draw parents of mentity
226 fun draw_parents(mentity: MEntity, max_depth: nullable Int, current_depth: nullable Int) do
227 if done_parents.has(mentity) then return
228 done_parents.add mentity
229 current_depth = current_depth or else 0
230 if max_depth != null and current_depth >= max_depth then
231 from_dotdotdot(mentity)
232 return
233 end
234 var parents = mentity.collect_parents(mainmodule, filter)
235 if parents.length > 10 then
236 from_dotdotdot(mentity)
237 return
238 end
239 for parent in parents do
240 if parent isa MModule then
241 var mgroup = parent.mgroup
242 if mgroup != null and mgroup.default_mmodule == parent then parent = mgroup
243 end
244 if parent isa MGroup then
245 if parent.mpackage.mgroups.first == parent then parent = parent.mpackage
246 end
247 draw_edge(mentity, parent)
248 end
249 for parent in parents do
250 if parent isa MModule then
251 var mgroup = parent.mgroup
252 if mgroup != null and mgroup.default_mmodule == parent then parent = mgroup
253 end
254 if parent isa MGroup then
255 if parent.mpackage.mgroups.first == parent then parent = parent.mpackage
256 end
257 draw_parents(parent, max_depth, current_depth + 1)
258 end
259 end
260
261 # Recursively draw children of mentity
262 fun draw_children(mentity: MEntity, max_depth: nullable Int, current_depth: nullable Int) do
263 if done_children.has(mentity) then return
264 done_children.add mentity
265 current_depth = current_depth or else 0
266 if max_depth != null and current_depth >= max_depth then
267 to_dotdotdot(mentity)
268 return
269 end
270 var children = mentity.collect_children(mainmodule, filter)
271 if children.length > 10 then
272 to_dotdotdot(mentity)
273 return
274 end
275 for child in children do
276 if child isa MGroup then
277 if child.mpackage.mgroups.first == child then child = child.mpackage
278 end
279 draw_edge(child, mentity)
280 end
281 for child in children do
282 if child isa MGroup then
283 if child.mpackage.mgroups.first == child then child = child.mpackage
284 end
285 draw_children(child, max_depth, current_depth + 1)
286 end
287 end
288
289 # Draw a node from a `mentity`
290 fun draw_node(mentity: MEntity): DotElement do
291 if nodes.has_key(mentity) then return nodes[mentity]
292 var node: DotElement = mentity.to_dot_node
293 if mentity == center then node = highlight(node)
294 nodes[mentity] = node
295 graph.add node
296 return node
297 end
298
299 private var edges = new HashMap2[MEntity, MEntity, DotEdge]
300
301 # Draw a edges between two mentities
302 fun draw_edge(from, to: MEntity): DotEdge do
303 if edges.has(from, to) then return edges[from, to].as(not null)
304 if edges.has(to, from) then return edges[to, from].as(not null)
305 var nfrom = draw_node(from)
306 var nto = draw_node(to)
307 var edge = new DotEdge(nfrom, nto)
308 edges[from, to] = edge
309 graph.add edge
310 return edge
311 end
312
313 private var to_dots = new HashMap[MEntity, DotElement]
314
315 # Create a link from `mentity` to a `...` node
316 fun to_dotdotdot(mentity: MEntity): DotEdge do
317 var nto = draw_node(mentity)
318 var dots = to_dots.get_or_null(mentity)
319 if dots == null then
320 dots = dotdotdot("{nto.id}...")
321 to_dots[mentity] = dots
322 end
323 graph.add dots
324 var edge = new DotEdge(dots, nto)
325 graph.add edge
326 return edge
327 end
328
329 private var from_dots = new HashMap[MEntity, DotElement]
330
331 # Create a link from a `...` node to a `mentity`
332 fun from_dotdotdot(mentity: MEntity): DotEdge do
333 var nfrom = draw_node(mentity)
334 var dots = to_dots.get_or_null(mentity)
335 if dots == null then
336 dots = dotdotdot("...{nfrom.id}")
337 from_dots[mentity] = dots
338 end
339 graph.add dots
340 var edge = new DotEdge(dots, nfrom)
341 graph.add edge
342 return edge
343 end
344
345 # Change the border color of the node
346 fun highlight(dot: DotElement): DotElement do
347 dot["color"] = "#1E9431"
348 return dot
349 end
350
351 # Generate a `...` node
352 fun dotdotdot(id: String): DotNode do
353 var node = new DotNode(id)
354 node["label"] = "..."
355 node["shape"] = "none"
356 return node
357 end
358 end
359
360 redef class MEntity
361 # Return `self` as a DotNode
362 fun to_dot_node: DotNode do
363 var node = new DotNode(full_name)
364 node["label"] = name
365 return node
366 end
367 end
368
369 redef class MPackage
370 redef fun to_dot_node do
371 var node = super
372 node["shape"] = "tab"
373 return node
374 end
375 end
376
377 redef class MGroup
378 redef fun to_dot_node do
379 var node = super
380 node["shape"] = "folder"
381 return node
382 end
383 end
384
385 redef class MModule
386 redef fun to_dot_node do
387 var node = super
388 node["shape"] = "note"
389 return node
390 end
391 end
392
393 redef class MClass
394 redef fun to_dot_node do
395 var node = super
396 node["shape"] = "box"
397 return node
398 end
399 end