Permalink
Browse files

Detect cycles in graph using TSort.

  • Loading branch information...
1 parent 21a4bf6 commit 879b38643e4732bb97dcc3cff8d81bcf8e727992 @threedaymonk threedaymonk committed Mar 22, 2012
Showing with 41 additions and 3 deletions.
  1. +28 −3 lib/sibyl/graph.rb
  2. +13 −0 test/graph_validation_test.rb
View
@@ -1,20 +1,26 @@
require "sibyl/parser"
require "sibyl/ruby_transform"
+require "tsort"
module Sibyl
class Graph
+ include TSort
+
attr_reader :metadata
def initialize(source)
elements = parse(source)
@metadata = extract_metadata(elements)
@steps = extract_steps(elements)
+ @first_step = @steps.first
+ @steps_by_name = Hash[@steps.map { |s| [s.name, s] }]
end
def valid?
@steps.any? &&
!has_unreachable_steps? &&
- !has_unresolved_targets?
+ !has_unresolved_targets? &&
+ !has_cycles?
end
def l10n_keys
@@ -25,15 +31,34 @@ def l10n_keys
private
def has_unreachable_steps?
- (step_names - target_names - [@steps.first.name]).any?
+ (step_names - target_names - [first_step_name]).any?
end
def has_unresolved_targets?
(target_names - step_names).any?
end
+ def has_cycles?
+ tsort
+ false
+ rescue TSort::Cyclic
+ true
+ end
+
+ def first_step_name
+ @first_step.name
+ end
+
def step_names
- @steps.map(&:name)
+ @steps_by_name.keys
+ end
+
+ def tsort_each_node(&blk)
+ step_names.each(&blk)
+ end
+
+ def tsort_each_child(node, &blk)
+ @steps_by_name[node].exits.each(&blk)
end
def target_names
@@ -44,4 +44,17 @@ def graph(source)
refute g.valid?
end
+
+ it "should be invalid if there are cycles" do
+ g = graph(%{
+ step option a
+ option foo -> b
+ option bar -> c
+ step number b
+ go -> a
+ outcome c
+ })
+
+ refute g.valid?
+ end
end

0 comments on commit 879b386

Please sign in to comment.