turbine-graph 0.1.0
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.
- 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
|