[rubygems/rubygems] Use a constant empty tar header to avoid extra allocations
[ruby.git] / lib / bundler / graph.rb
blobb22b17a453bcce83058e6689cf5fd08feb81e931
1 # frozen_string_literal: true
3 require "set"
4 module Bundler
5   class Graph
6     GRAPH_NAME = :Gemfile
8     def initialize(env, output_file, show_version = false, show_requirements = false, output_format = "png", without = [])
9       @env               = env
10       @output_file       = output_file
11       @show_version      = show_version
12       @show_requirements = show_requirements
13       @output_format     = output_format
14       @without_groups    = without.map(&:to_sym)
16       @groups            = []
17       @relations         = Hash.new {|h, k| h[k] = Set.new }
18       @node_options      = {}
19       @edge_options      = {}
21       _populate_relations
22     end
24     attr_reader :groups, :relations, :node_options, :edge_options, :output_file, :output_format
26     def viz
27       GraphVizClient.new(self).run
28     end
30     private
32     def _populate_relations
33       parent_dependencies = _groups.values.to_set.flatten
34       loop do
35         break if parent_dependencies.empty?
37         tmp = Set.new
38         parent_dependencies.each do |dependency|
39           child_dependencies = spec_for_dependency(dependency).runtime_dependencies.to_set
40           @relations[dependency.name] += child_dependencies.map(&:name).to_set
41           tmp += child_dependencies
43           @node_options[dependency.name] = _make_label(dependency, :node)
44           child_dependencies.each do |c_dependency|
45             @edge_options["#{dependency.name}_#{c_dependency.name}"] = _make_label(c_dependency, :edge)
46           end
47         end
48         parent_dependencies = tmp
49       end
50     end
52     def _groups
53       relations = Hash.new {|h, k| h[k] = Set.new }
54       @env.current_dependencies.each do |dependency|
55         dependency.groups.each do |group|
56           next if @without_groups.include?(group)
58           relations[group.to_s].add(dependency)
59           @relations[group.to_s].add(dependency.name)
61           @node_options[group.to_s] ||= _make_label(group, :node)
62           @edge_options["#{group}_#{dependency.name}"] = _make_label(dependency, :edge)
63         end
64       end
65       @groups = relations.keys
66       relations
67     end
69     def _make_label(symbol_or_string_or_dependency, element_type)
70       case element_type.to_sym
71       when :node
72         if symbol_or_string_or_dependency.is_a?(Gem::Dependency)
73           label = symbol_or_string_or_dependency.name.dup
74           label << "\n#{spec_for_dependency(symbol_or_string_or_dependency).version}" if @show_version
75         else
76           label = symbol_or_string_or_dependency.to_s
77         end
78       when :edge
79         label = nil
80         if symbol_or_string_or_dependency.respond_to?(:requirements_list) && @show_requirements
81           tmp = symbol_or_string_or_dependency.requirements_list.join(", ")
82           label = tmp if tmp != ">= 0"
83         end
84       else
85         raise ArgumentError, "2nd argument is invalid"
86       end
87       label.nil? ? {} : { label: label }
88     end
90     def spec_for_dependency(dependency)
91       @env.requested_specs.find {|s| s.name == dependency.name }
92     end
94     class GraphVizClient
95       def initialize(graph_instance)
96         @graph_name    = graph_instance.class::GRAPH_NAME
97         @groups        = graph_instance.groups
98         @relations     = graph_instance.relations
99         @node_options  = graph_instance.node_options
100         @edge_options  = graph_instance.edge_options
101         @output_file   = graph_instance.output_file
102         @output_format = graph_instance.output_format
103       end
105       def g
106         @g ||= ::GraphViz.digraph(@graph_name, concentrate: true, normalize: true, nodesep: 0.55) do |g|
107           g.edge[:weight]   = 2
108           g.edge[:fontname] = g.node[:fontname] = "Arial, Helvetica, SansSerif"
109           g.edge[:fontsize] = 12
110         end
111       end
113       def run
114         @groups.each do |group|
115           g.add_nodes(
116             group, {
117               style: "filled",
118               fillcolor: "#B9B9D5",
119               shape: "box3d",
120               fontsize: 16,
121             }.merge(@node_options[group])
122           )
123         end
125         @relations.each do |parent, children|
126           children.each do |child|
127             if @groups.include?(parent)
128               g.add_nodes(child, { style: "filled", fillcolor: "#B9B9D5" }.merge(@node_options[child]))
129               g.add_edges(parent, child, { constraint: false }.merge(@edge_options["#{parent}_#{child}"]))
130             else
131               g.add_nodes(child, @node_options[child])
132               g.add_edges(parent, child, @edge_options["#{parent}_#{child}"])
133             end
134           end
135         end
137         if @output_format.to_s == "debug"
138           $stdout.puts g.output none: String
139           Bundler.ui.info "debugging bundle viz..."
140         else
141           begin
142             g.output @output_format.to_sym => "#{@output_file}.#{@output_format}"
143             Bundler.ui.info "#{@output_file}.#{@output_format}"
144           rescue ArgumentError => e
145             warn "Unsupported output format. See Ruby-Graphviz/lib/graphviz/constants.rb"
146             raise e
147           end
148         end
149       end
150     end
151   end