authorize 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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