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
32 # See `allowed_formats`.
33 var format
= "dot" is optional
, writable
35 # Allowed rendering formats.
37 # Can be `dot` or `svg`.
38 var allowed_formats
: Array[String] = ["dot", "svg"]
41 var dot
: nullable Writable = null is optional
, writable
43 # Render `dot` depending on `format`
44 fun render
: nullable Writable do
46 if dot
== null then return null
47 if format
== "svg" then
48 var proc
= new ProcessDuplex("dot", "-Tsvg")
49 var svg
= proc
.write_and_read
(dot
.write_to_string
)
57 redef fun init_command
do
58 if not allowed_formats
.has
(format
) then
59 return new ErrorBadGraphFormat(format
, allowed_formats
)
65 # Bad graph format requested
66 class ErrorBadGraphFormat
73 var allowed_formats
: Array[String]
76 var allowed_values
= new Buffer
77 for allowed
in allowed_formats
do
78 allowed_values
.append
"`{allowed}`"
79 if allowed
!= allowed_formats
.last
then
80 allowed_values
.append
", "
83 return "Bad format `{format}`. Allowed values are {allowed_values.write_to_string}."
89 # Return an UML diagram about a `mentity`.
94 autoinit
(view
, mentity
, mentity_name
, format
, uml
)
97 var uml
: nullable UMLModel = null is optional
, writable
99 redef fun init_command
do
100 if uml
!= null then return new CmdSuccess
103 if not res
isa CmdSuccess then return res
104 var mentity
= self.mentity
.as(not null)
106 if mentity
isa MClassDef then mentity
= mentity
.mclass
107 if mentity
isa MClass then
108 uml
= new UMLModel(view
, view
.mainmodule
)
109 else if mentity
isa MModule then
110 uml
= new UMLModel(view
, view
.mainmodule
)
112 return new WarningNoUML(mentity
)
119 if uml
== null then return null
120 if mentity
isa MClass then
121 dot
= uml
.generate_class_uml
.write_to_string
122 else if mentity
isa MModule then
123 dot
= uml
.generate_package_uml
.write_to_string
129 # No UML model for `mentity`
136 redef fun to_s
do return "No UML for `{mentity.full_name}`"
139 # Render a hierarchy graph for `mentity` if any.
140 class CmdInheritanceGraph
144 autoinit
(view
, mentity
, mentity_name
, pdepth
, cdepth
, format
, graph
)
146 # Parents depth to display
147 var pdepth
: nullable Int = null is optional
, writable
149 # Children depth to display
150 var cdepth
: nullable Int = null is optional
, writable
152 # Inheritance graph to return
153 var graph
: nullable InheritanceGraph = null is optional
, writable
155 redef fun init_command
do
156 if graph
!= null then return new CmdSuccess
159 if not res
isa CmdSuccess then return res
160 var mentity
= self.mentity
.as(not null)
162 graph
= new InheritanceGraph(mentity
, view
)
167 var graph
= self.graph
168 if graph
== null then return ""
169 self.dot
= graph
.draw
(pdepth
, cdepth
).to_dot
174 # Graph for mentity hierarchies
176 # Recursively build parents and children list from a `center`.
177 class InheritanceGraph
179 # MEntity at the center of this graph
182 # ModelView used to filter graph
186 var graph
: DotGraph is lazy
do
187 var graph
= new DotGraph("package_diagram", "digraph")
189 graph
["compound"] = "true"
190 graph
["rankdir"] = "BT"
191 graph
["ranksep"] = 0.3
192 graph
["nodesep"] = 0.3
194 graph
.nodes_attrs
["margin"] = 0.1
195 graph
.nodes_attrs
["width"] = 0
196 graph
.nodes_attrs
["height"] = 0
197 graph
.nodes_attrs
["fontsize"] = 10
198 graph
.nodes_attrs
["fontname"] = "helvetica"
200 graph
.edges_attrs
["dir"] = "none"
201 graph
.edges_attrs
["color"] = "gray"
207 fun draw
(parents_depth
, children_depth
: nullable Int): DotGraph do
209 draw_parents
(center
, parents_depth
)
210 draw_children
(center
, children_depth
)
214 private var nodes
= new HashMap[MEntity, DotElement]
215 private var done_parents
= new HashSet[MEntity]
216 private var done_children
= new HashSet[MEntity]
218 # Recursively draw parents of mentity
219 fun draw_parents
(mentity
: MEntity, max_depth
: nullable Int, current_depth
: nullable Int) do
220 if done_parents
.has
(mentity
) then return
221 done_parents
.add mentity
222 current_depth
= current_depth
or else 0
223 if max_depth
!= null and current_depth
>= max_depth
then
224 from_dotdotdot
(mentity
)
227 var parents
= mentity
.collect_parents
(view
)
228 if parents
.length
> 10 then
229 from_dotdotdot
(mentity
)
232 for parent
in parents
do
233 if parent
isa MModule then
234 var mgroup
= parent
.mgroup
235 if mgroup
!= null and mgroup
.default_mmodule
== parent
then parent
= mgroup
237 if parent
isa MGroup then
238 if parent
.mpackage
.mgroups
.first
== parent
then parent
= parent
.mpackage
240 draw_edge
(mentity
, parent
)
242 for parent
in parents
do
243 if parent
isa MModule then
244 var mgroup
= parent
.mgroup
245 if mgroup
!= null and mgroup
.default_mmodule
== parent
then parent
= mgroup
247 if parent
isa MGroup then
248 if parent
.mpackage
.mgroups
.first
== parent
then parent
= parent
.mpackage
250 draw_parents
(parent
, max_depth
, current_depth
+ 1)
254 # Recursively draw children of mentity
255 fun draw_children
(mentity
: MEntity, max_depth
: nullable Int, current_depth
: nullable Int) do
256 if done_children
.has
(mentity
) then return
257 done_children
.add mentity
258 current_depth
= current_depth
or else 0
259 if max_depth
!= null and current_depth
>= max_depth
then
260 to_dotdotdot
(mentity
)
263 var children
= mentity
.collect_children
(view
)
264 if children
.length
> 10 then
265 to_dotdotdot
(mentity
)
268 for child
in children
do
269 if child
isa MGroup then
270 if child
.mpackage
.mgroups
.first
== child
then child
= child
.mpackage
272 draw_edge
(child
, mentity
)
274 for child
in children
do
275 if child
isa MGroup then
276 if child
.mpackage
.mgroups
.first
== child
then child
= child
.mpackage
278 draw_children
(child
, max_depth
, current_depth
+ 1)
282 # Draw a node from a `mentity`
283 fun draw_node
(mentity
: MEntity): DotElement do
284 if nodes
.has_key
(mentity
) then return nodes
[mentity
]
285 var node
: DotElement = mentity
.to_dot_node
286 if mentity
== center
then node
= highlight
(node
)
287 nodes
[mentity
] = node
292 private var edges
= new HashMap2[MEntity, MEntity, DotEdge]
294 # Draw a edges between two mentities
295 fun draw_edge
(from
, to
: MEntity): DotEdge do
296 if edges
.has
(from
, to
) then return edges
[from
, to
].as(not null)
297 if edges
.has
(to
, from
) then return edges
[to
, from
].as(not null)
298 var nfrom
= draw_node
(from
)
299 var nto
= draw_node
(to
)
300 var edge
= new DotEdge(nfrom
, nto
)
301 edges
[from
, to
] = edge
306 private var to_dots
= new HashMap[MEntity, DotElement]
308 # Create a link from `mentity` to a `...` node
309 fun to_dotdotdot
(mentity
: MEntity): DotEdge do
310 var nto
= draw_node
(mentity
)
311 var dots
= to_dots
.get_or_null
(mentity
)
313 dots
= dotdotdot
("{nto.id}...")
314 to_dots
[mentity
] = dots
317 var edge
= new DotEdge(dots
, nto
)
322 private var from_dots
= new HashMap[MEntity, DotElement]
324 # Create a link from a `...` node to a `mentity`
325 fun from_dotdotdot
(mentity
: MEntity): DotEdge do
326 var nfrom
= draw_node
(mentity
)
327 var dots
= to_dots
.get_or_null
(mentity
)
329 dots
= dotdotdot
("...{nfrom.id}")
330 from_dots
[mentity
] = dots
333 var edge
= new DotEdge(dots
, nfrom
)
338 # Change the border color of the node
339 fun highlight
(dot
: DotElement): DotElement do
340 dot
["color"] = "#1E9431"
344 # Generate a `...` node
345 fun dotdotdot
(id
: String): DotNode do
346 var node
= new DotNode(id
)
347 node
["label"] = "..."
348 node
["shape"] = "none"
354 # Return `self` as a DotNode
355 fun to_dot_node
: DotNode do
356 var node
= new DotNode(full_name
)
363 redef fun to_dot_node
do
365 node
["shape"] = "tab"
371 redef fun to_dot_node
do
373 node
["shape"] = "folder"
379 redef fun to_dot_node
do
381 node
["shape"] = "note"
387 redef fun to_dot_node
do
389 node
["shape"] = "box"