1 # This file is part of NIT ( http://www.nitlanguage.org ).
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
7 # http://www.apache.org/licenses/LICENSE-2.0
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.
17 # Commands that return graphical representations about a Model or a MEntity.
25 # An abstract command that returns a dot graph
26 abstract class CmdGraph
29 # Mainmodule for linearization
30 var mainmodule
: MModule
35 # See `allowed_formats`.
36 var format
= "dot" is optional
, writable
38 # Allowed rendering formats.
40 # Can be `dot` or `svg`.
41 var allowed_formats
: Array[String] = ["dot", "svg"]
44 var dot
: nullable Writable = null is optional
, writable
46 # Render `dot` depending on `format`
47 fun render
: nullable Writable do
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
)
60 redef fun init_command
do
61 if not allowed_formats
.has
(format
) then
62 return new ErrorBadGraphFormat(format
, allowed_formats
)
68 # Bad graph format requested
69 class ErrorBadGraphFormat
76 var allowed_formats
: Array[String]
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
", "
86 return "Bad format `{format}`. Allowed values are {allowed_values.write_to_string}."
92 # Return an UML diagram about a `mentity`.
97 autoinit
(model
, mainmodule
, filter
, mentity
, mentity_name
, format
, uml
)
100 var uml
: nullable UMLModel = null is optional
, writable
102 redef fun init_command
do
103 if uml
!= null then return new CmdSuccess
106 if not res
isa CmdSuccess then return res
107 var mentity
= self.mentity
.as(not null)
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
)
113 return new WarningNoUML(mentity
)
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
130 # No UML model for `mentity`
137 redef fun to_s
do return "No UML for `{mentity.full_name}`"
140 # Render a hierarchy graph for `mentity` if any.
141 class CmdInheritanceGraph
145 autoinit
(model
, mainmodule
, filter
, mentity
, mentity_name
, pdepth
, cdepth
, format
, graph
)
147 # Parents depth to display
148 var pdepth
: nullable Int = null is optional
, writable
150 # Children depth to display
151 var cdepth
: nullable Int = null is optional
, writable
153 # Inheritance graph to return
154 var graph
: nullable InheritanceGraph = null is optional
, writable
156 redef fun init_command
do
157 if graph
!= null then return new CmdSuccess
160 if not res
isa CmdSuccess then return res
161 var mentity
= self.mentity
.as(not null)
163 graph
= new InheritanceGraph(mentity
, model
, mainmodule
, filter
)
168 var graph
= self.graph
169 if graph
== null then return ""
170 self.dot
= graph
.draw
(pdepth
, cdepth
).to_dot
175 # Graph for mentity hierarchies
177 # Recursively build parents and children list from a `center`.
178 class InheritanceGraph
180 # MEntity at the center of this graph
183 # Model used to build graph from
186 # Mainmodule for class linearization
187 var mainmodule
: MModule
189 # Filter to apply on model if any
190 var filter
: nullable ModelFilter
193 var graph
: DotGraph is lazy
do
194 var graph
= new DotGraph("package_diagram", "digraph")
196 graph
["compound"] = "true"
197 graph
["rankdir"] = "BT"
198 graph
["ranksep"] = 0.3
199 graph
["nodesep"] = 0.3
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"
207 graph
.edges_attrs
["dir"] = "none"
208 graph
.edges_attrs
["color"] = "gray"
214 fun draw
(parents_depth
, children_depth
: nullable Int): DotGraph do
216 draw_parents
(center
, parents_depth
)
217 draw_children
(center
, children_depth
)
221 private var nodes
= new HashMap[MEntity, DotElement]
222 private var done_parents
= new HashSet[MEntity]
223 private var done_children
= new HashSet[MEntity]
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
)
234 var parents
= mentity
.collect_parents
(mainmodule
, filter
)
235 if parents
.length
> 10 then
236 from_dotdotdot
(mentity
)
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
244 if parent
isa MGroup then
245 if parent
.mpackage
.mgroups
.first
== parent
then parent
= parent
.mpackage
247 draw_edge
(mentity
, parent
)
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
254 if parent
isa MGroup then
255 if parent
.mpackage
.mgroups
.first
== parent
then parent
= parent
.mpackage
257 draw_parents
(parent
, max_depth
, current_depth
+ 1)
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
)
270 var children
= mentity
.collect_children
(mainmodule
, filter
)
271 if children
.length
> 10 then
272 to_dotdotdot
(mentity
)
275 for child
in children
do
276 if child
isa MGroup then
277 if child
.mpackage
.mgroups
.first
== child
then child
= child
.mpackage
279 draw_edge
(child
, mentity
)
281 for child
in children
do
282 if child
isa MGroup then
283 if child
.mpackage
.mgroups
.first
== child
then child
= child
.mpackage
285 draw_children
(child
, max_depth
, current_depth
+ 1)
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
299 private var edges
= new HashMap2[MEntity, MEntity, DotEdge]
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
313 private var to_dots
= new HashMap[MEntity, DotElement]
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
)
320 dots
= dotdotdot
("{nto.id}...")
321 to_dots
[mentity
] = dots
324 var edge
= new DotEdge(dots
, nto
)
329 private var from_dots
= new HashMap[MEntity, DotElement]
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
)
336 dots
= dotdotdot
("...{nfrom.id}")
337 from_dots
[mentity
] = dots
340 var edge
= new DotEdge(dots
, nfrom
)
345 # Change the border color of the node
346 fun highlight
(dot
: DotElement): DotElement do
347 dot
["color"] = "#1E9431"
351 # Generate a `...` node
352 fun dotdotdot
(id
: String): DotNode do
353 var node
= new DotNode(id
)
354 node
["label"] = "..."
355 node
["shape"] = "none"
361 # Return `self` as a DotNode
362 fun to_dot_node
: DotNode do
363 var node
= new DotNode(full_name
)
370 redef fun to_dot_node
do
372 node
["shape"] = "tab"
378 redef fun to_dot_node
do
380 node
["shape"] = "folder"
386 redef fun to_dot_node
do
388 node
["shape"] = "note"
394 redef fun to_dot_node
do
396 node
["shape"] = "box"