diff options
author | Erik Michaels-Ober <sferik@gmail.com> | 2011-12-12 10:15:47 -0800 |
---|---|---|
committer | Erik Michaels-Ober <sferik@gmail.com> | 2011-12-12 10:15:47 -0800 |
commit | f5edc0ce84b7c96734db7e2ac37eb348086c5040 (patch) | |
tree | 2137880f60b3afac2f4b657e7b3c3271ef493637 | |
parent | 7c7282f34d17db5f1c76606e479b0d1ec935d184 (diff) | |
parent | 52f8cb11b317d1b85894b70264f1e9c62cf89517 (diff) | |
download | bundler-f5edc0ce84b7c96734db7e2ac37eb348086c5040.tar.gz |
Merge pull request #1586 from hirochachacha/1213_rewrite_viz_graph
fix bundle viz command
-rw-r--r-- | lib/bundler/cli.rb | 9 | ||||
-rw-r--r-- | lib/bundler/graph.rb | 216 |
2 files changed, 122 insertions, 103 deletions
diff --git a/lib/bundler/cli.rb b/lib/bundler/cli.rb index 5a86ec572a..9e3a3344f2 100644 --- a/lib/bundler/cli.rb +++ b/lib/bundler/cli.rb @@ -507,16 +507,17 @@ module Bundler Viz requires the ruby-graphviz gem (and its dependencies). The associated gems must also be installed via 'bundle install'. D - method_option :file, :type => :string, :default => 'gem_graph.png', :aliases => '-f', :banner => "The name to use for the generated png file." + method_option :file, :type => :string, :default => 'gem_graph', :aliases => '-f', :banner => "The name to use for the generated file. see format option" method_option :version, :type => :boolean, :default => false, :aliases => '-v', :banner => "Set to show each gem version." method_option :requirements, :type => :boolean, :default => false, :aliases => '-r', :banner => "Set to show the version of each required dependency." + method_option :format, :type => :string, :default => "png", :aliases => '-F', :banner => "This is output format option. Supported format is png, jpg, svg, dot ..." def viz output_file = File.expand_path(options[:file]) - graph = Graph.new( Bundler.load ) + output_format = options[:format] + graph = Graph.new(Bundler.load, output_file, options[:version], options[:requirements], options[:format]) begin - graph.viz(output_file, options[:version], options[:requirements]) - Bundler.ui.info output_file + graph.viz rescue LoadError => e Bundler.ui.error e.inspect Bundler.ui.warn "Make sure you have the graphviz ruby gem. You can install it with:" diff --git a/lib/bundler/graph.rb b/lib/bundler/graph.rb index 3cea736ac2..6ab23d90ef 100644 --- a/lib/bundler/graph.rb +++ b/lib/bundler/graph.rb @@ -1,130 +1,148 @@ +require 'set' module Bundler class Graph + GRAPH_NAME = :Gemfile - USER_OPTIONS = {:style => 'filled', :fillcolor => '#B9B9D5'}.freeze + def initialize(env, output_file, show_version = false, show_requirements = false, output_format = "png") + @env = env + @output_file = output_file + @show_version = show_version + @show_requirements = show_requirements + @output_format = output_format - def initialize(env) - @env = env - end - - def nodes - populate - @nodes - end + @groups = [] + @relations = Hash.new {|h, k| h[k] = Set.new} + @node_options = {} + @edge_options = {} - def groups - populate - @groups + _populate_relations end - def viz(output_file, show_gem_versions = false, show_dependency_requirements = false) - require 'graphviz' - populate + attr_reader :groups, :relations, :node_options, :edge_options, :output_file, :output_format - graph_viz = GraphViz::new('Gemfile', {:concentrate => true, :normalize => true, :nodesep => 0.55}) - graph_viz.edge[:fontname] = graph_viz.node[:fontname] = 'Arial, Helvetica, SansSerif' - graph_viz.edge[:fontsize] = 12 - - viz_nodes = {} + def viz + GraphVizClient.new(self).run + end - # populate all of the nodes - nodes.each do |name, node| - label = name.dup - label << "\n#{node.version}" if show_gem_versions - options = { :label => label } - options.merge!( USER_OPTIONS ) if node.is_user - viz_nodes[name] = graph_viz.add_node( name, options ) - end + private - group_nodes = {} - @groups.each do |name, dependencies| - group_nodes[name] = graph_viz.add_node(name.to_s, { :shape => 'box3d', :fontsize => 16 }.merge(USER_OPTIONS)) - dependencies.each do |dependency| - options = { :weight => 2 } - if show_dependency_requirements && (dependency.requirement.to_s != ">= 0") - options[:label] = dependency.requirement.to_s + def _populate_relations + relations = Hash.new {|h, k| h[k] = Set.new} + parent_dependencies = _groups.values.to_set.flatten + while true + if parent_dependencies.empty? + break + else + tmp = Set.new + parent_dependencies.each do |dependency| + child_dependencies = dependency.to_spec.runtime_dependencies.to_set + relations[dependency.name] += child_dependencies.to_set + @relations[dependency.name] += child_dependencies.map(&:name).to_set + tmp += child_dependencies + + @node_options[dependency.name] = {:label => _make_label(dependency, :node)} + child_dependencies.each do |c_dependency| + @edge_options["#{dependency.name}_#{c_dependency.name}"] = {:label => _make_label(c_dependency, :edge)} + end end - graph_viz.add_edge( group_nodes[name], viz_nodes[dependency.name], options ) + parent_dependencies = tmp end end + @relations + end - @groups.keys.select { |group| group != :default }.each do |group| - graph_viz.add_edge( group_nodes[group], group_nodes[:default], { :weight => 2 } ) - end - - viz_nodes.each do |name, node| - nodes[name].dependencies.each do |dependency| - options = { } - if nodes[dependency.name].is_user - options[:constraint] = false - end - if show_dependency_requirements && (dependency.requirement.to_s != ">= 0") - options[:label] = dependency.requirement.to_s - end + def _groups + relations = Hash.new {|h, k| h[k] = Set.new} + @env.current_dependencies.each do |dependency| + dependency.groups.each do |group| + relations[group.to_s].add(dependency) + @relations[group.to_s].add(dependency.name) - graph_viz.add_edge( node, viz_nodes[dependency.name], options ) + @node_options[group.to_s] ||= {:label => _make_label(group, :node)} + @edge_options["#{group}_#{dependency.name}"] = {:label => _make_label(dependency, :edge)} end end - - graph_viz.output( :png => output_file ) + @groups = relations.keys + relations end - private - - def populate - return if @populated - - # hash of name => GraphNode - @nodes = {} - @groups = {} - - # Populate @nodes - @env.specs.each { |spec| @nodes[spec.name] = GraphNode.new(spec.name, spec.version) } - - # For gems in Gemfile, add details - @env.current_dependencies.each do |dependency| - next unless node = @nodes[dependency.name] - node.is_user = true - - dependency.groups.each do |group| - if @groups.has_key? group - group = @groups[group] - else - group = @groups[group] = [] - end - group << dependency + def _make_label(symbol_or_string_or_dependency, element_type) + case element_type.to_sym + when :node + if symbol_or_string_or_dependency.is_a?(Gem::Dependency) + label = symbol_or_string_or_dependency.name.dup + label << "\n#{symbol_or_string_or_dependency.to_spec.version.to_s}" if @show_version + else + label = symbol_or_string_or_dependency.to_s + end + when :edge + label = nil + if symbol_or_string_or_dependency.respond_to?(:requirements_list) && @show_requirements + tmp = symbol_or_string_or_dependency.requirements_list.join(", ") + label = tmp if tmp != ">= 0" end + else + raise ArgumentError, "2nd argument is invalid" end + label + end - # walk though a final time and add edges - @env.specs.each do |spec| + class GraphVizClient + def initialize(graph_instance) + @graph_name = graph_instance.class::GRAPH_NAME + @groups = graph_instance.groups + @relations = graph_instance.relations + @node_options = graph_instance.node_options + @edge_options = graph_instance.edge_options + @output_file = graph_instance.output_file + @output_format = graph_instance.output_format + end - from = @nodes[spec.name] - spec.runtime_dependencies.each do |dependency| - from.dependencies << dependency + def g + require 'graphviz' + @g ||= ::GraphViz.digraph(@graph_name, {:concentrate => true, :normalize => true, :nodesep => 0.55}) do |g| + g.edge[:weight] = 2 + g.edge[:fontname] = g.node[:fontname] = 'Arial, Helvetica, SansSerif' + g.edge[:fontsize] = 12 end - end - @nodes.freeze - @groups.freeze - @populated = true - end - - end + def run + @groups.each do |group| + g.add_node( + group, + {:style => 'filled', + :fillcolor => '#B9B9D5', + :shape => "box3d", + :fontsize => 16}.merge(@node_options[group]) + ) + end - # Add version info - class GraphNode + @relations.each do |parent, children| + children.each do |child| + if @groups.include?(parent) + g.add_node(child, {:style => 'filled', :fillcolor => '#B9B9D5'}.merge(@node_options[child])) + g.add_edge(parent, child, {:constraint => false}.merge(@edge_options["#{parent}_#{child}"])) + else + g.add_node(child, @node_options[child]) + g.add_edge(parent, child, @edge_options["#{parent}_#{child}"]) + end + end + end - def initialize(name, version) - @name = name - @version = version - @is_user = false - @dependencies = [] + if @output_format.to_s == "debug" + $stdout.puts g.output :none => String + Bundler.ui.info "debugging bundle viz..." + else + begin + g.output @output_format.to_sym => "#{@output_file}.#{@output_format}" + Bundler.ui.info "#{@output_file}.#{@output_format}" + rescue ArgumentError => e + $stderr.puts "Unsupported output format. See Ruby-Graphviz/lib/graphviz/constants.rb" + raise e + end + end + end end - - attr_reader :name, :dependencies, :version - attr_accessor :is_user - end end |