authorize 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. data/.gitignore +5 -0
  2. data/Gemfile +3 -0
  3. data/Gemfile.lock +42 -0
  4. data/LICENSE +20 -0
  5. data/README +155 -0
  6. data/Rakefile +25 -0
  7. data/TODO.txt +9 -0
  8. data/authorize.gemspec +25 -0
  9. data/generators/authorize/USAGE +8 -0
  10. data/generators/authorize/authorize_generator.rb +7 -0
  11. data/generators/authorize/templates/migrate/create_authorizations.rb +26 -0
  12. data/install.rb +1 -0
  13. data/lib/authorize.rb +2 -0
  14. data/lib/authorize/action_controller.rb +59 -0
  15. data/lib/authorize/action_view.rb +4 -0
  16. data/lib/authorize/active_record.rb +37 -0
  17. data/lib/authorize/bitmask.rb +84 -0
  18. data/lib/authorize/exceptions.rb +30 -0
  19. data/lib/authorize/graph.rb +4 -0
  20. data/lib/authorize/graph/directed_acyclic_graph.rb +10 -0
  21. data/lib/authorize/graph/directed_acyclic_graph_reverse_traverser.rb +27 -0
  22. data/lib/authorize/graph/directed_acyclic_graph_traverser.rb +30 -0
  23. data/lib/authorize/graph/directed_graph.rb +27 -0
  24. data/lib/authorize/graph/edge.rb +58 -0
  25. data/lib/authorize/graph/factory.rb +39 -0
  26. data/lib/authorize/graph/fixtures.rb +33 -0
  27. data/lib/authorize/graph/graph.rb +55 -0
  28. data/lib/authorize/graph/traverser.rb +89 -0
  29. data/lib/authorize/graph/undirected_graph.rb +14 -0
  30. data/lib/authorize/graph/vertex.rb +53 -0
  31. data/lib/authorize/permission.rb +97 -0
  32. data/lib/authorize/redis.rb +2 -0
  33. data/lib/authorize/redis/array.rb +36 -0
  34. data/lib/authorize/redis/base.rb +165 -0
  35. data/lib/authorize/redis/connection_manager.rb +88 -0
  36. data/lib/authorize/redis/connection_specification.rb +16 -0
  37. data/lib/authorize/redis/factory.rb +64 -0
  38. data/lib/authorize/redis/fixtures.rb +22 -0
  39. data/lib/authorize/redis/hash.rb +34 -0
  40. data/lib/authorize/redis/model_reference.rb +21 -0
  41. data/lib/authorize/redis/model_set.rb +19 -0
  42. data/lib/authorize/redis/set.rb +42 -0
  43. data/lib/authorize/redis/string.rb +17 -0
  44. data/lib/authorize/resource.rb +4 -0
  45. data/lib/authorize/resource_pool.rb +87 -0
  46. data/lib/authorize/role.rb +115 -0
  47. data/lib/authorize/test_helper.rb +42 -0
  48. data/lib/authorize/trustee.rb +4 -0
  49. data/lib/authorize/version.rb +3 -0
  50. data/rails/init.rb +5 -0
  51. data/tasks/authorize_tasks.rake +4 -0
  52. data/test/Rakefile +7 -0
  53. data/test/app/controllers/application_controller.rb +5 -0
  54. data/test/app/controllers/thingy_controller.rb +11 -0
  55. data/test/app/controllers/widgets_controller.rb +2 -0
  56. data/test/app/models/public.rb +14 -0
  57. data/test/app/models/user.rb +8 -0
  58. data/test/app/models/widget.rb +7 -0
  59. data/test/config/boot.rb +109 -0
  60. data/test/config/database.yml +25 -0
  61. data/test/config/environment.rb +28 -0
  62. data/test/config/environments/development.rb +4 -0
  63. data/test/config/environments/test.rb +0 -0
  64. data/test/config/initializers/mask.rb +1 -0
  65. data/test/config/initializers/redis.rb +8 -0
  66. data/test/config/routes.rb +5 -0
  67. data/test/db/.gitignore +1 -0
  68. data/test/db/schema.rb +26 -0
  69. data/test/log/.gitignore +2 -0
  70. data/test/public/javascripts/application.js +2 -0
  71. data/test/public/javascripts/controls.js +963 -0
  72. data/test/public/javascripts/dragdrop.js +972 -0
  73. data/test/public/javascripts/effects.js +1120 -0
  74. data/test/public/javascripts/prototype.js +4225 -0
  75. data/test/script/about +3 -0
  76. data/test/script/console +3 -0
  77. data/test/script/dbconsole +3 -0
  78. data/test/script/destroy +3 -0
  79. data/test/script/generate +3 -0
  80. data/test/script/performance/benchmarker +3 -0
  81. data/test/script/performance/profiler +3 -0
  82. data/test/script/performance/request +3 -0
  83. data/test/script/plugin +3 -0
  84. data/test/script/process/inspector +3 -0
  85. data/test/script/process/reaper +3 -0
  86. data/test/script/process/spawner +3 -0
  87. data/test/script/runner +3 -0
  88. data/test/script/server +3 -0
  89. data/test/test/fixtures/authorize/role_graph.yml +11 -0
  90. data/test/test/fixtures/permissions.yml +27 -0
  91. data/test/test/fixtures/redis/redis.yml +8 -0
  92. data/test/test/fixtures/redis/role_graph.yml +29 -0
  93. data/test/test/fixtures/roles.yml +28 -0
  94. data/test/test/fixtures/users.yml +12 -0
  95. data/test/test/fixtures/widgets.yml +12 -0
  96. data/test/test/functional/controller_class_test.rb +36 -0
  97. data/test/test/functional/controller_test.rb +46 -0
  98. data/test/test/test_helper.rb +35 -0
  99. data/test/test/unit/bitmask_test.rb +112 -0
  100. data/test/test/unit/fixture_test.rb +59 -0
  101. data/test/test/unit/graph_directed_acyclic_graph_reverse_traverser_test.rb +43 -0
  102. data/test/test/unit/graph_directed_acyclic_graph_traverser_test.rb +57 -0
  103. data/test/test/unit/graph_directed_graph_test.rb +66 -0
  104. data/test/test/unit/graph_edge_test.rb +53 -0
  105. data/test/test/unit/graph_graph_test.rb +50 -0
  106. data/test/test/unit/graph_traverser_test.rb +43 -0
  107. data/test/test/unit/graph_vertex_test.rb +57 -0
  108. data/test/test/unit/permission_test.rb +123 -0
  109. data/test/test/unit/redis_array_test.rb +60 -0
  110. data/test/test/unit/redis_connection_manager_test.rb +54 -0
  111. data/test/test/unit/redis_factory_test.rb +85 -0
  112. data/test/test/unit/redis_fixture_test.rb +18 -0
  113. data/test/test/unit/redis_hash_test.rb +43 -0
  114. data/test/test/unit/redis_model_reference_test.rb +39 -0
  115. data/test/test/unit/redis_set_test.rb +68 -0
  116. data/test/test/unit/redis_string_test.rb +25 -0
  117. data/test/test/unit/redis_test.rb +121 -0
  118. data/test/test/unit/resource_pool_test.rb +93 -0
  119. data/test/test/unit/resource_test.rb +33 -0
  120. data/test/test/unit/role_test.rb +143 -0
  121. data/test/test/unit/trustee_test.rb +35 -0
  122. data/test/tmp/.gitignore +2 -0
  123. data/uninstall.rb +1 -0
  124. metadata +319 -0
