require'set'require'tsort'moduleBundler::Molinillo# A directed acyclic graph that is tuned to hold named dependenciesclassDependencyGraphincludeEnumerable# Enumerates through the vertices of the graph.# @return [Array<Vertex>] The graph's vertices.defeachvertices.values.each{|v|yieldv}endincludeTSortalias_method:tsort_each_node,:eachdeftsort_each_child(vertex,&block)vertex.successors.each(&block)end# Topologically sorts the given vertices.# @param [Enumerable<Vertex>] vertices the vertices to be sorted, which must# all belong to the same graph.# @return [Array<Vertex>] The sorted vertices.defself.tsort(vertices)TSort.tsort(lambda{|b|vertices.each(&b)},lambda{|v,&b|(v.successors&vertices).each(&b)})end# A directed edge of a {DependencyGraph}# @attr [Vertex] origin The origin of the directed edge# @attr [Vertex] destination The destination of the directed edge# @attr [Array] requirements The requirements the directed edge representsEdge=Struct.new(:origin,:destination,:requirements)# @return [{String => Vertex}] vertices that have no {Vertex#predecessors},# keyed by by {Vertex#name}attr_reader:root_vertices# @return [{String => Vertex}] the vertices of the dependency graph, keyed# by {Vertex#name}attr_reader:vertices# @return [Set<Edge>] the edges of the dependency graphattr_reader:edgesdefinitialize@vertices={}@edges=Set.new@root_vertices={}end# Initializes a copy of a {DependencyGraph}, ensuring that all {#vertices}# have the correct {Vertex#graph} setdefinitialize_copy(other)super@vertices=other.vertices.reduce({})do|vertices,(name,vertex)|vertices.tapdo|hash|hash[name]=vertex.dup.tap{|v|v.graph=self}endend@root_vertices=Hash[@vertices.select{|n,_v|other.root_vertices[n]}]@edges=other.edges.mapdo|edge|Edge.new(vertex_named(edge.origin.name),vertex_named(edge.destination.name),edge.requirements.dup)endend# @return [String] a string suitable for debuggingdefinspect"#{self.class}:#{vertices.values.inspect}"end# @return [Boolean] whether the two dependency graphs are equal, determined# by a recursive traversal of each {#root_vertices} and its# {Vertex#successors}def==(other)root_vertices==other.root_verticesend# @param [String] name# @param [Object] payload# @param [Array<String>] parent_names# @param [Object] requirement the requirement that is requiring the child# @return [void]defadd_child_vertex(name,payload,parent_names,requirement)is_root=parent_names.include?(nil)parent_nodes=parent_names.compact.map{|n|vertex_named(n)}vertex=vertex_named(name)||ifis_rootadd_root_vertex(name,payload)elseadd_vertex(name,payload)endvertex.payload||=payloadparent_nodes.eachdo|parent_node|add_edge(parent_node,vertex,requirement)endvertexend# @param [String] name# @param [Object] payload# @return [Vertex] the vertex that was added to `self`defadd_vertex(name,payload)vertex=vertices[name]||=Vertex.new(self,name,payload)vertex.tap{|v|v.payload=payload}end# @param [String] name# @param [Object] payload# @return [Vertex] the vertex that was added to `self`defadd_root_vertex(name,payload)add_vertex(name,payload).tap{|v|root_vertices[name]=v}end# Detaches the {#vertex_named} `name` {Vertex} from the graph, recursively# removing any non-root vertices that were orphaned in the process# @param [String] name# @return [void]defdetach_vertex_named(name)vertex=vertex_named(name)returnunlessvertexsuccessors=vertex.successorsvertices.delete(name)edges.reject!{|e|e.origin==vertex||e.destination==vertex}successors.each{|v|detach_vertex_named(v.name)unlessroot_vertices[v.name]||v.predecessors.any?}end# @param [String] name# @return [Vertex,nil] the vertex with the given namedefvertex_named(name)vertices[name]end# @param [String] name# @return [Vertex,nil] the root vertex with the given namedefroot_vertex_named(name)root_vertices[name]end# Adds a new {Edge} to the dependency graph# @param [Vertex] origin# @param [Vertex] destination# @param [Object] requirement the requirement that this edge represents# @return [Edge] the added edgedefadd_edge(origin,destination,requirement)iforigin==destination||destination.path_to?(origin)raiseCircularDependencyError.new([origin,destination])endEdge.new(origin,destination,[requirement]).tap{|e|edges<<e}end# A vertex in a {DependencyGraph} that encapsulates a {#name} and a# {#payload}classVertex# @return [DependencyGraph] the graph this vertex is a node ofattr_accessor:graph# @return [String] the name of the vertexattr_accessor:name# @return [Object] the payload the vertex holdsattr_accessor:payload# @return [Arrary<Object>] the explicit requirements that required# this vertexattr_reader:explicit_requirements# @param [DependencyGraph] graph see {#graph}# @param [String] name see {#name}# @param [Object] payload see {#payload}definitialize(graph,name,payload)@graph=graph@name=name@payload=payload@explicit_requirements=[]end# @return [Array<Object>] all of the requirements that required# this vertexdefrequirementsincoming_edges.map(&:requirements).flatten+explicit_requirementsend# @return [Array<Edge>] the edges of {#graph} that have `self` as their# {Edge#origin}defoutgoing_edgesgraph.edges.select{|e|e.origin.shallow_eql?(self)}end# @return [Array<Edge>] the edges of {#graph} that have `self` as their# {Edge#destination}defincoming_edgesgraph.edges.select{|e|e.destination.shallow_eql?(self)}end# @return [Set<Vertex>] the vertices of {#graph} that have an edge with# `self` as their {Edge#destination}defpredecessorsincoming_edges.map(&:origin).to_setend# @return [Set<Vertex>] the vertices of {#graph} that have an edge with# `self` as their {Edge#origin}defsuccessorsoutgoing_edges.map(&:destination).to_setend# @return [Set<Vertex>] the vertices of {#graph} where `self` is an# {#ancestor?}defrecursive_successorssuccessors+successors.map(&:recursive_successors).reduce(Set.new,&:+)end# @return [String] a string suitable for debuggingdefinspect"#{self.class}:#{name}(#{payload.inspect})"end# @return [Boolean] whether the two vertices are equal, determined# by a recursive traversal of each {Vertex#successors}def==(other)shallow_eql?(other)&&successors==other.successorsend# @return [Boolean] whether the two vertices are equal, determined# solely by {#name} and {#payload} equalitydefshallow_eql?(other)other&&name==other.name&&payload==other.payloadendalias_method:eql?,:==# @return [Fixnum] a hash for the vertex based upon its {#name}defhashname.hashend# Is there a path from `self` to `other` following edges in the# dependency graph?# @return true iff there is a path following edges within this {#graph}defpath_to?(other)successors.include?(other)||successors.any?{|v|v.path_to?(other)}endalias_method:descendent?,:path_to?# Is there a path from `other` to `self` following edges in the# dependency graph?# @return true iff there is a path following edges within this {#graph}defancestor?(other)predecessors.include?(other)||predecessors.any?{|v|v.ancestor?(other)}endalias_method:is_reachable_from?,:ancestor?endendend