PageRenderTime 51ms CodeModel.GetById 22ms RepoModel.GetById 1ms app.codeStats 0ms

/vendor/jruby-1.1.6RC1/lib/ruby/1.8/rdoc/diagram.rb

https://bitbucket.org/nicksieger/advent-jruby
Ruby | 335 lines | 246 code | 50 blank | 39 comment | 21 complexity | 87462e6aa5ce499853fd06f1d7a92086 MD5 | raw file
Possible License(s): CPL-1.0, AGPL-1.0, LGPL-2.1, JSON
  1. # A wonderful hack by to draw package diagrams using the dot package.
  2. # Originally written by Jah, team Enticla.
  3. #
  4. # You must have the V1.7 or later in your path
  5. # http://www.research.att.com/sw/tools/graphviz/
  6. require "rdoc/dot/dot"
  7. require 'rdoc/options'
  8. module RDoc
  9. # Draw a set of diagrams representing the modules and classes in the
  10. # system. We draw one diagram for each file, and one for each toplevel
  11. # class or module. This means there will be overlap. However, it also
  12. # means that you'll get better context for objects.
  13. #
  14. # To use, simply
  15. #
  16. # d = Diagram.new(info) # pass in collection of top level infos
  17. # d.draw
  18. #
  19. # The results will be written to the +dot+ subdirectory. The process
  20. # also sets the +diagram+ attribute in each object it graphs to
  21. # the name of the file containing the image. This can be used
  22. # by output generators to insert images.
  23. class Diagram
  24. FONT = "Arial"
  25. DOT_PATH = "dot"
  26. # Pass in the set of top level objects. The method also creates
  27. # the subdirectory to hold the images
  28. def initialize(info, options)
  29. @info = info
  30. @options = options
  31. @counter = 0
  32. File.makedirs(DOT_PATH)
  33. @diagram_cache = {}
  34. end
  35. # Draw the diagrams. We traverse the files, drawing a diagram for
  36. # each. We also traverse each top-level class and module in that
  37. # file drawing a diagram for these too.
  38. def draw
  39. unless @options.quiet
  40. $stderr.print "Diagrams: "
  41. $stderr.flush
  42. end
  43. @info.each_with_index do |i, file_count|
  44. @done_modules = {}
  45. @local_names = find_names(i)
  46. @global_names = []
  47. @global_graph = graph = DOT::DOTDigraph.new('name' => 'TopLevel',
  48. 'fontname' => FONT,
  49. 'fontsize' => '8',
  50. 'bgcolor' => 'lightcyan1',
  51. 'compound' => 'true')
  52. # it's a little hack %) i'm too lazy to create a separate class
  53. # for default node
  54. graph << DOT::DOTNode.new('name' => 'node',
  55. 'fontname' => FONT,
  56. 'color' => 'black',
  57. 'fontsize' => 8)
  58. i.modules.each do |mod|
  59. draw_module(mod, graph, true, i.file_relative_name)
  60. end
  61. add_classes(i, graph, i.file_relative_name)
  62. i.diagram = convert_to_png("f_#{file_count}", graph)
  63. # now go through and document each top level class and
  64. # module independently
  65. i.modules.each_with_index do |mod, count|
  66. @done_modules = {}
  67. @local_names = find_names(mod)
  68. @global_names = []
  69. @global_graph = graph = DOT::DOTDigraph.new('name' => 'TopLevel',
  70. 'fontname' => FONT,
  71. 'fontsize' => '8',
  72. 'bgcolor' => 'lightcyan1',
  73. 'compound' => 'true')
  74. graph << DOT::DOTNode.new('name' => 'node',
  75. 'fontname' => FONT,
  76. 'color' => 'black',
  77. 'fontsize' => 8)
  78. draw_module(mod, graph, true)
  79. mod.diagram = convert_to_png("m_#{file_count}_#{count}",
  80. graph)
  81. end
  82. end
  83. $stderr.puts unless @options.quiet
  84. end
  85. #######
  86. private
  87. #######
  88. def find_names(mod)
  89. return [mod.full_name] + mod.classes.collect{|cl| cl.full_name} +
  90. mod.modules.collect{|m| find_names(m)}.flatten
  91. end
  92. def find_full_name(name, mod)
  93. full_name = name.dup
  94. return full_name if @local_names.include?(full_name)
  95. mod_path = mod.full_name.split('::')[0..-2]
  96. unless mod_path.nil?
  97. until mod_path.empty?
  98. full_name = mod_path.pop + '::' + full_name
  99. return full_name if @local_names.include?(full_name)
  100. end
  101. end
  102. return name
  103. end
  104. def draw_module(mod, graph, toplevel = false, file = nil)
  105. return if @done_modules[mod.full_name] and not toplevel
  106. @counter += 1
  107. url = mod.http_url("classes")
  108. m = DOT::DOTSubgraph.new('name' => "cluster_#{mod.full_name.gsub( /:/,'_' )}",
  109. 'label' => mod.name,
  110. 'fontname' => FONT,
  111. 'color' => 'blue',
  112. 'style' => 'filled',
  113. 'URL' => %{"#{url}"},
  114. 'fillcolor' => toplevel ? 'palegreen1' : 'palegreen3')
  115. @done_modules[mod.full_name] = m
  116. add_classes(mod, m, file)
  117. graph << m
  118. unless mod.includes.empty?
  119. mod.includes.each do |m|
  120. m_full_name = find_full_name(m.name, mod)
  121. if @local_names.include?(m_full_name)
  122. @global_graph << DOT::DOTEdge.new('from' => "#{m_full_name.gsub( /:/,'_' )}",
  123. 'to' => "#{mod.full_name.gsub( /:/,'_' )}",
  124. 'ltail' => "cluster_#{m_full_name.gsub( /:/,'_' )}",
  125. 'lhead' => "cluster_#{mod.full_name.gsub( /:/,'_' )}")
  126. else
  127. unless @global_names.include?(m_full_name)
  128. path = m_full_name.split("::")
  129. url = File.join('classes', *path) + ".html"
  130. @global_graph << DOT::DOTNode.new('name' => "#{m_full_name.gsub( /:/,'_' )}",
  131. 'shape' => 'box',
  132. 'label' => "#{m_full_name}",
  133. 'URL' => %{"#{url}"})
  134. @global_names << m_full_name
  135. end
  136. @global_graph << DOT::DOTEdge.new('from' => "#{m_full_name.gsub( /:/,'_' )}",
  137. 'to' => "#{mod.full_name.gsub( /:/,'_' )}",
  138. 'lhead' => "cluster_#{mod.full_name.gsub( /:/,'_' )}")
  139. end
  140. end
  141. end
  142. end
  143. def add_classes(container, graph, file = nil )
  144. use_fileboxes = Options.instance.fileboxes
  145. files = {}
  146. # create dummy node (needed if empty and for module includes)
  147. if container.full_name
  148. graph << DOT::DOTNode.new('name' => "#{container.full_name.gsub( /:/,'_' )}",
  149. 'label' => "",
  150. 'width' => (container.classes.empty? and
  151. container.modules.empty?) ?
  152. '0.75' : '0.01',
  153. 'height' => '0.01',
  154. 'shape' => 'plaintext')
  155. end
  156. container.classes.each_with_index do |cl, cl_index|
  157. last_file = cl.in_files[-1].file_relative_name
  158. if use_fileboxes && !files.include?(last_file)
  159. @counter += 1
  160. files[last_file] =
  161. DOT::DOTSubgraph.new('name' => "cluster_#{@counter}",
  162. 'label' => "#{last_file}",
  163. 'fontname' => FONT,
  164. 'color'=>
  165. last_file == file ? 'red' : 'black')
  166. end
  167. next if cl.name == 'Object' || cl.name[0,2] == "<<"
  168. url = cl.http_url("classes")
  169. label = cl.name.dup
  170. if use_fileboxes && cl.in_files.length > 1
  171. label << '\n[' +
  172. cl.in_files.collect {|i|
  173. i.file_relative_name
  174. }.sort.join( '\n' ) +
  175. ']'
  176. end
  177. attrs = {
  178. 'name' => "#{cl.full_name.gsub( /:/, '_' )}",
  179. 'fontcolor' => 'black',
  180. 'style'=>'filled',
  181. 'color'=>'palegoldenrod',
  182. 'label' => label,
  183. 'shape' => 'ellipse',
  184. 'URL' => %{"#{url}"}
  185. }
  186. c = DOT::DOTNode.new(attrs)
  187. if use_fileboxes
  188. files[last_file].push c
  189. else
  190. graph << c
  191. end
  192. end
  193. if use_fileboxes
  194. files.each_value do |val|
  195. graph << val
  196. end
  197. end
  198. unless container.classes.empty?
  199. container.classes.each_with_index do |cl, cl_index|
  200. cl.includes.each do |m|
  201. m_full_name = find_full_name(m.name, cl)
  202. if @local_names.include?(m_full_name)
  203. @global_graph << DOT::DOTEdge.new('from' => "#{m_full_name.gsub( /:/,'_' )}",
  204. 'to' => "#{cl.full_name.gsub( /:/,'_' )}",
  205. 'ltail' => "cluster_#{m_full_name.gsub( /:/,'_' )}")
  206. else
  207. unless @global_names.include?(m_full_name)
  208. path = m_full_name.split("::")
  209. url = File.join('classes', *path) + ".html"
  210. @global_graph << DOT::DOTNode.new('name' => "#{m_full_name.gsub( /:/,'_' )}",
  211. 'shape' => 'box',
  212. 'label' => "#{m_full_name}",
  213. 'URL' => %{"#{url}"})
  214. @global_names << m_full_name
  215. end
  216. @global_graph << DOT::DOTEdge.new('from' => "#{m_full_name.gsub( /:/,'_' )}",
  217. 'to' => "#{cl.full_name.gsub( /:/, '_')}")
  218. end
  219. end
  220. sclass = cl.superclass
  221. next if sclass.nil? || sclass == 'Object'
  222. sclass_full_name = find_full_name(sclass,cl)
  223. unless @local_names.include?(sclass_full_name) or @global_names.include?(sclass_full_name)
  224. path = sclass_full_name.split("::")
  225. url = File.join('classes', *path) + ".html"
  226. @global_graph << DOT::DOTNode.new(
  227. 'name' => "#{sclass_full_name.gsub( /:/, '_' )}",
  228. 'label' => sclass_full_name,
  229. 'URL' => %{"#{url}"})
  230. @global_names << sclass_full_name
  231. end
  232. @global_graph << DOT::DOTEdge.new('from' => "#{sclass_full_name.gsub( /:/,'_' )}",
  233. 'to' => "#{cl.full_name.gsub( /:/, '_')}")
  234. end
  235. end
  236. container.modules.each do |submod|
  237. draw_module(submod, graph)
  238. end
  239. end
  240. def convert_to_png(file_base, graph)
  241. str = graph.to_s
  242. return @diagram_cache[str] if @diagram_cache[str]
  243. op_type = Options.instance.image_format
  244. dotfile = File.join(DOT_PATH, file_base)
  245. src = dotfile + ".dot"
  246. dot = dotfile + "." + op_type
  247. unless @options.quiet
  248. $stderr.print "."
  249. $stderr.flush
  250. end
  251. File.open(src, 'w+' ) do |f|
  252. f << str << "\n"
  253. end
  254. system "dot", "-T#{op_type}", src, "-o", dot
  255. # Now construct the imagemap wrapper around
  256. # that png
  257. ret = wrap_in_image_map(src, dot)
  258. @diagram_cache[str] = ret
  259. return ret
  260. end
  261. # Extract the client-side image map from dot, and use it
  262. # to generate the imagemap proper. Return the whole
  263. # <map>..<img> combination, suitable for inclusion on
  264. # the page
  265. def wrap_in_image_map(src, dot)
  266. res = %{<map id="map" name="map">\n}
  267. dot_map = `dot -Tismap #{src}`
  268. dot_map.each do |area|
  269. unless area =~ /^rectangle \((\d+),(\d+)\) \((\d+),(\d+)\) ([\/\w.]+)\s*(.*)/
  270. $stderr.puts "Unexpected output from dot:\n#{area}"
  271. return nil
  272. end
  273. xs, ys = [$1.to_i, $3.to_i], [$2.to_i, $4.to_i]
  274. url, area_name = $5, $6
  275. res << %{ <area shape="rect" coords="#{xs.min},#{ys.min},#{xs.max},#{ys.max}" }
  276. res << %{ href="#{url}" alt="#{area_name}" />\n}
  277. end
  278. res << "</map>\n"
  279. # map_file = src.sub(/.dot/, '.map')
  280. # system("dot -Timap #{src} -o #{map_file}")
  281. res << %{<img src="#{dot}" usemap="#map" border="0" alt="#{dot}">}
  282. return res
  283. end
  284. end
  285. end