turbine-graph 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +15 -0
- data/Guardfile +6 -0
- data/LICENSE +27 -0
- data/README.md +189 -0
- data/Rakefile +126 -0
- data/examples/energy.rb +80 -0
- data/examples/family.rb +125 -0
- data/lib/turbine.rb +29 -0
- data/lib/turbine/algorithms/filtered_tarjan.rb +33 -0
- data/lib/turbine/algorithms/tarjan.rb +50 -0
- data/lib/turbine/edge.rb +94 -0
- data/lib/turbine/errors.rb +74 -0
- data/lib/turbine/graph.rb +113 -0
- data/lib/turbine/node.rb +246 -0
- data/lib/turbine/pipeline/README.mdown +31 -0
- data/lib/turbine/pipeline/dsl.rb +275 -0
- data/lib/turbine/pipeline/expander.rb +67 -0
- data/lib/turbine/pipeline/filter.rb +52 -0
- data/lib/turbine/pipeline/journal.rb +130 -0
- data/lib/turbine/pipeline/journal_filter.rb +71 -0
- data/lib/turbine/pipeline/pump.rb +19 -0
- data/lib/turbine/pipeline/segment.rb +175 -0
- data/lib/turbine/pipeline/sender.rb +51 -0
- data/lib/turbine/pipeline/split.rb +132 -0
- data/lib/turbine/pipeline/trace.rb +55 -0
- data/lib/turbine/pipeline/transform.rb +49 -0
- data/lib/turbine/pipeline/traversal.rb +34 -0
- data/lib/turbine/pipeline/unique.rb +47 -0
- data/lib/turbine/properties.rb +48 -0
- data/lib/turbine/traversal/base.rb +133 -0
- data/lib/turbine/traversal/breadth_first.rb +49 -0
- data/lib/turbine/traversal/depth_first.rb +46 -0
- data/lib/turbine/version.rb +4 -0
- data/turbine.gemspec +84 -0
- metadata +120 -0
@@ -0,0 +1,51 @@
|
|
1
|
+
module Turbine
|
2
|
+
module Pipeline
|
3
|
+
# A segment which transforms its input by sending a +message+ to each
|
4
|
+
# input and returning the result.
|
5
|
+
#
|
6
|
+
# ( Pump.new([1, 2]) | Sender.new(:to_s) ).to_a
|
7
|
+
# # => ['1', '2']
|
8
|
+
#
|
9
|
+
# Each item coming from the source segment must have a public method with
|
10
|
+
# the same name as the +message+.
|
11
|
+
#
|
12
|
+
# Some methods in Turbine return an Array, Collection, or Enumerator as a
|
13
|
+
# sort of "result set" -- such as Node#in, Node#descendants, etc. In these
|
14
|
+
# cases, each element in the result set is yielded separately before
|
15
|
+
# continuing with the next input. See Expander for more details.
|
16
|
+
class Sender < Expander
|
17
|
+
attr_reader :message, :args
|
18
|
+
|
19
|
+
# Public: Creates a new Sender segment.
|
20
|
+
#
|
21
|
+
# message - The message (method name) to be sent to each value in the
|
22
|
+
# pipeline.
|
23
|
+
# args - Optional arguments to be sent with the message.
|
24
|
+
#
|
25
|
+
# Returns a Sender.
|
26
|
+
def initialize(message, *args)
|
27
|
+
@message = message
|
28
|
+
@args = args
|
29
|
+
|
30
|
+
super()
|
31
|
+
end
|
32
|
+
|
33
|
+
# Public: Describes the segments through which each input will pass.
|
34
|
+
#
|
35
|
+
# Returns a string.
|
36
|
+
def to_s
|
37
|
+
"#{ source_to_s } | #{ message.to_s }" \
|
38
|
+
"(#{ args.map(&:inspect).join(', ') })"
|
39
|
+
end
|
40
|
+
|
41
|
+
#######
|
42
|
+
private
|
43
|
+
#######
|
44
|
+
|
45
|
+
def input
|
46
|
+
super.public_send(@message, *args)
|
47
|
+
end
|
48
|
+
|
49
|
+
end # Sender
|
50
|
+
end # Pipeline
|
51
|
+
end # Turbine
|
@@ -0,0 +1,132 @@
|
|
1
|
+
module Turbine
|
2
|
+
module Pipeline
|
3
|
+
# Splits the upstream source into multiple pipelines which are evaluated
|
4
|
+
# in turn on the source, with the combined results being emitted.
|
5
|
+
#
|
6
|
+
# For example
|
7
|
+
#
|
8
|
+
# pump = Pump.new([node1, node2, node3])
|
9
|
+
# split = Split.new(->(x) { x.get(:age) },
|
10
|
+
# ->(x) { x.get(:gender) })
|
11
|
+
#
|
12
|
+
# (pump | split).to_a # => [ 18, :male, 27, :female, 25, :male ]
|
13
|
+
#
|
14
|
+
# You may supply as many separate branches as you wish.
|
15
|
+
class Split < Expander
|
16
|
+
Branch = Struct.new(:pump, :pipe)
|
17
|
+
|
18
|
+
# Public: Creates a new Split segment.
|
19
|
+
#
|
20
|
+
# branches - One or more procs; each proc is given a new pipeline DSL so
|
21
|
+
# that you may transform / filter the inputs before the
|
22
|
+
# results are merged back into the output.
|
23
|
+
#
|
24
|
+
# Returns a new Split.
|
25
|
+
def initialize(*branches)
|
26
|
+
if branches.none?
|
27
|
+
raise ArgumentError, 'Split requires at least one proc'
|
28
|
+
end
|
29
|
+
|
30
|
+
super()
|
31
|
+
|
32
|
+
# Each DSL is evaluated once, and +handle_result+ changes the source
|
33
|
+
# for each value being processed. This is more efficient than creating
|
34
|
+
# and evaluating a new DSL for every input.
|
35
|
+
@branches = branches.map do |branch|
|
36
|
+
dsl = Pipeline.dsl([])
|
37
|
+
pump = dsl.source
|
38
|
+
|
39
|
+
Branch.new(pump, branch.call(dsl))
|
40
|
+
end
|
41
|
+
|
42
|
+
# JRuby doesn't support calling +next+ on enum.cycle.with_index.
|
43
|
+
@branches_cycle = @branches.zip((0...@branches.length).to_a).cycle
|
44
|
+
end
|
45
|
+
|
46
|
+
# Public: Returns the trace containing the most recently emitted values
|
47
|
+
# for all source segments. The trace for the current branch pipeline is
|
48
|
+
# merged into the trace.
|
49
|
+
#
|
50
|
+
# See Segment#trace.
|
51
|
+
#
|
52
|
+
# Returns an array.
|
53
|
+
def trace
|
54
|
+
super { |trace| trace.push(*@previous_trace) }
|
55
|
+
end
|
56
|
+
|
57
|
+
# Public: Enables or disables tracing on the segment. Passes the boolean
|
58
|
+
# through to the internal branch pipelines also, so that their traces
|
59
|
+
# may be combined with the output.
|
60
|
+
#
|
61
|
+
# Returns the tracing setting.
|
62
|
+
def tracing=(use_tracing)
|
63
|
+
super
|
64
|
+
|
65
|
+
@branches.each do |branch|
|
66
|
+
branch.pipe.source.tracing = use_tracing
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
#######
|
71
|
+
private
|
72
|
+
#######
|
73
|
+
|
74
|
+
# Internal: Returns the next value to be processed by the pipeline.
|
75
|
+
#
|
76
|
+
# Calling +input+ will fetch the input from the upstream segment,
|
77
|
+
# process it on the first branch and return the value. The next call
|
78
|
+
# will process the same input on the second branch, and so on util the
|
79
|
+
# value has been passed through each branch. Only then do we fetch a new
|
80
|
+
# input and start over.
|
81
|
+
#
|
82
|
+
# Returns an object.
|
83
|
+
def input
|
84
|
+
branch, iteration = @branches_cycle.next
|
85
|
+
|
86
|
+
# We've been through each branch for the current source, time to fetch
|
87
|
+
# the next one?
|
88
|
+
if iteration.zero?
|
89
|
+
@branch_source = Array(super).to_enum
|
90
|
+
end
|
91
|
+
|
92
|
+
branch.pump.source = @branch_source
|
93
|
+
branch.pipe.source.rewind
|
94
|
+
|
95
|
+
values = branch.pipe.to_a
|
96
|
+
|
97
|
+
@previous_trace = branch.pipe.source.trace.drop(1) if @tracing
|
98
|
+
|
99
|
+
values.any? ? values : input
|
100
|
+
end
|
101
|
+
end # Split
|
102
|
+
|
103
|
+
# A special case of split which emits the input value, and the results
|
104
|
+
# of a the given branches.
|
105
|
+
#
|
106
|
+
# For example
|
107
|
+
#
|
108
|
+
# # Get your friends and their friends, and emit both as a single list.
|
109
|
+
# nodes.out(:friend).also(->(node) { node.out(:friend) })
|
110
|
+
#
|
111
|
+
class Also < Split
|
112
|
+
# Creates a new Also segment.
|
113
|
+
#
|
114
|
+
# branches - A single branch whose results will be emitted along with
|
115
|
+
# the input value.
|
116
|
+
#
|
117
|
+
# For example
|
118
|
+
#
|
119
|
+
# nodes.also(->(n) { n.out(:spouse) }, ->(n) { n.out(:child) })
|
120
|
+
#
|
121
|
+
# If you only need to supply a single branch, you can pass it as a block
|
122
|
+
# instead of a proc wrapped in an array.
|
123
|
+
#
|
124
|
+
# nodes.also { |n| n.out(:spouse) }
|
125
|
+
#
|
126
|
+
# Returns a new Also.
|
127
|
+
def initialize(*branches, &block)
|
128
|
+
super(*[->(node) { node }, *branches, block].compact)
|
129
|
+
end
|
130
|
+
end # Also
|
131
|
+
end # Pipeline
|
132
|
+
end # Turbine
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module Turbine
|
2
|
+
module Pipeline
|
3
|
+
# Trace alters the pipeline such that instead of returning a single
|
4
|
+
# "reduced" value each time the pipeline is run, an array is returned with
|
5
|
+
# each element containing the result of each segment.
|
6
|
+
#
|
7
|
+
# See DSL#trace for more information.
|
8
|
+
class Trace < Segment
|
9
|
+
# Public: Sets the segment which serves as the source for the Trace.
|
10
|
+
# Enables tracing on the source, and all of the parent sources.
|
11
|
+
#
|
12
|
+
# Returns the source.
|
13
|
+
def source=(upstream)
|
14
|
+
upstream.tracing = true
|
15
|
+
super
|
16
|
+
end
|
17
|
+
|
18
|
+
# Public: Runs the pipeline once, returning the full trace which was
|
19
|
+
# traversed in order to retrieve the value.
|
20
|
+
#
|
21
|
+
# Returns an object.
|
22
|
+
def next
|
23
|
+
@source.next
|
24
|
+
@source.trace
|
25
|
+
end
|
26
|
+
|
27
|
+
# When included into a segment, sets it so that the value emitted by the
|
28
|
+
# segment is not included in traces. Useful for filters which would
|
29
|
+
# otherwise result in a duplicate value in the trace.
|
30
|
+
module Transparent
|
31
|
+
# Public: Trace each transformation made to an input value.
|
32
|
+
#
|
33
|
+
# See Segment#trace.
|
34
|
+
#
|
35
|
+
# Returns an array.
|
36
|
+
def trace
|
37
|
+
@source.trace
|
38
|
+
end
|
39
|
+
end # Transparent
|
40
|
+
|
41
|
+
# When included into a segment, raises an error if the user tries to
|
42
|
+
# enable tracing.
|
43
|
+
module Untraceable
|
44
|
+
# Public: Enable or disable tracing on the segment. Raises a
|
45
|
+
# NotTraceableError when called with a truthy value.
|
46
|
+
#
|
47
|
+
# Returns the tracing setting.
|
48
|
+
def tracing=(use_tracing)
|
49
|
+
raise NotTraceableError.new(self) if use_tracing
|
50
|
+
super
|
51
|
+
end
|
52
|
+
end # Untraceable
|
53
|
+
end # Trace
|
54
|
+
end # Pipeline
|
55
|
+
end # Turbine
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Turbine
|
2
|
+
module Pipeline
|
3
|
+
# A segment which transforms the input into something else. For example,
|
4
|
+
# a simple transform might receive an integer and output it's square root.
|
5
|
+
#
|
6
|
+
# Transform.new { |x| Math.sqrt(x) }
|
7
|
+
#
|
8
|
+
class Transform < Segment
|
9
|
+
# Public: Creates a new Transform element.
|
10
|
+
#
|
11
|
+
# You may opt to use the Transform class directly, passing a block when
|
12
|
+
# initializing which is used to transform each value into something
|
13
|
+
# else. Alternatively, provide no block and use a subclass with a custom
|
14
|
+
# +transform+ method.
|
15
|
+
#
|
16
|
+
# Without a filter block, all elements are emitted.
|
17
|
+
#
|
18
|
+
# block - An optional block used to transform each value passing through
|
19
|
+
# the pipeline into something else.
|
20
|
+
#
|
21
|
+
# Returns a transform.
|
22
|
+
def initialize(&block)
|
23
|
+
@transform = (block || method(:transform))
|
24
|
+
super()
|
25
|
+
end
|
26
|
+
|
27
|
+
#######
|
28
|
+
private
|
29
|
+
#######
|
30
|
+
|
31
|
+
# Internal: Handles each value from the pipeline, using the +transform+
|
32
|
+
# block or method to convert it into something else.
|
33
|
+
#
|
34
|
+
# value - The value being processed.
|
35
|
+
#
|
36
|
+
# Returns nothing.
|
37
|
+
def handle_value(value)
|
38
|
+
super(@transform.call(value))
|
39
|
+
end
|
40
|
+
|
41
|
+
# Internal: The default transform.
|
42
|
+
#
|
43
|
+
# Returns the +value+ untouched.
|
44
|
+
def transform(value)
|
45
|
+
value
|
46
|
+
end
|
47
|
+
end # Transform
|
48
|
+
end # Pipeline
|
49
|
+
end # Turbine
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Turbine
|
2
|
+
module Pipeline
|
3
|
+
class Traverse < Expander
|
4
|
+
include Trace::Untraceable
|
5
|
+
|
6
|
+
# Public: Creates a new Traverse segment. Uses one of the traversal
|
7
|
+
# classes to emit every descendant of the input node.
|
8
|
+
#
|
9
|
+
# direction - The direction in which to traverse edges. :in or :out.
|
10
|
+
# label - An optional label by which to restrict the edges
|
11
|
+
# traversed.
|
12
|
+
# klass - The traversal strategy. Defaults to BreadthFirst.
|
13
|
+
#
|
14
|
+
# Returns a new Traverse.
|
15
|
+
def initialize(direction, label = nil, klass = nil)
|
16
|
+
@direction = direction
|
17
|
+
@label = label
|
18
|
+
@klass ||= Traversal::BreadthFirst
|
19
|
+
end
|
20
|
+
|
21
|
+
#######
|
22
|
+
private
|
23
|
+
#######
|
24
|
+
|
25
|
+
# Public: Passes each value into a traversal class, emitting every
|
26
|
+
# adjacent node.
|
27
|
+
#
|
28
|
+
# Returns the traversed objects.
|
29
|
+
def input
|
30
|
+
@klass.new(super, @direction, [@label]).to_enum
|
31
|
+
end
|
32
|
+
end # Traverse
|
33
|
+
end # Pipeline
|
34
|
+
end # Turbine
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Turbine
|
2
|
+
module Pipeline
|
3
|
+
# A Pipeline segment which only emits values which it hasn't emitted
|
4
|
+
# previously.
|
5
|
+
#
|
6
|
+
# In order to determine if a value is a duplicate, Unique needs to keep a
|
7
|
+
# reference to each input it sees. For large result sets, you may prefer
|
8
|
+
# to sacrifice performance for reduced space complexity by passing a
|
9
|
+
# block; this used to reduce each input to a simpler value for storage and
|
10
|
+
# comparison:
|
11
|
+
#
|
12
|
+
# pipeline.uniq { |value| value.hash }
|
13
|
+
#
|
14
|
+
# See also: Array#uniq.
|
15
|
+
class Unique < Filter
|
16
|
+
|
17
|
+
# Public: Creates a new Unique segment.
|
18
|
+
#
|
19
|
+
# block - An optional block which is used to "reduce" each value for
|
20
|
+
# comparison with previously seen value.
|
21
|
+
#
|
22
|
+
# Returns a Unique.
|
23
|
+
def initialize(&block)
|
24
|
+
@seen = Set.new
|
25
|
+
|
26
|
+
super do |value|
|
27
|
+
key = block ? block.call(value) : value
|
28
|
+
seen = @seen.include?(key)
|
29
|
+
|
30
|
+
@seen.add(key)
|
31
|
+
|
32
|
+
not seen
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Public: Rewinds the segment so that iteration can happen from the
|
37
|
+
# first input again.
|
38
|
+
#
|
39
|
+
# Returns nothing.
|
40
|
+
def rewind
|
41
|
+
@seen.clear
|
42
|
+
super
|
43
|
+
end
|
44
|
+
|
45
|
+
end # Unique
|
46
|
+
end # Pipeline
|
47
|
+
end # Turbine
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Turbine
|
2
|
+
module Properties
|
3
|
+
|
4
|
+
# Public: Returns the properties associated with the model.
|
5
|
+
#
|
6
|
+
# Returns a hash containing the properties. This is the original
|
7
|
+
# properties hash, not a duplicate.
|
8
|
+
def properties
|
9
|
+
@properties ||= Hash.new
|
10
|
+
end
|
11
|
+
|
12
|
+
# Public: Mass-assigns properties to the model.
|
13
|
+
#
|
14
|
+
# new_props - A hash containing zero or more properties. The internal
|
15
|
+
# properties hash is set to whatever parameters you provide; a
|
16
|
+
# duplicate is not made before assignment. You may provide
|
17
|
+
# +nil+ to remove all properties.
|
18
|
+
#
|
19
|
+
# Returns the properties.
|
20
|
+
def properties=(new_props)
|
21
|
+
unless new_props.is_a?(Hash) || new_props.nil?
|
22
|
+
raise InvalidPropertiesError.new(self, new_props)
|
23
|
+
end
|
24
|
+
|
25
|
+
@properties = new_props
|
26
|
+
end
|
27
|
+
|
28
|
+
# Public: Sets a single property on the model.
|
29
|
+
#
|
30
|
+
# key - The property name.
|
31
|
+
# value - The value to be set.
|
32
|
+
#
|
33
|
+
# Returns the value.
|
34
|
+
def set(key, value)
|
35
|
+
properties[key] = value
|
36
|
+
end
|
37
|
+
|
38
|
+
# Public: Returns a single property on the model.
|
39
|
+
#
|
40
|
+
# key - The property to be retrieved.
|
41
|
+
#
|
42
|
+
# Returns the value or nil if the property does not exist.
|
43
|
+
def get(key)
|
44
|
+
properties[key]
|
45
|
+
end
|
46
|
+
|
47
|
+
end # Properties
|
48
|
+
end # Turbine
|
@@ -0,0 +1,133 @@
|
|
1
|
+
module Turbine
|
2
|
+
module Traversal
|
3
|
+
# Provides the means for traversing through the graph.
|
4
|
+
#
|
5
|
+
# Traversal classes do not themselves provide the methods commonly used
|
6
|
+
# for iterating through collections (each, map, etc), but act as a
|
7
|
+
# generator for the values in an Enumerator.
|
8
|
+
#
|
9
|
+
# enumerator = DepthFirst.new(node, :in).to_enum
|
10
|
+
# # => #<Enumerator: Node, Node, ...>
|
11
|
+
#
|
12
|
+
# enumerator.each { |node| ... }
|
13
|
+
# enumerator.map { |node| ... }
|
14
|
+
# # etc ...
|
15
|
+
#
|
16
|
+
# The Base class should not be used directly, but instead you should use
|
17
|
+
# DepthFirst or BreadthFirst which define strategies for the order in
|
18
|
+
# which items are traversed.
|
19
|
+
#
|
20
|
+
# Each unique item is traversed a maximum of once (loops are not
|
21
|
+
# repeatedly followed).
|
22
|
+
#
|
23
|
+
# Traversals are normally used to iterate through nodes, however you may
|
24
|
+
# also use them to traverse edges by providing a +fetcher+ argument which
|
25
|
+
# tells the traversal how to reach the next set of adjacent items:
|
26
|
+
#
|
27
|
+
# DepthFirst.new(node, :in_edges, [], :out).to_enum
|
28
|
+
# # => #<Enumerator: Edge, Edge, ...>
|
29
|
+
#
|
30
|
+
# As an end-user, you should rarely have to instantiate a traversal class
|
31
|
+
# yourself; Node#ancestors and Node#descendants provide a more convenient
|
32
|
+
# short-cut.
|
33
|
+
class Base
|
34
|
+
# Creates a new graph traversal.
|
35
|
+
#
|
36
|
+
# start - The node from which to start traversing.
|
37
|
+
# method - The method to be used to fetch the adjacent nodes (typically
|
38
|
+
# +in+ or +out+).
|
39
|
+
# args - Additional arguments to be used when calling +method+.
|
40
|
+
# fetcher - An optional method name to be called on each adjacent item
|
41
|
+
# in order to fetch *its* adjacent items. Useful if traversing
|
42
|
+
# edges instead of nodes.
|
43
|
+
#
|
44
|
+
# Returns a new traversal.
|
45
|
+
def initialize(start, method, args = nil, fetcher = nil)
|
46
|
+
@start = start
|
47
|
+
@method = method
|
48
|
+
@args = args
|
49
|
+
@fetcher = fetcher
|
50
|
+
end
|
51
|
+
|
52
|
+
# Public: A human-readable version of the traversal.
|
53
|
+
#
|
54
|
+
# Returns a string.
|
55
|
+
def inspect
|
56
|
+
"#<#{ self.class.name } start=#{ @start.inspect } " \
|
57
|
+
"method=#{ @method.inspect }" \
|
58
|
+
"#{ @fetcher ? " fetcher=#{ @fetcher.inspect }" : '' }>"
|
59
|
+
end
|
60
|
+
|
61
|
+
# Public: The next node in the traversal.
|
62
|
+
#
|
63
|
+
# Raises a StopIteration if all reachable nodes have been visited.
|
64
|
+
#
|
65
|
+
# For example
|
66
|
+
#
|
67
|
+
# traversal.next # => #<Turbine::Node key=:one>
|
68
|
+
# traversal.next # => #<Turbine::Node key=:two>
|
69
|
+
# traversal.next # => ! StopIteration
|
70
|
+
#
|
71
|
+
# Returns a Node.
|
72
|
+
def next
|
73
|
+
@fiber.resume
|
74
|
+
end
|
75
|
+
|
76
|
+
# Public: The traversal as an enumerator. This is the main way to
|
77
|
+
# traverse since the enumerator implements +each+, +map+, +with_index+,
|
78
|
+
# etc.
|
79
|
+
#
|
80
|
+
# Returns an Enumerator.
|
81
|
+
def to_enum
|
82
|
+
Enumerator.new do |control|
|
83
|
+
rewind
|
84
|
+
loop { control.yield(self.next) }
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
#######
|
89
|
+
private
|
90
|
+
#######
|
91
|
+
|
92
|
+
# Internal: Given a +node+ iterates through each of it's adjacent nodes
|
93
|
+
# using the +method+ and +args+ supplied when initializing the
|
94
|
+
# DepthFirst instance.
|
95
|
+
#
|
96
|
+
# When the node itself has matching adjacent nodes, those will also be
|
97
|
+
# visited. If there are loops within the graph, they will not be
|
98
|
+
# followed; each node is visited no more than once.
|
99
|
+
#
|
100
|
+
# node - The node from which to traverse.
|
101
|
+
# block - A block executed for each matching node.
|
102
|
+
#
|
103
|
+
# Returns nothing.
|
104
|
+
def visit(node, &block)
|
105
|
+
raise NotImplementedError, 'Define visit in a subclass'
|
106
|
+
end
|
107
|
+
|
108
|
+
# Internal: Fetches the next iteration item. If the traversal was
|
109
|
+
# initialized with a +fetcher+, this is called on the item, otherwise
|
110
|
+
# the item is returned untouched.
|
111
|
+
#
|
112
|
+
# Useful when traversing edges instead of nodes.
|
113
|
+
#
|
114
|
+
# Returns an object.
|
115
|
+
def fetch(adjacent)
|
116
|
+
@fetcher ? adjacent.public_send(@fetcher) : adjacent
|
117
|
+
end
|
118
|
+
|
119
|
+
# Internal: Resets the traversal to restart from the beginning.
|
120
|
+
#
|
121
|
+
# Returns nothing.
|
122
|
+
def rewind
|
123
|
+
@seen = { @start => true }
|
124
|
+
|
125
|
+
@fiber = Fiber.new do
|
126
|
+
visit(@start) { |*args| Fiber.yield(*args) }
|
127
|
+
raise StopIteration
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
end # Base
|
132
|
+
end # Traversal
|
133
|
+
end # Turbine
|