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
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
|