Sha256: 6471442375782804f5f00eada39ffbde65c49be2012d56fa320b802dde9e98d3

Contents?: true

Size: 1.76 KB

Versions: 11

Compression:

Stored size: 1.76 KB

Contents

# typed: true
# frozen_string_literal: true

module Packwerk
  class Graph
    def initialize(*edges)
      @edges = edges.uniq
      @cycles = Set.new
      process
    end

    def cycles
      @cycles.dup
    end

    def acyclic?
      @cycles.empty?
    end

    private

    def nodes
      @edges.flatten.uniq
    end

    def process
      # See https://en.wikipedia.org/wiki/Topological_sorting#Depth-first_search
      @processed ||= begin
        nodes.each { |node| visit(node) }
        true
      end
    end

    def visit(node, visited_nodes: Set.new, path: [])
      # Already visited, short circuit to avoid unnecessary processing
      return if visited_nodes.include?(node)

      # We've returned to a node that we've already visited, so we've found a cycle!
      if path.include?(node)
        # Filter out the part of the path that isn't a cycle. For example, with the following path:
        #
        #   a -> b -> c -> d -> b
        #
        # "a" isn't part of the cycle. The cycle should only appear once in the path, so we reject
        # everything from the beginning to the first instance of the current node.
        add_cycle(path.drop_while { |n| n != node })
        return
      end

      path << node
      neighbours(node).each do |neighbour|
        visit(neighbour, visited_nodes: visited_nodes, path: path)
      end
      path.pop
    ensure
      visited_nodes << node
    end

    def neighbours(node)
      @edges
        .lazy
        .select { |src, _dst| src == node }
        .map { |_src, dst| dst }
    end

    def add_cycle(cycle)
      # Ensure that the lexicographically smallest item is the first one labeled in a cycle
      min_node = cycle.min
      cycle.rotate! until cycle.first == min_node

      @cycles << cycle
    end
  end
end

Version data entries

11 entries across 11 versions & 1 rubygems

Version Path
packwerk-1.3.2 lib/packwerk/graph.rb
packwerk-1.3.1 lib/packwerk/graph.rb
packwerk-1.3.0 lib/packwerk/graph.rb
packwerk-1.2.0 lib/packwerk/graph.rb
packwerk-1.1.3 lib/packwerk/graph.rb
packwerk-1.1.2 lib/packwerk/graph.rb
packwerk-1.1.1 lib/packwerk/graph.rb
packwerk-1.1.0 lib/packwerk/graph.rb
packwerk-1.0.2 lib/packwerk/graph.rb
packwerk-1.0.1 lib/packwerk/graph.rb
packwerk-1.0.0 lib/packwerk/graph.rb