@@ -0,0 +1,84 @@
1
+ require 'set'
2
+
3
+ class Authorize::Bitmask < Set
4
+ include Comparable
5
+
6
+ class << self
7
+ attr_reader :name_values
8
+ def new(fixnum_or_enum = Set.new)
9
+ enum = fixnum_or_enum.kind_of?(Fixnum) ? enum(fixnum_or_enum) : fixnum_or_enum
10
+ super(enum)
11
+ end
12
+
13
+ # Record bit names and define dynamic methods
14
+ def name_values=(h)
15
+ h.each do |n, v|
16
+ define_method("_#{n}") {include?(n) ? true : false}
17
+ define_method("_#{n}=") {|v| v ? self.add(n) : self.delete(n)}
18
+ alias_method "_#{n}?", "_#{n}"
19
+ end
20
+ @name_values = h
21
+ end
22
+
23
+ # The maximum value this bitmask can hold (in which every named bit is set).
24
+ def max
25
+ name_values.values.inject{|memo, v| memo | v}
26
+ end
27
+
28
+ # Enumerates all operations included in the given mask
29
+ def enum(mask)
30
+ raise RangeError, "Unnamed bits in mask (#{mask.to_s(2)})" unless (mask | max) == max
31
+ name_values.inject(Set[]){|s, (p, v)| s << p if (v == (mask & v)); s }
32
+ end
33
+ end
34
+
35
+ def add(el)
36
+ raise ArgumentError, "Unrecognized bit name (#{el})" unless self.class.name_values.keys.include?(el)
37
+ super
38
+ end
39
+ alias << add
40
+
41
+ # Calculate the integer value for the mask
42
+ def to_i
43
+ inject(0) {|memo, n| memo | self.class.name_values[n]}
44
+ end
45
+ alias to_int to_i
46
+
47
+ def valid?
48
+ inject(true) {|memo, n| memo && !!self.class.name_values[n]}
49
+ end
50
+
51
+ # Return an equivalent Bitmask using only fundamental names, never aggregate names
52
+ def fundamental
53
+ complete.to_canonical_array.inject(self.class.new) do |memo, n|
54
+ memo << n unless (memo.to_i & self.class.name_values[n]) == self.class.name_values[n]
55
+ memo
56
+ end
57
+ end
58
+
59
+ # Return an equivalent Bitmask using aggregated names to replace fundamental names where possible
60
+ def minimal
61
+ complete.to_canonical_array.reverse.inject(self.class.new) do |memo, n|
62
+ memo << n unless (memo.to_i & self.class.name_values[n]) == self.class.name_values[n]
63
+ memo
64
+ end
65
+ end
66
+
67
+ # Return an equivalent Bitmask using all possible names (fundamental and aggregate)
68
+ def complete
69
+ self.class.new(to_int)
70
+ end
71
+
72
+ # Comparability derives from integer representation
73
+ def <=>(other)
74
+ to_int <=> other.to_int
75
+ end
76
+
77
+ def to_s
78
+ to_canonical_array.join("|")
79
+ end
80
+
81
+ def to_canonical_array
82
+ sort_by{|name| self.class.name_values[name]}
83
+ end
84
+ end
@@ -0,0 +1,30 @@
1
+ module Authorize #:nodoc:
2
+
3
+ # Base error class for Authorization module
4
+ class AuthorizationError < StandardError
5
+ end
6
+
7
+ # Raised when the authorization expression is invalid (cannot be parsed)
8
+ class AuthorizationExpressionInvalid < AuthorizationError
9
+ end
10
+
11
+ # Raised when we can't find the current user
12
+ class CannotObtainTokens < AuthorizationError
13
+ end
14
+
15
+ # Raised when an authorization expression contains a model class that doesn't exist
16
+ class CannotObtainModelClass < AuthorizationError
17
+ end
18
+
19
+ # Raised when an authorization expression contains a model reference that doesn't exist
20
+ class CannotObtainModelObject < AuthorizationError
21
+ end
22
+
23
+ # Raised when the obtained trustee object doesn't implement #has_role?
24
+ class TrusteeDoesntImplementRoles < AuthorizationError
25
+ end
26
+
27
+ # Raised when the obtained model doesn't implement #accepts_role?
28
+ class ModelDoesntImplementRoles < AuthorizationError
29
+ end
30
+ end
@@ -0,0 +1,4 @@
1
+ module Authorize
2
+ module Graph
3
+ end
4
+ end
@@ -0,0 +1,10 @@
1
+ require 'authorize/redis'
2
+ module Authorize
3
+ module Graph
4
+ class DirectedAcyclicGraph < DirectedGraph
5
+ def traverse(*args, &block)
6
+ DirectedAcyclicGraphTraverser.traverse(*args, &block)
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,27 @@
1
+ require 'enumerator'
2
+
3
+ module Authorize
4
+ module Graph
5
+ class DirectedAcyclicGraphReverseTraverser < DirectedAcyclicGraphTraverser
6
+ private
7
+ # Recursively traverse vertices breadth-wise, in reverse.
8
+ # Traversal is pruned if the block returns an untrue value.
9
+ def _traverse_breadth_first(start, depth, &block)
10
+ depth += 1
11
+ start.inbound_edges.select{|e| yield(e.from, e, depth)}.each do |e|
12
+ _traverse_breadth_first(e.from, depth, &block)
13
+ end
14
+ end
15
+
16
+ # Recursively traverse vertices depth-wise, in reverse.
17
+ # Traversal is pruned if the block returns an untrue value.
18
+ def _traverse_depth_first(start, depth, &block)
19
+ depth += 1
20
+ start.inbound_edges.each do |e|
21
+ _traverse_depth_first(e.from, depth, &block) if yield(e.from, e, depth)
22
+ end
23
+ end
24
+ alias _traverse _traverse_depth_first
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,30 @@
1
+ require 'enumerator'
2
+
3
+ module Authorize
4
+ module Graph
5
+ class DirectedAcyclicGraphTraverser < Traverser
6
+ def traverse(check = false, &block)
7
+ super(&block) unless check
8
+ t = self.class.new(self, :traverse)
9
+ if check
10
+ t.cycle_detector.pruner.cost_collector
11
+ else
12
+ t.pruner.cost_collector
13
+ end
14
+ end
15
+
16
+ # Detect cycles in the graph by recording the path taken (effectively an array of visited vertices indexed by
17
+ # depth). When a cycle is detected (by finding the current vertex earlier in the path), raise an exception.
18
+ def cycle_detector(&block)
19
+ return self.class.new(self, :cycle_detector) unless block_given?
20
+ seen = ::Array.new
21
+ self.each do |vertex, edge, depth|
22
+ found = seen.index(vertex)
23
+ raise "Cycle detected at #{vertex} along #{edge} at depth #{found} and #{depth}" if found && (found < depth)
24
+ seen[depth] = vertex
25
+ yield vertex, edge, depth
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,27 @@
1
+ require 'authorize/redis'
2
+ module Authorize
3
+ module Graph
4
+ # A directed graph implementation. Every edge either connects "to" a vertex or "from" it.
5
+ # This implementation ensures that edges are not duplicated (which precludes multigraphs). In
6
+ # cases where a duplicate edge is requested, the given properties are merged with the properties
7
+ # of the existing edge and the existing edge is returned.
8
+
9
+ # Notes:
10
+ # Edges are created in the context of a graph in order to allow for graph-specific indexing
11
+ class DirectedGraph < Graph::Graph
12
+ # Find or create a directed edge joining the given vertices
13
+ def join(name, v0, v1, properties = {})
14
+ existing_edge = v0.edges.detect{|e| v1.eql?(e.to)}
15
+ existing_edge.try(:merge, properties)
16
+ existing_edge || edge(name, v0, v1, properties)
17
+ end
18
+
19
+ def disjoin(v0, v1)
20
+ return unless existing_edge = v0.edges.detect{|e| v1.eql?(e.to)}
21
+ existing_edge.tap do |edge|
22
+ edge.destroy
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,58 @@
1
+ module Authorize
2
+ module Graph
3
+ # An edge connects two vertices. The order in which the vertices are supplied is preserved and can be
4
+ # used to imply direction.
5
+ # TODO: persist the connected vertices in an array.
6
+ # TODO: a hyperedge can be modeled with a set of vertices instead of explicit left and right vertices.
7
+ class Edge < Redis::Hash
8
+ include Redis::ModelReference
9
+
10
+ def self.exists?(id)
11
+ super(subordinate_key(id, 'l_id'))
12
+ end
13
+
14
+ def self.load_all(namespace = name)
15
+ redis_glob = subordinate_key(namespace, '*', 'l_id')
16
+ re = Regexp.new(subordinate_key(namespace, ".+(?=#{NAMESPACE_SEPARATOR})"))
17
+ keys = db.keys(redis_glob)
18
+ keys = keys.map{|m| m.slice(re)}
19
+ keys.map{|id| load(id)}
20
+ end
21
+
22
+ def initialize(v0, v1, properties = {})
23
+ super()
24
+ set_reference(subordinate_key('l_id'), v0)
25
+ set_reference(subordinate_key('r_id'), v1)
26
+ v0.outbound_edges << self
27
+ v1.inbound_edges << self
28
+ merge(properties) if properties.any?
29
+ end
30
+
31
+ def from
32
+ load_reference(subordinate_key('l_id'), Vertex)
33
+ end
34
+ alias left from
35
+
36
+ def to
37
+ load_reference(subordinate_key('r_id'), Vertex)
38
+ end
39
+ alias right to
40
+
41
+ def vertices
42
+ [from, to]
43
+ end
44
+
45
+ def destroy
46
+ from && from.outbound_edges.delete(self)
47
+ to && to.inbound_edges.delete(self)
48
+ self.class.db.del(subordinate_key('l_id'))
49
+ self.class.db.del(subordinate_key('r_id'))
50
+ super
51
+ end
52
+
53
+ def valid?
54
+ from && to && super
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,39 @@
1
+ require 'authorize/graph/vertex'
2
+ require 'authorize/graph/edge'
3
+ require 'authorize/redis/factory'
4
+
5
+ module Authorize
6
+ module Graph
7
+ class Factory < Redis::Factory
8
+ def directed_graph(name, value = Set[], options = {}, &block)
9
+ options = {:edge_ids => ::Set[]}.merge(options)
10
+ obj = set(name, value) do
11
+ set('edge_ids', options[:edge_ids])
12
+ yield if block_given?
13
+ end
14
+ DirectedGraph.load(obj.id)
15
+ end
16
+
17
+ def vertex(name, value = {}, options = {}, &block)
18
+ options = {:edge_ids => ::Set[], :inbound_edge_ids => ::Set[]}.merge(options)
19
+ obj = hash(name, value) do
20
+ string('_', nil)
21
+ set('edge_ids', options[:edge_ids])
22
+ set('inbound_edge_ids', options[:inbound_edge_ids])
23
+ yield if block_given?
24
+ end
25
+ Vertex.load(obj.id)
26
+ end
27
+
28
+ def edge(name, value = {}, options = {}, &block)
29
+ options = {:l_id => nil, :r_id => nil}.merge(options)
30
+ obj = hash(name, value) do
31
+ string(:l_id, options[:l_id])
32
+ string(:r_id, options[:r_id])
33
+ yield if block_given?
34
+ end
35
+ Edge.load(obj.id)
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,33 @@
1
+ require 'active_support/test_case'
2
+ require 'active_record/fixtures'
3
+
4
+ module Authorize
5
+ module Graph
6
+ module Fixtures
7
+ YAML.add_domain_type("hapgoods.com,2010", 'graph') do |type, value|
8
+ process(Role.graph, value)
9
+ end
10
+
11
+ def create_fixtures(db = Redis::Base.db, pathname = Pathname.new(ActiveSupport::TestCase.fixture_path).join('authorize', 'role_graph.yml'), flush = true)
12
+ db.flushdb if flush
13
+ YAML.load(ERB.new(pathname.read).result)
14
+ end
15
+ module_function :create_fixtures
16
+
17
+ def self.process(graph, nodes, parent = nil)
18
+ nodes.each do |node|
19
+ name = node.respond_to?(:keys) ? node.keys.first : node
20
+ children = node.respond_to?(:values) ? node.values.first : []
21
+ key = name_to_key(name)
22
+ vertex = graph.vertex(key)
23
+ graph.edge(nil, parent, vertex) if parent
24
+ process(graph, children, vertex) unless children.empty?
25
+ end
26
+ end
27
+
28
+ def self.name_to_key(name)
29
+ ::Fixtures.identify(name).to_s
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,55 @@
1
+ require 'authorize/redis'
2
+
3
+ module Authorize
4
+ module Graph
5
+ # A binary property graph. Vertices and Edges have an arbitrary set of named properties.
6
+ # Reference: http://www.nist.gov/dads/HTML/graph.html
7
+ class Graph < Redis::Set
8
+ def self.exists?(id)
9
+ db.keys(subordinate_key(id, '*')).any?
10
+ end
11
+
12
+ attr_writer :edge_namespace, :vertex_namespace
13
+
14
+ def edge_namespace
15
+ @edge_namespace ||= subordinate_key('_edges')
16
+ end
17
+
18
+ def vertex_namespace
19
+ @vertex_namespace ||= subordinate_key('_vertices')
20
+ end
21
+
22
+ def edges
23
+ Edge.load_all(edge_namespace)
24
+ end
25
+
26
+ def vertices
27
+ Vertex.load_all(vertex_namespace)
28
+ end
29
+
30
+ # Create an vertex on this graph with the given name and additional properties.
31
+ def vertex(name, *args)
32
+ name ||= self.class.next_counter(vertex_namespace)
33
+ key = self.class.subordinate_key(vertex_namespace, name)
34
+ Vertex.new(key, *args)
35
+ end
36
+
37
+ # Create an edge on this graph with the given name and additional properties.
38
+ def edge(name, *args)
39
+ name ||= self.class.next_counter(edge_namespace)
40
+ key = self.class.subordinate_key(edge_namespace, name)
41
+ Edge.new(key, *args)
42
+ end
43
+
44
+ # Load the existing vertex in this graph with the given name.
45
+ def vertex_by_name(name)
46
+ key = self.class.subordinate_key(vertex_namespace, name)
47
+ Vertex.load(key)
48
+ end
49
+
50
+ def traverse(start = Vertex.load(sort_by{rand}.first))
51
+ Traverser.traverse(start)
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,89 @@
1
+ require 'enumerator'
2
+
3
+ module Authorize
4
+ module Graph
5
+ # Traverse the graph by enumerating the encountered vertices.
6
+ class Traverser < Enumerable::Enumerator
7
+ # Traverse the graph starting at the given vertex. A "bootstrap" enumerator is created from the starting
8
+ # vertex. The bootstrap enumerator is then passed through a recursive expander and finally, several filters.
9
+ # In ruby 1.9, bootstrapping could be simplified with an anonymous enumerator (Enumerator.new {})
10
+ def self.traverse(start, *args, &block)
11
+ self.new(start, :tap).traverse(*args).visit(&block)
12
+ end
13
+
14
+ # Prune the graph by accumulating a set of visited nodes. When a vertex has already been visited, interrupt the
15
+ # visit and signal the yielder.
16
+ def pruner(&block)
17
+ return self.class.new(self, :pruner) unless block_given?
18
+ seen = ::Set.new
19
+ self.each do |vertex, edge, depth|
20
+ next false if seen.include?(vertex)
21
+ seen << vertex
22
+ yield vertex, edge, depth
23
+ true # Don't let client block return value influence traversal
24
+ end
25
+ end
26
+
27
+ # Output the values yielded by the yielder as well as the return value of the supplied block.
28
+ def debugger(&block)
29
+ return self.class.new(self, :debugger) unless block_given?
30
+ count = 0 # A transit counter that
31
+ self.each do |*args|
32
+ count += 1
33
+ block.call(*args).tap do |result|
34
+ print "#{count}\t#{result}\t" + args.join("\t") + "\n"
35
+ end
36
+ end
37
+ end
38
+
39
+ # Return the accumulated cost of traversing the graph. The default accumulator is a simple transit counter.
40
+ def cost_collector(cost = 0, f = lambda{|e| 1}, &block)
41
+ return self.class.new(self, :cost_collector) unless block_given?
42
+ inject(cost) do |total, (vertex, edge, depth)|
43
+ yield vertex, edge, depth
44
+ total + f.call(edge)
45
+ end
46
+ end
47
+
48
+ # Invoke callback on vertex. Strip the edge to be more conventional.
49
+ # This is typically the last filter in the traverse chain.
50
+ def visit(&block)
51
+ return self.class.new(self, :visit) unless block_given?
52
+ self.each do |vertex, edge, depth|
53
+ vertex.visit(edge, &block)
54
+ end
55
+ end
56
+
57
+ # Visit the yielded start vertex and traverse to its adjacencies.
58
+ # This operation effectively "expands" the enumerator.
59
+ def traverse(&block)
60
+ return self.class.new(self, :traverse).pruner.cost_collector unless block_given?
61
+ each do |vertex, edge|
62
+ depth = 0
63
+ yield vertex, edge, depth
64
+ _traverse(vertex, depth, &block)
65
+ end
66
+ end
67
+
68
+ private
69
+ # Recursively traverse vertices breadth-wise
70
+ # Traversal is pruned if the block returns an untrue value.
71
+ def _traverse_breadth_first(start, depth, &block)
72
+ depth += 1
73
+ start.outbound_edges.select{|e| yield(e.to, e, depth)}.each do |e|
74
+ _traverse_breadth_first(e.to, depth, &block)
75
+ end
76
+ end
77
+
78
+ # Recursively traverse vertices depth-wise
79
+ # Traversal is pruned if the block returns an untrue value.
80
+ def _traverse_depth_first(start, depth, &block)
81
+ depth += 1
82
+ start.outbound_edges.each do |e|
83
+ _traverse_depth_first(e.to, depth, &block) if yield(e.to, e, depth)
84
+ end
85
+ end
86
+ alias _traverse _traverse_depth_first
87
+ end
88
+ end
89
+ end