From 1056489ea3e1c3f201936666b2725bfc142bb0f2 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Thu, 28 Apr 2022 17:15:43 +0900 Subject: Merge https://github.com/rubygems/rubygems/commit/3f7d0352e84b29d4a2d4cd93b31e5ebdb5f79cc6 --- lib/bundler/definition.rb | 19 +- lib/bundler/resolver.rb | 87 ++- lib/bundler/resolver/spec_group.rb | 4 +- lib/bundler/ruby_version.rb | 13 - lib/bundler/rubygems_ext.rb | 17 + lib/bundler/source/metadata.rb | 2 +- .../molinillo/lib/molinillo/dependency_graph.rb | 2 +- .../vendor/molinillo/lib/molinillo/errors.rb | 58 +- .../vendor/molinillo/lib/molinillo/gem_metadata.rb | 2 +- lib/bundler/vendor/tsort/lib/tsort.rb | 637 ++++++++++----------- 10 files changed, 433 insertions(+), 408 deletions(-) (limited to 'lib/bundler') diff --git a/lib/bundler/definition.rb b/lib/bundler/definition.rb index 09be2db68c..c1d865d049 100644 --- a/lib/bundler/definition.rb +++ b/lib/bundler/definition.rb @@ -736,30 +736,13 @@ module Bundler def metadata_dependencies @metadata_dependencies ||= begin - ruby_versions = ruby_version_requirements(@ruby_version) [ - Dependency.new("Ruby\0", ruby_versions), + Dependency.new("Ruby\0", RubyVersion.system.gem_version), Dependency.new("RubyGems\0", Gem::VERSION), ] end end - def ruby_version_requirements(ruby_version) - return [] unless ruby_version - if ruby_version.patchlevel - [ruby_version.to_gem_version_with_patchlevel] - else - ruby_version.versions.map do |version| - requirement = Gem::Requirement.new(version) - if requirement.exact? - "~> #{version}.0" - else - requirement - end - end - end - end - def expand_dependencies(dependencies, remote = false) deps = [] dependencies.each do |dep| diff --git a/lib/bundler/resolver.rb b/lib/bundler/resolver.rb index be0751d9d2..433a5e53e1 100644 --- a/lib/bundler/resolver.rb +++ b/lib/bundler/resolver.rb @@ -312,29 +312,66 @@ module Bundler e = Molinillo::VersionConflict.new(conflicts, e.specification_provider) unless conflicts.empty? - solver_name = "Bundler" - possibility_type = "gem" e.message_with_trees( - :solver_name => solver_name, - :possibility_type => possibility_type, - :reduce_trees => lambda do |trees| + :full_message_for_conflict => lambda do |name, conflict| + o = if name.end_with?("\0") + String.new("Bundler found conflicting requirements for the #{name} version:") + else + String.new("Bundler could not find compatible versions for gem \"#{name}\":") + end + o << %(\n) + if conflict.locked_requirement + o << %( In snapshot (#{name_for_locking_dependency_source}):\n) + o << %( #{SharedHelpers.pretty_dependency(conflict.locked_requirement)}\n) + o << %(\n) + end + o << %( In #{name_for_explicit_dependency_source}:\n) + trees = conflict.requirement_trees + # called first, because we want to reduce the amount of work required to find maximal empty sets trees = trees.uniq {|t| t.flatten.map {|dep| [dep.name, dep.requirement] } } # bail out if tree size is too big for Array#combination to make any sense - return trees if trees.size > 15 - maximal = 1.upto(trees.size).map do |size| - trees.map(&:last).flatten(1).combination(size).to_a - end.flatten(1).select do |deps| - Bundler::VersionRanges.empty?(*Bundler::VersionRanges.for_many(deps.map(&:requirement))) - end.min_by(&:size) - - trees.reject! {|t| !maximal.include?(t.last) } if maximal - - trees.sort_by {|t| t.reverse.map(&:name) } - end, - :printable_requirement => lambda {|req| SharedHelpers.pretty_dependency(req) }, - :additional_message_for_conflict => lambda do |o, name, conflict| + if trees.size <= 15 + maximal = 1.upto(trees.size).map do |size| + trees.map(&:last).flatten(1).combination(size).to_a + end.flatten(1).select do |deps| + Bundler::VersionRanges.empty?(*Bundler::VersionRanges.for_many(deps.map(&:requirement))) + end.min_by(&:size) + + trees.reject! {|t| !maximal.include?(t.last) } if maximal + + trees.sort_by! {|t| t.reverse.map(&:name) } + end + + metadata_requirements = {} + + o << trees.map do |tree| + t = "".dup + depth = 2 + + base_tree = tree.first + base_tree_name = base_tree.name + + if base_tree_name.end_with?("\0") + metadata_requirements[base_tree_name] = base_tree + t = nil + else + tree.each do |req| + t << " " * depth << SharedHelpers.pretty_dependency(req) + unless tree.last == req + if spec = conflict.activated_by_name[req.name] + t << %( was resolved to #{spec.version}, which) + end + t << %( depends on) + end + t << %(\n) + depth += 1 + end + end + t + end.compact.join("\n") + if name == "bundler" o << %(\n Current Bundler version:\n bundler (#{Bundler::VERSION})) @@ -355,11 +392,13 @@ module Bundler o << "Your bundle requires a different version of Bundler than the one you're running, and that version could not be found.\n" end end + elsif name.end_with?("\0") + o << %(\n Current #{name} version:\n #{SharedHelpers.pretty_dependency(metadata_requirements[name])}\n\n) elsif conflict.locked_requirement o << "\n" o << %(Running `bundle update` will rebuild your snapshot from scratch, using only\n) o << %(the gems in your Gemfile, which may resolve the conflict.\n) - elsif !conflict.existing && !name.end_with?("\0") + elsif !conflict.existing o << "\n" relevant_source = conflict.requirement.source || source_for(name) @@ -372,14 +411,8 @@ module Bundler o << gem_not_found_message(name, conflict.requirement, relevant_source, extra_message) end - end, - :version_for_spec => lambda {|spec| spec.version }, - :incompatible_version_message_for_conflict => lambda do |name, _conflict| - if name.end_with?("\0") - %(#{solver_name} found conflicting requirements for the #{name} version:) - else - %(#{solver_name} could not find compatible versions for #{possibility_type} "#{name}":) - end + + o end ) end diff --git a/lib/bundler/resolver/spec_group.rb b/lib/bundler/resolver/spec_group.rb index 232520de77..18ab5a52a3 100644 --- a/lib/bundler/resolver/spec_group.rb +++ b/lib/bundler/resolver/spec_group.rb @@ -97,10 +97,10 @@ module Bundler spec = @specs[platform].first return [] if spec.is_a?(LazySpecification) dependencies = [] - if !spec.required_ruby_version.nil? && !spec.required_ruby_version.none? + unless spec.required_ruby_version.none? dependencies << DepProxy.get_proxy(Gem::Dependency.new("Ruby\0", spec.required_ruby_version), platform) end - if !spec.required_rubygems_version.nil? && !spec.required_rubygems_version.none? + unless spec.required_rubygems_version.none? dependencies << DepProxy.get_proxy(Gem::Dependency.new("RubyGems\0", spec.required_rubygems_version), platform) end dependencies diff --git a/lib/bundler/ruby_version.rb b/lib/bundler/ruby_version.rb index d3b7963920..3f51cf4528 100644 --- a/lib/bundler/ruby_version.rb +++ b/lib/bundler/ruby_version.rb @@ -110,19 +110,6 @@ module Bundler @ruby_version ||= RubyVersion.new(ruby_version, patchlevel, ruby_engine, ruby_engine_version) end - def to_gem_version_with_patchlevel - @gem_version_with_patch ||= begin - Gem::Version.create("#{@gem_version}.#{@patchlevel}") - rescue ArgumentError - @gem_version - end - end - - def exact? - return @exact if defined?(@exact) - @exact = versions.all? {|v| Gem::Requirement.create(v).exact? } - end - private def matches?(requirements, version) diff --git a/lib/bundler/rubygems_ext.rb b/lib/bundler/rubygems_ext.rb index 78422ceb76..b0e35e005e 100644 --- a/lib/bundler/rubygems_ext.rb +++ b/lib/bundler/rubygems_ext.rb @@ -67,6 +67,23 @@ module Gem full_gem_path end + unless const_defined?(:LATEST_RUBY_WITHOUT_PATCH_VERSIONS) + LATEST_RUBY_WITHOUT_PATCH_VERSIONS = Gem::Version.new("2.1") + + alias_method :rg_required_ruby_version=, :required_ruby_version= + def required_ruby_version=(req) + self.rg_required_ruby_version = req + + @required_ruby_version.requirements.map! do |op, v| + if v >= LATEST_RUBY_WITHOUT_PATCH_VERSIONS && v.release.segments.size == 4 + [op == "~>" ? "=" : op, Gem::Version.new(v.segments.tap {|s| s.delete_at(3) }.join("."))] + else + [op, v] + end + end + end + end + def groups @groups ||= [] end diff --git a/lib/bundler/source/metadata.rb b/lib/bundler/source/metadata.rb index 524afd750a..185fe70824 100644 --- a/lib/bundler/source/metadata.rb +++ b/lib/bundler/source/metadata.rb @@ -5,7 +5,7 @@ module Bundler class Metadata < Source def specs @specs ||= Index.build do |idx| - idx << Gem::Specification.new("Ruby\0", RubyVersion.system.to_gem_version_with_patchlevel) + idx << Gem::Specification.new("Ruby\0", RubyVersion.system.gem_version) idx << Gem::Specification.new("RubyGems\0", Gem::VERSION) do |s| s.required_rubygems_version = Gem::Requirement.default end diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph.rb b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph.rb index 10a25d2f08..4d577213b9 100644 --- a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph.rb +++ b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph.rb @@ -32,7 +32,7 @@ module Bundler::Molinillo # all belong to the same graph. # @return [Array] The sorted vertices. def self.tsort(vertices) - TSort.tsort( + Bundler::TSort.tsort( lambda { |b| vertices.each(&b) }, lambda { |v, &b| (v.successors & vertices).each(&b) } ) diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/errors.rb b/lib/bundler/vendor/molinillo/lib/molinillo/errors.rb index e210202b69..8c8cafb447 100644 --- a/lib/bundler/vendor/molinillo/lib/molinillo/errors.rb +++ b/lib/bundler/vendor/molinillo/lib/molinillo/errors.rb @@ -107,36 +107,42 @@ module Bundler::Molinillo end end - conflicts.sort.reduce(''.dup) do |o, (name, conflict)| - o << "\n" << incompatible_version_message_for_conflict.call(name, conflict) << "\n" - if conflict.locked_requirement - o << %( In snapshot (#{name_for_locking_dependency_source}):\n) - o << %( #{printable_requirement.call(conflict.locked_requirement)}\n) - o << %(\n) - end - o << %( In #{name_for_explicit_dependency_source}:\n) - trees = reduce_trees.call(conflict.requirement_trees) - - o << trees.map do |tree| - t = ''.dup - depth = 2 - tree.each do |req| - t << ' ' * depth << printable_requirement.call(req) - unless tree.last == req - if spec = conflict.activated_by_name[name_for(req)] - t << %( was resolved to #{version_for_spec.call(spec)}, which) + full_message_for_conflict = opts.delete(:full_message_for_conflict) do + proc do |name, conflict| + o = "\n".dup << incompatible_version_message_for_conflict.call(name, conflict) << "\n" + if conflict.locked_requirement + o << %( In snapshot (#{name_for_locking_dependency_source}):\n) + o << %( #{printable_requirement.call(conflict.locked_requirement)}\n) + o << %(\n) + end + o << %( In #{name_for_explicit_dependency_source}:\n) + trees = reduce_trees.call(conflict.requirement_trees) + + o << trees.map do |tree| + t = ''.dup + depth = 2 + tree.each do |req| + t << ' ' * depth << printable_requirement.call(req) + unless tree.last == req + if spec = conflict.activated_by_name[name_for(req)] + t << %( was resolved to #{version_for_spec.call(spec)}, which) + end + t << %( depends on) end - t << %( depends on) + t << %(\n) + depth += 1 end - t << %(\n) - depth += 1 - end - t - end.join("\n") + t + end.join("\n") - additional_message_for_conflict.call(o, name, conflict) + additional_message_for_conflict.call(o, name, conflict) - o + o + end + end + + conflicts.sort.reduce(''.dup) do |o, (name, conflict)| + o << full_message_for_conflict.call(name, conflict) end.strip end end diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/gem_metadata.rb b/lib/bundler/vendor/molinillo/lib/molinillo/gem_metadata.rb index e13a781a50..a0cfc21672 100644 --- a/lib/bundler/vendor/molinillo/lib/molinillo/gem_metadata.rb +++ b/lib/bundler/vendor/molinillo/lib/molinillo/gem_metadata.rb @@ -2,5 +2,5 @@ module Bundler::Molinillo # The version of Bundler::Molinillo. - VERSION = '0.7.0'.freeze + VERSION = '0.8.0'.freeze end diff --git a/lib/bundler/vendor/tsort/lib/tsort.rb b/lib/bundler/vendor/tsort/lib/tsort.rb index 8454583295..4a0e1a4e25 100644 --- a/lib/bundler/vendor/tsort/lib/tsort.rb +++ b/lib/bundler/vendor/tsort/lib/tsort.rb @@ -6,24 +6,24 @@ # # -# TSort implements topological sorting using Tarjan's algorithm for +# Bundler::TSort implements topological sorting using Tarjan's algorithm for # strongly connected components. # -# TSort is designed to be able to be used with any object which can be +# Bundler::TSort is designed to be able to be used with any object which can be # interpreted as a directed graph. # -# TSort requires two methods to interpret an object as a graph, +# Bundler::TSort requires two methods to interpret an object as a graph, # tsort_each_node and tsort_each_child. # # * tsort_each_node is used to iterate for all nodes over a graph. # * tsort_each_child is used to iterate for child nodes of a given node. # # The equality of nodes are defined by eql? and hash since -# TSort uses Hash internally. +# Bundler::TSort uses Hash internally. # # == A Simple Example # -# The following example demonstrates how to mix the TSort module into an +# The following example demonstrates how to mix the Bundler::TSort module into an # existing class (in this case, Hash). Here, we're treating each key in # the hash as a node in the graph, and so we simply alias the required # #tsort_each_node method to Hash's #each_key method. For each key in the @@ -32,10 +32,10 @@ # method, which fetches the array of child nodes and then iterates over that # array using the user-supplied block. # -# require 'tsort' +# require 'bundler/vendor/tsort/lib/tsort' # # class Hash -# include TSort +# include Bundler::TSort # alias tsort_each_node each_key # def tsort_each_child(node, &block) # fetch(node).each(&block) @@ -52,7 +52,7 @@ # # A very simple `make' like tool can be implemented as follows: # -# require 'tsort' +# require 'bundler/vendor/tsort/lib/tsort' # # class Make # def initialize @@ -70,7 +70,7 @@ # each_strongly_connected_component_from(target) {|ns| # if ns.length != 1 # fs = ns.delete_if {|n| Array === n} -# raise TSort::Cyclic.new("cyclic dependencies: #{fs.join ', '}") +# raise Bundler::TSort::Cyclic.new("cyclic dependencies: #{fs.join ', '}") # end # n = ns.first # if Array === n @@ -93,7 +93,7 @@ # def tsort_each_child(node, &block) # @dep[node].each(&block) # end -# include TSort +# include Bundler::TSort # end # # def command(arg) @@ -120,334 +120,333 @@ # R. E. Tarjan, "Depth First Search and Linear Graph Algorithms", # SIAM Journal on Computing, Vol. 1, No. 2, pp. 146-160, June 1972. # -module Bundler - module TSort - class Cyclic < StandardError - end - # Returns a topologically sorted array of nodes. - # The array is sorted from children to parents, i.e. - # the first element has no child and the last node has no parent. - # - # If there is a cycle, TSort::Cyclic is raised. - # - # class G - # include TSort - # def initialize(g) - # @g = g - # end - # def tsort_each_child(n, &b) @g[n].each(&b) end - # def tsort_each_node(&b) @g.each_key(&b) end - # end - # - # graph = G.new({1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]}) - # p graph.tsort #=> [4, 2, 3, 1] - # - # graph = G.new({1=>[2], 2=>[3, 4], 3=>[2], 4=>[]}) - # p graph.tsort # raises TSort::Cyclic - # - def tsort - each_node = method(:tsort_each_node) - each_child = method(:tsort_each_child) - TSort.tsort(each_node, each_child) - end +module Bundler::TSort + class Cyclic < StandardError + end - # Returns a topologically sorted array of nodes. - # The array is sorted from children to parents, i.e. - # the first element has no child and the last node has no parent. - # - # The graph is represented by _each_node_ and _each_child_. - # _each_node_ should have +call+ method which yields for each node in the graph. - # _each_child_ should have +call+ method which takes a node argument and yields for each child node. - # - # If there is a cycle, TSort::Cyclic is raised. - # - # g = {1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]} - # each_node = lambda {|&b| g.each_key(&b) } - # each_child = lambda {|n, &b| g[n].each(&b) } - # p TSort.tsort(each_node, each_child) #=> [4, 2, 3, 1] - # - # g = {1=>[2], 2=>[3, 4], 3=>[2], 4=>[]} - # each_node = lambda {|&b| g.each_key(&b) } - # each_child = lambda {|n, &b| g[n].each(&b) } - # p TSort.tsort(each_node, each_child) # raises TSort::Cyclic - # - def TSort.tsort(each_node, each_child) - TSort.tsort_each(each_node, each_child).to_a - end + # Returns a topologically sorted array of nodes. + # The array is sorted from children to parents, i.e. + # the first element has no child and the last node has no parent. + # + # If there is a cycle, Bundler::TSort::Cyclic is raised. + # + # class G + # include Bundler::TSort + # def initialize(g) + # @g = g + # end + # def tsort_each_child(n, &b) @g[n].each(&b) end + # def tsort_each_node(&b) @g.each_key(&b) end + # end + # + # graph = G.new({1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]}) + # p graph.tsort #=> [4, 2, 3, 1] + # + # graph = G.new({1=>[2], 2=>[3, 4], 3=>[2], 4=>[]}) + # p graph.tsort # raises Bundler::TSort::Cyclic + # + def tsort + each_node = method(:tsort_each_node) + each_child = method(:tsort_each_child) + Bundler::TSort.tsort(each_node, each_child) + end - # The iterator version of the #tsort method. - # obj.tsort_each is similar to obj.tsort.each, but - # modification of _obj_ during the iteration may lead to unexpected results. - # - # #tsort_each returns +nil+. - # If there is a cycle, TSort::Cyclic is raised. - # - # class G - # include TSort - # def initialize(g) - # @g = g - # end - # def tsort_each_child(n, &b) @g[n].each(&b) end - # def tsort_each_node(&b) @g.each_key(&b) end - # end - # - # graph = G.new({1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]}) - # graph.tsort_each {|n| p n } - # #=> 4 - # # 2 - # # 3 - # # 1 - # - def tsort_each(&block) # :yields: node - each_node = method(:tsort_each_node) - each_child = method(:tsort_each_child) - TSort.tsort_each(each_node, each_child, &block) - end + # Returns a topologically sorted array of nodes. + # The array is sorted from children to parents, i.e. + # the first element has no child and the last node has no parent. + # + # The graph is represented by _each_node_ and _each_child_. + # _each_node_ should have +call+ method which yields for each node in the graph. + # _each_child_ should have +call+ method which takes a node argument and yields for each child node. + # + # If there is a cycle, Bundler::TSort::Cyclic is raised. + # + # g = {1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]} + # each_node = lambda {|&b| g.each_key(&b) } + # each_child = lambda {|n, &b| g[n].each(&b) } + # p Bundler::TSort.tsort(each_node, each_child) #=> [4, 2, 3, 1] + # + # g = {1=>[2], 2=>[3, 4], 3=>[2], 4=>[]} + # each_node = lambda {|&b| g.each_key(&b) } + # each_child = lambda {|n, &b| g[n].each(&b) } + # p Bundler::TSort.tsort(each_node, each_child) # raises Bundler::TSort::Cyclic + # + def self.tsort(each_node, each_child) + tsort_each(each_node, each_child).to_a + end - # The iterator version of the TSort.tsort method. - # - # The graph is represented by _each_node_ and _each_child_. - # _each_node_ should have +call+ method which yields for each node in the graph. - # _each_child_ should have +call+ method which takes a node argument and yields for each child node. - # - # g = {1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]} - # each_node = lambda {|&b| g.each_key(&b) } - # each_child = lambda {|n, &b| g[n].each(&b) } - # TSort.tsort_each(each_node, each_child) {|n| p n } - # #=> 4 - # # 2 - # # 3 - # # 1 - # - def TSort.tsort_each(each_node, each_child) # :yields: node - return to_enum(__method__, each_node, each_child) unless block_given? + # The iterator version of the #tsort method. + # obj.tsort_each is similar to obj.tsort.each, but + # modification of _obj_ during the iteration may lead to unexpected results. + # + # #tsort_each returns +nil+. + # If there is a cycle, Bundler::TSort::Cyclic is raised. + # + # class G + # include Bundler::TSort + # def initialize(g) + # @g = g + # end + # def tsort_each_child(n, &b) @g[n].each(&b) end + # def tsort_each_node(&b) @g.each_key(&b) end + # end + # + # graph = G.new({1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]}) + # graph.tsort_each {|n| p n } + # #=> 4 + # # 2 + # # 3 + # # 1 + # + def tsort_each(&block) # :yields: node + each_node = method(:tsort_each_node) + each_child = method(:tsort_each_child) + Bundler::TSort.tsort_each(each_node, each_child, &block) + end - TSort.each_strongly_connected_component(each_node, each_child) {|component| - if component.size == 1 - yield component.first - else - raise Cyclic.new("topological sort failed: #{component.inspect}") - end - } - end + # The iterator version of the Bundler::TSort.tsort method. + # + # The graph is represented by _each_node_ and _each_child_. + # _each_node_ should have +call+ method which yields for each node in the graph. + # _each_child_ should have +call+ method which takes a node argument and yields for each child node. + # + # g = {1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]} + # each_node = lambda {|&b| g.each_key(&b) } + # each_child = lambda {|n, &b| g[n].each(&b) } + # Bundler::TSort.tsort_each(each_node, each_child) {|n| p n } + # #=> 4 + # # 2 + # # 3 + # # 1 + # + def self.tsort_each(each_node, each_child) # :yields: node + return to_enum(__method__, each_node, each_child) unless block_given? - # Returns strongly connected components as an array of arrays of nodes. - # The array is sorted from children to parents. - # Each elements of the array represents a strongly connected component. - # - # class G - # include TSort - # def initialize(g) - # @g = g - # end - # def tsort_each_child(n, &b) @g[n].each(&b) end - # def tsort_each_node(&b) @g.each_key(&b) end - # end - # - # graph = G.new({1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]}) - # p graph.strongly_connected_components #=> [[4], [2], [3], [1]] - # - # graph = G.new({1=>[2], 2=>[3, 4], 3=>[2], 4=>[]}) - # p graph.strongly_connected_components #=> [[4], [2, 3], [1]] - # - def strongly_connected_components - each_node = method(:tsort_each_node) - each_child = method(:tsort_each_child) - TSort.strongly_connected_components(each_node, each_child) - end + each_strongly_connected_component(each_node, each_child) {|component| + if component.size == 1 + yield component.first + else + raise Cyclic.new("topological sort failed: #{component.inspect}") + end + } + end - # Returns strongly connected components as an array of arrays of nodes. - # The array is sorted from children to parents. - # Each elements of the array represents a strongly connected component. - # - # The graph is represented by _each_node_ and _each_child_. - # _each_node_ should have +call+ method which yields for each node in the graph. - # _each_child_ should have +call+ method which takes a node argument and yields for each child node. - # - # g = {1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]} - # each_node = lambda {|&b| g.each_key(&b) } - # each_child = lambda {|n, &b| g[n].each(&b) } - # p TSort.strongly_connected_components(each_node, each_child) - # #=> [[4], [2], [3], [1]] - # - # g = {1=>[2], 2=>[3, 4], 3=>[2], 4=>[]} - # each_node = lambda {|&b| g.each_key(&b) } - # each_child = lambda {|n, &b| g[n].each(&b) } - # p TSort.strongly_connected_components(each_node, each_child) - # #=> [[4], [2, 3], [1]] - # - def TSort.strongly_connected_components(each_node, each_child) - TSort.each_strongly_connected_component(each_node, each_child).to_a - end + # Returns strongly connected components as an array of arrays of nodes. + # The array is sorted from children to parents. + # Each elements of the array represents a strongly connected component. + # + # class G + # include Bundler::TSort + # def initialize(g) + # @g = g + # end + # def tsort_each_child(n, &b) @g[n].each(&b) end + # def tsort_each_node(&b) @g.each_key(&b) end + # end + # + # graph = G.new({1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]}) + # p graph.strongly_connected_components #=> [[4], [2], [3], [1]] + # + # graph = G.new({1=>[2], 2=>[3, 4], 3=>[2], 4=>[]}) + # p graph.strongly_connected_components #=> [[4], [2, 3], [1]] + # + def strongly_connected_components + each_node = method(:tsort_each_node) + each_child = method(:tsort_each_child) + Bundler::TSort.strongly_connected_components(each_node, each_child) + end - # The iterator version of the #strongly_connected_components method. - # obj.each_strongly_connected_component is similar to - # obj.strongly_connected_components.each, but - # modification of _obj_ during the iteration may lead to unexpected results. - # - # #each_strongly_connected_component returns +nil+. - # - # class G - # include TSort - # def initialize(g) - # @g = g - # end - # def tsort_each_child(n, &b) @g[n].each(&b) end - # def tsort_each_node(&b) @g.each_key(&b) end - # end - # - # graph = G.new({1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]}) - # graph.each_strongly_connected_component {|scc| p scc } - # #=> [4] - # # [2] - # # [3] - # # [1] - # - # graph = G.new({1=>[2], 2=>[3, 4], 3=>[2], 4=>[]}) - # graph.each_strongly_connected_component {|scc| p scc } - # #=> [4] - # # [2, 3] - # # [1] - # - def each_strongly_connected_component(&block) # :yields: nodes - each_node = method(:tsort_each_node) - each_child = method(:tsort_each_child) - TSort.each_strongly_connected_component(each_node, each_child, &block) - end + # Returns strongly connected components as an array of arrays of nodes. + # The array is sorted from children to parents. + # Each elements of the array represents a strongly connected component. + # + # The graph is represented by _each_node_ and _each_child_. + # _each_node_ should have +call+ method which yields for each node in the graph. + # _each_child_ should have +call+ method which takes a node argument and yields for each child node. + # + # g = {1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]} + # each_node = lambda {|&b| g.each_key(&b) } + # each_child = lambda {|n, &b| g[n].each(&b) } + # p Bundler::TSort.strongly_connected_components(each_node, each_child) + # #=> [[4], [2], [3], [1]] + # + # g = {1=>[2], 2=>[3, 4], 3=>[2], 4=>[]} + # each_node = lambda {|&b| g.each_key(&b) } + # each_child = lambda {|n, &b| g[n].each(&b) } + # p Bundler::TSort.strongly_connected_components(each_node, each_child) + # #=> [[4], [2, 3], [1]] + # + def self.strongly_connected_components(each_node, each_child) + each_strongly_connected_component(each_node, each_child).to_a + end - # The iterator version of the TSort.strongly_connected_components method. - # - # The graph is represented by _each_node_ and _each_child_. - # _each_node_ should have +call+ method which yields for each node in the graph. - # _each_child_ should have +call+ method which takes a node argument and yields for each child node. - # - # g = {1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]} - # each_node = lambda {|&b| g.each_key(&b) } - # each_child = lambda {|n, &b| g[n].each(&b) } - # TSort.each_strongly_connected_component(each_node, each_child) {|scc| p scc } - # #=> [4] - # # [2] - # # [3] - # # [1] - # - # g = {1=>[2], 2=>[3, 4], 3=>[2], 4=>[]} - # each_node = lambda {|&b| g.each_key(&b) } - # each_child = lambda {|n, &b| g[n].each(&b) } - # TSort.each_strongly_connected_component(each_node, each_child) {|scc| p scc } - # #=> [4] - # # [2, 3] - # # [1] - # - def TSort.each_strongly_connected_component(each_node, each_child) # :yields: nodes - return to_enum(__method__, each_node, each_child) unless block_given? + # The iterator version of the #strongly_connected_components method. + # obj.each_strongly_connected_component is similar to + # obj.strongly_connected_components.each, but + # modification of _obj_ during the iteration may lead to unexpected results. + # + # #each_strongly_connected_component returns +nil+. + # + # class G + # include Bundler::TSort + # def initialize(g) + # @g = g + # end + # def tsort_each_child(n, &b) @g[n].each(&b) end + # def tsort_each_node(&b) @g.each_key(&b) end + # end + # + # graph = G.new({1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]}) + # graph.each_strongly_connected_component {|scc| p scc } + # #=> [4] + # # [2] + # # [3] + # # [1] + # + # graph = G.new({1=>[2], 2=>[3, 4], 3=>[2], 4=>[]}) + # graph.each_strongly_connected_component {|scc| p scc } + # #=> [4] + # # [2, 3] + # # [1] + # + def each_strongly_connected_component(&block) # :yields: nodes + each_node = method(:tsort_each_node) + each_child = method(:tsort_each_child) + Bundler::TSort.each_strongly_connected_component(each_node, each_child, &block) + end - id_map = {} - stack = [] - each_node.call {|node| - unless id_map.include? node - TSort.each_strongly_connected_component_from(node, each_child, id_map, stack) {|c| - yield c - } - end - } - nil - end + # The iterator version of the Bundler::TSort.strongly_connected_components method. + # + # The graph is represented by _each_node_ and _each_child_. + # _each_node_ should have +call+ method which yields for each node in the graph. + # _each_child_ should have +call+ method which takes a node argument and yields for each child node. + # + # g = {1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]} + # each_node = lambda {|&b| g.each_key(&b) } + # each_child = lambda {|n, &b| g[n].each(&b) } + # Bundler::TSort.each_strongly_connected_component(each_node, each_child) {|scc| p scc } + # #=> [4] + # # [2] + # # [3] + # # [1] + # + # g = {1=>[2], 2=>[3, 4], 3=>[2], 4=>[]} + # each_node = lambda {|&b| g.each_key(&b) } + # each_child = lambda {|n, &b| g[n].each(&b) } + # Bundler::TSort.each_strongly_connected_component(each_node, each_child) {|scc| p scc } + # #=> [4] + # # [2, 3] + # # [1] + # + def self.each_strongly_connected_component(each_node, each_child) # :yields: nodes + return to_enum(__method__, each_node, each_child) unless block_given? - # Iterates over strongly connected component in the subgraph reachable from - # _node_. - # - # Return value is unspecified. - # - # #each_strongly_connected_component_from doesn't call #tsort_each_node. - # - # class G - # include TSort - # def initialize(g) - # @g = g - # end - # def tsort_each_child(n, &b) @g[n].each(&b) end - # def tsort_each_node(&b) @g.each_key(&b) end - # end - # - # graph = G.new({1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]}) - # graph.each_strongly_connected_component_from(2) {|scc| p scc } - # #=> [4] - # # [2] - # - # graph = G.new({1=>[2], 2=>[3, 4], 3=>[2], 4=>[]}) - # graph.each_strongly_connected_component_from(2) {|scc| p scc } - # #=> [4] - # # [2, 3] - # - def each_strongly_connected_component_from(node, id_map={}, stack=[], &block) # :yields: nodes - TSort.each_strongly_connected_component_from(node, method(:tsort_each_child), id_map, stack, &block) - end + id_map = {} + stack = [] + each_node.call {|node| + unless id_map.include? node + each_strongly_connected_component_from(node, each_child, id_map, stack) {|c| + yield c + } + end + } + nil + end - # Iterates over strongly connected components in a graph. - # The graph is represented by _node_ and _each_child_. - # - # _node_ is the first node. - # _each_child_ should have +call+ method which takes a node argument - # and yields for each child node. - # - # Return value is unspecified. - # - # #TSort.each_strongly_connected_component_from is a class method and - # it doesn't need a class to represent a graph which includes TSort. - # - # graph = {1=>[2], 2=>[3, 4], 3=>[2], 4=>[]} - # each_child = lambda {|n, &b| graph[n].each(&b) } - # TSort.each_strongly_connected_component_from(1, each_child) {|scc| - # p scc - # } - # #=> [4] - # # [2, 3] - # # [1] - # - def TSort.each_strongly_connected_component_from(node, each_child, id_map={}, stack=[]) # :yields: nodes - return to_enum(__method__, node, each_child, id_map, stack) unless block_given? + # Iterates over strongly connected component in the subgraph reachable from + # _node_. + # + # Return value is unspecified. + # + # #each_strongly_connected_component_from doesn't call #tsort_each_node. + # + # class G + # include Bundler::TSort + # def initialize(g) + # @g = g + # end + # def tsort_each_child(n, &b) @g[n].each(&b) end + # def tsort_each_node(&b) @g.each_key(&b) end + # end + # + # graph = G.new({1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]}) + # graph.each_strongly_connected_component_from(2) {|scc| p scc } + # #=> [4] + # # [2] + # + # graph = G.new({1=>[2], 2=>[3, 4], 3=>[2], 4=>[]}) + # graph.each_strongly_connected_component_from(2) {|scc| p scc } + # #=> [4] + # # [2, 3] + # + def each_strongly_connected_component_from(node, id_map={}, stack=[], &block) # :yields: nodes + Bundler::TSort.each_strongly_connected_component_from(node, method(:tsort_each_child), id_map, stack, &block) + end - minimum_id = node_id = id_map[node] = id_map.size - stack_length = stack.length - stack << node + # Iterates over strongly connected components in a graph. + # The graph is represented by _node_ and _each_child_. + # + # _node_ is the first node. + # _each_child_ should have +call+ method which takes a node argument + # and yields for each child node. + # + # Return value is unspecified. + # + # #Bundler::TSort.each_strongly_connected_component_from is a class method and + # it doesn't need a class to represent a graph which includes Bundler::TSort. + # + # graph = {1=>[2], 2=>[3, 4], 3=>[2], 4=>[]} + # each_child = lambda {|n, &b| graph[n].each(&b) } + # Bundler::TSort.each_strongly_connected_component_from(1, each_child) {|scc| + # p scc + # } + # #=> [4] + # # [2, 3] + # # [1] + # + def self.each_strongly_connected_component_from(node, each_child, id_map={}, stack=[]) # :yields: nodes + return to_enum(__method__, node, each_child, id_map, stack) unless block_given? - each_child.call(node) {|child| - if id_map.include? child - child_id = id_map[child] - minimum_id = child_id if child_id && child_id < minimum_id - else - sub_minimum_id = - TSort.each_strongly_connected_component_from(child, each_child, id_map, stack) {|c| - yield c - } - minimum_id = sub_minimum_id if sub_minimum_id < minimum_id - end - } + minimum_id = node_id = id_map[node] = id_map.size + stack_length = stack.length + stack << node - if node_id == minimum_id - component = stack.slice!(stack_length .. -1) - component.each {|n| id_map[n] = nil} - yield component + each_child.call(node) {|child| + if id_map.include? child + child_id = id_map[child] + minimum_id = child_id if child_id && child_id < minimum_id + else + sub_minimum_id = + each_strongly_connected_component_from(child, each_child, id_map, stack) {|c| + yield c + } + minimum_id = sub_minimum_id if sub_minimum_id < minimum_id end + } - minimum_id + if node_id == minimum_id + component = stack.slice!(stack_length .. -1) + component.each {|n| id_map[n] = nil} + yield component end - # Should be implemented by a extended class. - # - # #tsort_each_node is used to iterate for all nodes over a graph. - # - def tsort_each_node # :yields: node - raise NotImplementedError.new - end + minimum_id + end - # Should be implemented by a extended class. - # - # #tsort_each_child is used to iterate for child nodes of _node_. - # - def tsort_each_child(node) # :yields: child - raise NotImplementedError.new - end + # Should be implemented by a extended class. + # + # #tsort_each_node is used to iterate for all nodes over a graph. + # + def tsort_each_node # :yields: node + raise NotImplementedError.new + end + + # Should be implemented by a extended class. + # + # #tsort_each_child is used to iterate for child nodes of _node_. + # + def tsort_each_child(node) # :yields: child + raise NotImplementedError.new end end -- cgit v1.2.3