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 [Object] requirement The requirement the directed edge representsEdge=Struct.new(:origin,:destination,:requirement)# @return [{String => Vertex}] the vertices of the dependency graph, keyed# by {Vertex#name}attr_reader:verticesdefinitialize@vertices={}end# Initializes a copy of a {DependencyGraph}, ensuring that all {#vertices}# are properly copied.definitialize_copy(other)super@vertices={}traverse=lambdado|new_v,old_v|returnifnew_v.outgoing_edges.size==old_v.outgoing_edges.sizeold_v.outgoing_edges.eachdo|edge|destination=add_vertex(edge.destination.name,edge.destination.payload)add_edge_no_circular(new_v,destination,edge.requirement)traverse.call(destination,edge.destination)endendother.vertices.eachdo|name,vertex|new_vertex=add_vertex(name,vertex.payload,vertex.root?)new_vertex.explicit_requirements.replace(vertex.explicit_requirements)traverse.call(new_vertex,vertex)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)returnfalseunlessothervertices.eachdo|name,vertex|other_vertex=other.vertex_named(name)returnfalseunlessother_vertexreturnfalseunlessother_vertex.successors.map(&:name).to_set==vertex.successors.map(&:name).to_setendend# @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)vertex=add_vertex(name,payload)parent_names.eachdo|parent_name|unlessparent_namevertex.root=truenextendparent_node=vertex_named(parent_name)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,root=false)vertex=vertices[name]||=Vertex.new(name,payload)vertex.payload||=payloadvertex.root||=rootvertexend# 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)returnunlessvertex=vertices.delete(name)vertex.outgoing_edges.eachdo|e|v=e.destinationv.incoming_edges.delete(e)detach_vertex_named(v.name)unlessv.root?||v.predecessors.any?endend# @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)vertex=vertex_named(name)vertexifvertex&&vertex.root?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)ifdestination.path_to?(origin)raiseCircularDependencyError.new([origin,destination])endadd_edge_no_circular(origin,destination,requirement)endprivatedefadd_edge_no_circular(origin,destination,requirement)edge=Edge.new(origin,destination,requirement)origin.outgoing_edges<<edgedestination.incoming_edges<<edgeedgeend# A vertex in a {DependencyGraph} that encapsulates a {#name} and a# {#payload}classVertex# @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# @return [Boolean] whether the vertex is considered a root vertexattr_accessor:rootalias_method:root?,:root# @param [String] name see {#name}# @param [Object] payload see {#payload}definitialize(name,payload)@name=name@payload=payload@explicit_requirements=[]@outgoing_edges=[]@incoming_edges=[]end# @return [Array<Object>] all of the requirements that required# this vertexdefrequirementsincoming_edges.map(&:requirement)+explicit_requirementsend# @return [Array<Edge>] the edges of {#graph} that have `self` as their# {Edge#origin}attr_accessor:outgoing_edges# @return [Array<Edge>] the edges of {#graph} that have `self` as their# {Edge#destination}attr_accessor:incoming_edges# @return [Array<Vertex>] the vertices of {#graph} that have an edge with# `self` as their {Edge#destination}defpredecessorsincoming_edges.map(&:origin)end# @return [Array<Vertex>] the vertices of {#graph} where `self` is a# {#descendent?}defrecursive_predecessorsvertices=predecessorsvertices+=vertices.map(&:recursive_predecessors).flatten(1)vertices.uniq!verticesend# @return [Array<Vertex>] the vertices of {#graph} that have an edge with# `self` as their {Edge#origin}defsuccessorsoutgoing_edges.map(&:destination)end# @return [Array<Vertex>] the vertices of {#graph} where `self` is an# {#ancestor?}defrecursive_successorsvertices=successorsvertices+=vertices.map(&:recursive_successors).flatten(1)vertices.uniq!verticesend# @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.to_set==other.successors.to_setend# @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)equal?(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)other.path_to?(self)endalias_method:is_reachable_from?,:ancestor?endendend