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
data/lib/turbine/node.rb
ADDED
@@ -0,0 +1,246 @@
|
|
1
|
+
module Turbine
|
2
|
+
# In graph theory, a node is the fundamental unit of which graphs are
|
3
|
+
# formed: a directed graph consists of a set of nodes and a set of edges.
|
4
|
+
class Node
|
5
|
+
include Properties
|
6
|
+
|
7
|
+
# Public: Returns the unique key which identifies the node.
|
8
|
+
attr_reader :key
|
9
|
+
|
10
|
+
# Creates a new Node.
|
11
|
+
#
|
12
|
+
# key - A unique identifier for the node. The uniqueness of the key
|
13
|
+
# is not checked upon initializing, but instead when the node
|
14
|
+
# is added to the graph (Graph#add).
|
15
|
+
# properties - Optional key/value properties to be associated with the
|
16
|
+
# node.
|
17
|
+
#
|
18
|
+
# Returns the node.
|
19
|
+
def initialize(key, properties = nil)
|
20
|
+
@key = key
|
21
|
+
@in_edges = Set.new
|
22
|
+
@out_edges = Set.new
|
23
|
+
|
24
|
+
self.properties = properties
|
25
|
+
end
|
26
|
+
|
27
|
+
# Public: Returns nodes which have an outgoing edge to this node.
|
28
|
+
#
|
29
|
+
# label - An optional label by which to filter the in edges, before
|
30
|
+
# fetching the matched nodes.
|
31
|
+
#
|
32
|
+
# Returns an array of Node instances.
|
33
|
+
def in(label = nil)
|
34
|
+
Pipeline.dsl(self).in(label)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Public: Returns verticies to which this node has outgoing edges.
|
38
|
+
#
|
39
|
+
# label - An optional label by which to filter the out edges, before
|
40
|
+
# fetching the matched nodes.
|
41
|
+
#
|
42
|
+
# Returns an array of Node instances.
|
43
|
+
def out(label = nil)
|
44
|
+
Pipeline.dsl(self).out(label)
|
45
|
+
end
|
46
|
+
|
47
|
+
# Public: Returns this node's incoming edges.
|
48
|
+
#
|
49
|
+
# label - An optional label; only edges with this label will be returned.
|
50
|
+
# Passing nil will return all in edges.
|
51
|
+
#
|
52
|
+
# Raises an InvalidEdgeFilterError if you supply both a +label+ and
|
53
|
+
# +block+ for filtering the edges.
|
54
|
+
#
|
55
|
+
# Returns an array of Edges.
|
56
|
+
def in_edges(label = nil)
|
57
|
+
Pipeline.dsl(self).in_edges(label)
|
58
|
+
end
|
59
|
+
|
60
|
+
# Public: Returns this node's outgoing edges.
|
61
|
+
#
|
62
|
+
# label - An optional label; only edges with this label will be returned.
|
63
|
+
# Passing nil will return all out edges.
|
64
|
+
#
|
65
|
+
# Raises an InvalidEdgeFilterError if you supply both a +label+ and
|
66
|
+
# +block+ for filtering the edges.
|
67
|
+
#
|
68
|
+
# Returns an array of Edges.
|
69
|
+
def out_edges(label = nil)
|
70
|
+
Pipeline.dsl(self).out_edges(label)
|
71
|
+
end
|
72
|
+
|
73
|
+
# Public: Returns an enumerator containing all nodes which are outward
|
74
|
+
# nodes, and all of their outward nodes.
|
75
|
+
#
|
76
|
+
# Uses a BreadthFirst traversal so that immediately adjacent nodes are
|
77
|
+
# visited before more distant nodes.
|
78
|
+
#
|
79
|
+
# Returns an Enumerator containing Nodes.
|
80
|
+
def descendants(label = nil)
|
81
|
+
Pipeline.dsl(self).traverse(:out, label)
|
82
|
+
end
|
83
|
+
|
84
|
+
# Public: Returns an enumerator containing all nodes which are inward
|
85
|
+
# nodes, and all of their inward nodes.
|
86
|
+
#
|
87
|
+
# Uses a BreadthFirst traversal so that immediately adjacent nodes are
|
88
|
+
# visited before more distant nodes.
|
89
|
+
#
|
90
|
+
# Returns an Enumerator containing Nodes.
|
91
|
+
def ancestors(label = nil)
|
92
|
+
Pipeline.dsl(self).traverse(:in, label)
|
93
|
+
end
|
94
|
+
|
95
|
+
# Internal: Low-level method which retrieves all of the edges in a given
|
96
|
+
# +direction+, in an array.
|
97
|
+
#
|
98
|
+
# Returns an array of edges.
|
99
|
+
def edges(direction, label = nil)
|
100
|
+
select_edges(direction == :in ? @in_edges : @out_edges, label)
|
101
|
+
end
|
102
|
+
|
103
|
+
# Internal: Low-level method which retrieves all of the nodes in a given
|
104
|
+
# +direction+, in an array.
|
105
|
+
#
|
106
|
+
# Returns an array of nodes.
|
107
|
+
def nodes(direction, label = nil)
|
108
|
+
edges(direction, label).map(&(direction == :in ? :from : :to))
|
109
|
+
end
|
110
|
+
|
111
|
+
# Public: Returns a human-readable version of the node.
|
112
|
+
def inspect
|
113
|
+
"#<#{ self.class.name } key=#{ @key.inspect }>"
|
114
|
+
end
|
115
|
+
|
116
|
+
alias_method :to_s, :inspect
|
117
|
+
|
118
|
+
# Public: Connects this node to another.
|
119
|
+
#
|
120
|
+
# target - The node to which you want to connect. The +target+ node
|
121
|
+
# will be the "from" end of the edge.
|
122
|
+
# label - An optional label describing the relationship between the
|
123
|
+
# two nodes.
|
124
|
+
# properties - Optional key/value properties to be associated with the
|
125
|
+
# edge.
|
126
|
+
#
|
127
|
+
# Example:
|
128
|
+
#
|
129
|
+
# phil = Turbine::Node.new(:phil)
|
130
|
+
# luke = Turbine::Node.new(:luke)
|
131
|
+
#
|
132
|
+
# phil.connect_to(luke, :child)
|
133
|
+
#
|
134
|
+
# Returns the Edge which was created.
|
135
|
+
#
|
136
|
+
# Raises a Turbine::DuplicateEdgeError if the Edge already existed.
|
137
|
+
def connect_to(target, label = nil, properties = nil)
|
138
|
+
Edge.new(self, target, label, properties).tap do |edge|
|
139
|
+
self.connect_via(edge)
|
140
|
+
target.connect_via(edge)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
# Public: Disconnects this node from the +target+. Assumes that the
|
145
|
+
# receiver is the +from+ node.
|
146
|
+
#
|
147
|
+
# target - The node from which the receiver is to be disconnected.
|
148
|
+
# label - An optional label; only the edge with this label will be
|
149
|
+
# removed, with other edges kept intact. No label will remove all
|
150
|
+
# outward edges between the receiver and the target.
|
151
|
+
#
|
152
|
+
# Raises NoSuchEdge if the two nodes are not connected.
|
153
|
+
#
|
154
|
+
# Returns nothing.
|
155
|
+
def disconnect_from(target, label = nil)
|
156
|
+
edges(:out, label).select { |edge| edge.to == target }.each do |edge|
|
157
|
+
disconnect_via(edge)
|
158
|
+
target.disconnect_via(edge)
|
159
|
+
end
|
160
|
+
|
161
|
+
nil
|
162
|
+
end
|
163
|
+
|
164
|
+
# Internal: Given an Edge, establishes the connection for this node.
|
165
|
+
#
|
166
|
+
# Please note that you need to call +connect_via+ on both the "from" and
|
167
|
+
# "to" nodes. Unless you need to create the connection using a subclass of
|
168
|
+
# Edge, you will likey prefer using the simpler +connect_to+.
|
169
|
+
#
|
170
|
+
# Example:
|
171
|
+
#
|
172
|
+
# phil = Turbine::Node.new(:phil)
|
173
|
+
# haley = Turbine::Node.new(:haley)
|
174
|
+
#
|
175
|
+
# edge = Turbine::Edge.new(phil, haley, :child)
|
176
|
+
#
|
177
|
+
# # Adds an link from "phil" to "haley".
|
178
|
+
# phil.connect_via(edge)
|
179
|
+
# haley.connect_via(edge)
|
180
|
+
#
|
181
|
+
# Raises a Turbine::CannotConnectError if this node is not the +from+ or
|
182
|
+
# +to+ node specified by the edge.
|
183
|
+
#
|
184
|
+
# Returns the given edge.
|
185
|
+
def connect_via(edge)
|
186
|
+
connect_endpoint(@in_edges, edge) if edge.to == self
|
187
|
+
connect_endpoint(@out_edges, edge) if edge.from == self
|
188
|
+
|
189
|
+
edge
|
190
|
+
end
|
191
|
+
|
192
|
+
# Internal: Given an edge, removes the connection for this node.
|
193
|
+
#
|
194
|
+
# Please note that you need to call +disconnect_via+ on both the "from"
|
195
|
+
# and "to" nodes.
|
196
|
+
#
|
197
|
+
# Example:
|
198
|
+
#
|
199
|
+
# haley = Turbine::Node.new(:haley)
|
200
|
+
# dylan = Turbine::Node.new(:dylan)
|
201
|
+
#
|
202
|
+
# edge = haley.connect_to(dylan, :boyfriend)
|
203
|
+
#
|
204
|
+
# haley.disconnect_via(edge)
|
205
|
+
# dylan.disconnect_via(edge)
|
206
|
+
#
|
207
|
+
# Returns nothing.
|
208
|
+
def disconnect_via(edge)
|
209
|
+
@in_edges.delete(edge)
|
210
|
+
@out_edges.delete(edge)
|
211
|
+
|
212
|
+
nil
|
213
|
+
end
|
214
|
+
|
215
|
+
#######
|
216
|
+
private
|
217
|
+
#######
|
218
|
+
|
219
|
+
# Internal: Given an edge, and a Node's in_edges or out_edges, adds the
|
220
|
+
# edge only if there is not a similar edge already present.
|
221
|
+
#
|
222
|
+
# collection - The collection to which the edge is to be added.
|
223
|
+
# edge - The edge.
|
224
|
+
#
|
225
|
+
# Returns nothing.
|
226
|
+
def connect_endpoint(collection, edge)
|
227
|
+
if collection.any? { |o| ! edge.equal?(o) && edge.similar?(o) }
|
228
|
+
raise DuplicateEdgeError.new(self, edge)
|
229
|
+
end
|
230
|
+
|
231
|
+
collection.add(edge)
|
232
|
+
end
|
233
|
+
|
234
|
+
# Internal: Given an array of edges, and an optional label label, selects
|
235
|
+
# the edges from the given set.
|
236
|
+
#
|
237
|
+
# edges - The array of edges to be filtered.
|
238
|
+
# label - The label of the edges to be emitted.
|
239
|
+
#
|
240
|
+
# Returns an array of edges.
|
241
|
+
def select_edges(edges, label)
|
242
|
+
label.nil? ? edges : edges.select { |edge| edge.label == label }
|
243
|
+
end
|
244
|
+
|
245
|
+
end # Node
|
246
|
+
end # Turbine
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# Pipeline
|
2
|
+
|
3
|
+
Pipeline classes, appended to one another, provide a way to take a
|
4
|
+
source and lazily transform and filter that source. It is also more
|
5
|
+
efficient than the current `in` and `out` implementations in that
|
6
|
+
chaining multiple expressions together can be done without the need for
|
7
|
+
temporary collections between each chained method, and each source is
|
8
|
+
traversed only once.
|
9
|
+
|
10
|
+
This provides support for interesting traversal expressions:
|
11
|
+
|
12
|
+
```Ruby
|
13
|
+
# Get and "name" the child's parents:
|
14
|
+
luke.in(:child).as('parents').
|
15
|
+
# Get Grandparents:
|
16
|
+
in(:child).
|
17
|
+
# Get Grandparents' children (which includes Luke's parents):
|
18
|
+
out(:child).uniq.
|
19
|
+
# Remove Luke's parents, leaving the Uncles and Aunts:
|
20
|
+
except('parents').
|
21
|
+
# Add the Uncles' and Aunts' spouses:
|
22
|
+
also { |node| node.out(:spouse) }
|
23
|
+
```
|
24
|
+
|
25
|
+
Pipeline is inspired by Dave Thomas' 2008 articles on using Fiber to
|
26
|
+
create pipelines in Ruby<sup>[1][pipelines-one] & [2][pipelines-two]</sup>,
|
27
|
+
and the Java ["Pipes" library][pipes-library].
|
28
|
+
|
29
|
+
[pipelines-one]: http://pragdave.blogs.pragprog.com/pragdave/2007/12/pipelines-using.html
|
30
|
+
[pipelines-two]: http://pragdave.blogs.pragprog.com/pragdave/2008/01/pipelines-using.html
|
31
|
+
[pipes-library]: https://github.com/tinkerpop/pipes/wiki
|
@@ -0,0 +1,275 @@
|
|
1
|
+
module Turbine
|
2
|
+
module Pipeline
|
3
|
+
# Public: Starts a new Pipeline chain using the given +source+ as the
|
4
|
+
# source.
|
5
|
+
#
|
6
|
+
# source - An object, or array of objects, which will be iterated through
|
7
|
+
# the pipeline.
|
8
|
+
#
|
9
|
+
# Returns a DSL.
|
10
|
+
def self.dsl(source)
|
11
|
+
DSL.new(Pump.new(Array(source)))
|
12
|
+
end
|
13
|
+
|
14
|
+
# Provides the chaining DSL used throughout Turbine, such as when calling
|
15
|
+
# Node#in, Node#descendants, etc.
|
16
|
+
class DSL
|
17
|
+
extend Forwardable
|
18
|
+
include Enumerable
|
19
|
+
|
20
|
+
def_delegators :@source, :to_a, :each, :to_s
|
21
|
+
|
22
|
+
# The final segment in the pipeline.
|
23
|
+
attr_reader :source
|
24
|
+
|
25
|
+
# Public: Creates a new DSL instance.
|
26
|
+
#
|
27
|
+
# source - A Segment which acts as the head of the pipeline. Normally
|
28
|
+
# an instance of Pump.
|
29
|
+
#
|
30
|
+
# Returns a DSL.
|
31
|
+
def initialize(source)
|
32
|
+
@source = source
|
33
|
+
end
|
34
|
+
|
35
|
+
# Public: Queries each input for its +key+ property. Expects the input
|
36
|
+
# to include Turbine::Properties.
|
37
|
+
#
|
38
|
+
# key - The property key to be queried.
|
39
|
+
#
|
40
|
+
# For example
|
41
|
+
#
|
42
|
+
# pipe.get(:age) # => [11, 15, 18, 44, 46]
|
43
|
+
#
|
44
|
+
# Returns a new DSL.
|
45
|
+
def get(key)
|
46
|
+
append(Sender.new(:get, key))
|
47
|
+
end
|
48
|
+
|
49
|
+
# Public: Retrieves the in_edges from the input nodes.
|
50
|
+
#
|
51
|
+
# label - An optional label; only edges with a matching label will be
|
52
|
+
# emitted by the pipeline.
|
53
|
+
#
|
54
|
+
# Returns a DSL.
|
55
|
+
def in_edges(label = nil)
|
56
|
+
append(Sender.new(:edges, :in, label))
|
57
|
+
end
|
58
|
+
|
59
|
+
# Public: Retrieves the out_edges from the input nodes.
|
60
|
+
#
|
61
|
+
# label - An optional label; only edges with a matching label will be
|
62
|
+
# emitted by the pipeline.
|
63
|
+
#
|
64
|
+
# Returns a new DSL.
|
65
|
+
def out_edges(label = nil)
|
66
|
+
append(Sender.new(:edges, :out, label))
|
67
|
+
end
|
68
|
+
|
69
|
+
# Public: Retrieves the inbound nodes on the input node or edge.
|
70
|
+
#
|
71
|
+
# label - An optional label; only edges connected to the node via an
|
72
|
+
# edge with this label will be emitted by the pipeline.
|
73
|
+
#
|
74
|
+
# Returns a new DSL.
|
75
|
+
def in(label = nil)
|
76
|
+
append(Sender.new(:nodes, :in, label))
|
77
|
+
end
|
78
|
+
|
79
|
+
# Public: Retrieves the outbound nodes on the input node or edge.
|
80
|
+
#
|
81
|
+
# label - An optional label; only edges connected to the node via an
|
82
|
+
# edge with this label will be emitted by the pipeline.
|
83
|
+
#
|
84
|
+
# Returns a new DSL.
|
85
|
+
def out(label = nil)
|
86
|
+
append(Sender.new(:nodes, :out, label))
|
87
|
+
end
|
88
|
+
|
89
|
+
# Public: Using the breadth-first traversal strategy, fetches all of a
|
90
|
+
# node's adjacent nodes, and their adjacent nodes, and so on, in a given
|
91
|
+
# +direction+
|
92
|
+
#
|
93
|
+
# direction - In which direction from the current node do you wat to
|
94
|
+
# traverse? :in or :out?
|
95
|
+
# label - An optional label which is used to restrict the edges
|
96
|
+
# traversed to those with the label.
|
97
|
+
#
|
98
|
+
# For example
|
99
|
+
#
|
100
|
+
# # Fetches all nodes via outgoing edges.
|
101
|
+
# dsl.traverse(:out).to_a
|
102
|
+
#
|
103
|
+
# # Gets all out nodes via edges which have the :child label.
|
104
|
+
# dsl.traverse(:out, :child)
|
105
|
+
#
|
106
|
+
# Returns a new DSL.
|
107
|
+
def traverse(direction, label = nil)
|
108
|
+
append(Traverse.new(direction, label))
|
109
|
+
end
|
110
|
+
|
111
|
+
# Public: Given a block, emits input elements for which the block
|
112
|
+
# evaluates to true.
|
113
|
+
#
|
114
|
+
# block - A block used to determine which element are emitted.
|
115
|
+
#
|
116
|
+
# Returns a new DSL.
|
117
|
+
def select(&block)
|
118
|
+
append(Filter.new(&block))
|
119
|
+
end
|
120
|
+
|
121
|
+
# Public: Given a block, emits input elements for which the block
|
122
|
+
# evaluates to false.
|
123
|
+
#
|
124
|
+
# block - A block used to determine which elements are emitted.
|
125
|
+
#
|
126
|
+
# Returns a new DSL.
|
127
|
+
def reject(&block)
|
128
|
+
append(Filter.new { |value| ! block.call(value) })
|
129
|
+
end
|
130
|
+
|
131
|
+
# Public: Given a block, transforms each input value to the result of
|
132
|
+
# running the block with the input.
|
133
|
+
#
|
134
|
+
# block - A block used to transform each input value.
|
135
|
+
#
|
136
|
+
# Returns a new DSL.
|
137
|
+
def map(&block)
|
138
|
+
append(Transform.new(&block))
|
139
|
+
end
|
140
|
+
|
141
|
+
# Public: Splits the pipeline into separate branches, computes the
|
142
|
+
# values from each branch in turn, then combines the results.
|
143
|
+
#
|
144
|
+
# branches - One or more blocks which will be given the DSL.
|
145
|
+
#
|
146
|
+
# For example
|
147
|
+
#
|
148
|
+
# nodes.split(->(x) { x.get(:gender) },
|
149
|
+
# ->(x) { x.in_edges.length },
|
150
|
+
# ->(x) { x.out_edges.length }).to_a
|
151
|
+
# # => [ :male, 2, 3, :female, 1, 6, :female, 2, 2, ... ]
|
152
|
+
#
|
153
|
+
# Returns a new DSL.
|
154
|
+
def split(*branches)
|
155
|
+
append(Split.new(*branches))
|
156
|
+
end
|
157
|
+
|
158
|
+
# Public: Like +split+, but also yields the input value before running
|
159
|
+
# each branch.
|
160
|
+
#
|
161
|
+
# branches - One or more blocks which will be given the DSL.
|
162
|
+
#
|
163
|
+
# For example
|
164
|
+
#
|
165
|
+
# # Yields each node, and their outwards nodes connected with a
|
166
|
+
# # :spouse edge.
|
167
|
+
# nodes.also(->(node) { node.out(:spouse) })
|
168
|
+
#
|
169
|
+
# If you only want to supply a single branch, you can pass a block
|
170
|
+
# instead.
|
171
|
+
#
|
172
|
+
# nodes.also { |node| node.out(:child) }
|
173
|
+
#
|
174
|
+
# Returns a new DSL.
|
175
|
+
def also(*branches, &block)
|
176
|
+
append(Also.new(*branches, &block))
|
177
|
+
end
|
178
|
+
|
179
|
+
# Public: Captures all of the values emitted by the previous segment so
|
180
|
+
# that a later segment (e.g. "only" or "except") can use them.
|
181
|
+
#
|
182
|
+
# name - A name assigned to the captured values.
|
183
|
+
#
|
184
|
+
# Returns a new DSL.
|
185
|
+
def as(name)
|
186
|
+
append(Journal.new(name))
|
187
|
+
end
|
188
|
+
|
189
|
+
# Public: Creates a filter so that only values which were present in a
|
190
|
+
# named journal (created using "as") are emitted.
|
191
|
+
#
|
192
|
+
# journal_name - The name of the "as" journal.
|
193
|
+
#
|
194
|
+
# For example
|
195
|
+
#
|
196
|
+
# # Did your grandparents "friend" your parents?
|
197
|
+
# node.in(:child).as(:parents).in(:child).out(:friend).only(:parents)
|
198
|
+
#
|
199
|
+
# Returns a new DSL.
|
200
|
+
def only(journal_name)
|
201
|
+
append(JournalFilter.new(:only, journal_name))
|
202
|
+
end
|
203
|
+
|
204
|
+
# Public: Creates a filter so that only values which were not present in
|
205
|
+
# a named journal (created using "as") are emitted.
|
206
|
+
#
|
207
|
+
# name - The name of the "as" journal.
|
208
|
+
#
|
209
|
+
# # Who are your uncles and aunts?
|
210
|
+
# node.in(:child).as(:parents).in(:child).out(:child).except(:parents)
|
211
|
+
#
|
212
|
+
# Returns a new DSL.
|
213
|
+
def except(journal_name)
|
214
|
+
append(JournalFilter.new(:except, journal_name))
|
215
|
+
end
|
216
|
+
|
217
|
+
# Public: Mutates the pipeline so that instead of returning a single
|
218
|
+
# value, it returns an array where each element is the result returned
|
219
|
+
# by each segment in the pipeline.
|
220
|
+
#
|
221
|
+
# Does not work correctly with pipelines where +descendants+ or
|
222
|
+
# +ancestors+ is used before +trace+.
|
223
|
+
#
|
224
|
+
# For example
|
225
|
+
#
|
226
|
+
# jay.out(:child).out(:child).trace.to_a
|
227
|
+
# # => [ [ #<Node key=:jay>, #<Node key=:claire>, #<Node key=:haley> ],
|
228
|
+
# # [ #<Node key=:jay>, #<Node key=:claire>, #<Node key=:alex> ],
|
229
|
+
# # ... ]
|
230
|
+
#
|
231
|
+
# This can be especially useful if you explicitly include edges in your
|
232
|
+
# pipeline:
|
233
|
+
#
|
234
|
+
# jay.out_edges(:child).in.out_edges(:child).in.trace.next
|
235
|
+
# # => [ [ #<Node key=:jay>,
|
236
|
+
# # #<Edge :jay -:child-> :claire>,
|
237
|
+
# # #<Node key=:claire>,
|
238
|
+
# # #<Edge :claire -:child-> :haley>,
|
239
|
+
# # #<Node key=:haley> ],
|
240
|
+
# # ... ]
|
241
|
+
#
|
242
|
+
# Returns a new DSL.
|
243
|
+
def trace
|
244
|
+
DSL.new(@source.append(Trace.new))
|
245
|
+
end
|
246
|
+
|
247
|
+
# Public: Filters each value so that only unique elements are emitted.
|
248
|
+
#
|
249
|
+
# block - An optional block used when determining if the value is
|
250
|
+
# unique. See Pipeline::Unique#initialize.
|
251
|
+
#
|
252
|
+
# Returns a new DSL.
|
253
|
+
def uniq(&block)
|
254
|
+
append(Unique.new(&block))
|
255
|
+
end
|
256
|
+
|
257
|
+
# Public: Creates a new DSL by appending the given +downstream+ segment
|
258
|
+
# to the current source.
|
259
|
+
#
|
260
|
+
# downstream - The segment to be added in the new DSL.
|
261
|
+
#
|
262
|
+
# Returns a new DSL.
|
263
|
+
def append(downstream)
|
264
|
+
DSL.new(@source.append(downstream))
|
265
|
+
end
|
266
|
+
|
267
|
+
# Public: A human-readable version of the DSL.
|
268
|
+
#
|
269
|
+
# Return a String.
|
270
|
+
def inspect
|
271
|
+
"#<#{ self.class.inspect } {#{ to_s }}>"
|
272
|
+
end
|
273
|
+
end # DSL
|
274
|
+
end # Pipeline
|
275
|
+
end # Turbine
|