traversal 0.0.1 → 0.0.2
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/lib/traversal/acts_as_traversable.rb +2 -2
- data/lib/traversal/description.rb +111 -29
- data/lib/traversal/iterator.rb +47 -11
- data/lib/traversal/version.rb +1 -1
- metadata +3 -10
- data/.gitignore +0 -6
- data/Gemfile +0 -9
- data/README.rdoc +0 -96
- data/Rakefile +0 -1
- data/spec/test_helper.rb +0 -8
- data/spec/traversal_spec.rb +0 -151
- data/traversal.gemspec +0 -24
@@ -31,8 +31,8 @@ module Traversal
|
|
31
31
|
# t = TreeNode.new
|
32
32
|
# t.traverse # equivalent to Traversal::Description.new.traverse(t)
|
33
33
|
# t.traverse(:siblings) # equivalent to Traversal::Description.new.traverse(t).follow(:siblings)
|
34
|
-
def traverse(
|
35
|
-
Traversal::Description.new.traverse(self).tap { |desc| desc.follow(
|
34
|
+
def traverse(*relations)
|
35
|
+
Traversal::Description.new.traverse(self).tap { |desc| desc.follow(*relations) unless relations.empty? }
|
36
36
|
end
|
37
37
|
end
|
38
38
|
end
|
@@ -1,25 +1,49 @@
|
|
1
1
|
# coding: utf-8
|
2
|
+
|
3
|
+
require "forwardable"
|
4
|
+
|
2
5
|
module Traversal
|
3
6
|
# Traversal description
|
4
7
|
class Description
|
8
|
+
class EmptyArgument; end
|
9
|
+
|
10
|
+
extend Forwardable
|
5
11
|
include Enumerable
|
6
12
|
|
7
13
|
DEPTH_FIRST = 0
|
8
14
|
BREADTH_FIRST = 1
|
9
15
|
|
10
|
-
attr_reader :start_node, :
|
16
|
+
attr_reader :start_node, :relations
|
17
|
+
def_delegators :each, :[], :at, :empty?,
|
18
|
+
:fetch, :find_index, :index,
|
19
|
+
:last, :reverse, :values_at
|
11
20
|
|
12
21
|
# Create blank traversal description
|
13
22
|
def initialize
|
14
|
-
@exclude
|
15
|
-
@
|
16
|
-
|
17
|
-
@
|
23
|
+
@exclude = []
|
24
|
+
@include_only = []
|
25
|
+
|
26
|
+
@prune = []
|
27
|
+
@expand_only = []
|
28
|
+
@stop_before = []
|
29
|
+
@stop_after = []
|
18
30
|
|
19
|
-
@start_node
|
20
|
-
@
|
31
|
+
@start_node = nil
|
32
|
+
@relations = []
|
21
33
|
|
22
|
-
@order
|
34
|
+
@order = DEPTH_FIRST
|
35
|
+
@uniq = true
|
36
|
+
end
|
37
|
+
|
38
|
+
# Tests equality of traversal descriptions
|
39
|
+
def ==(other)
|
40
|
+
return super unless other.is_a?(Description)
|
41
|
+
|
42
|
+
[:@start_node, :@relations, :@include_only,
|
43
|
+
:@exclude, :@prune, :@stop_before, :@uniq,
|
44
|
+
:@stop_after, :@order, :@expand_only].all? do |sym|
|
45
|
+
instance_variable_get(sym) == other.instance_variable_get(sym)
|
46
|
+
end
|
23
47
|
end
|
24
48
|
|
25
49
|
# Declare a traversal start point. From which node you want to follow relations?
|
@@ -35,14 +59,34 @@ module Traversal
|
|
35
59
|
# == Example
|
36
60
|
# traversal.follow(:children) # for each node will call node#children method
|
37
61
|
# traversal.follow { |node| node.children } # same effect
|
38
|
-
def follow(
|
39
|
-
|
62
|
+
def follow(*relations, &blk)
|
63
|
+
raise ArgumentError, 'arguments or block expected' if relations.empty? && !block_given?
|
64
|
+
|
65
|
+
tap do
|
66
|
+
relations << blk if block_given?
|
67
|
+
|
68
|
+
relations.each do |relation|
|
69
|
+
@relations << condition(relation)
|
70
|
+
end
|
71
|
+
#@relations = condition(*relations, &blk)
|
72
|
+
end
|
40
73
|
end
|
41
74
|
|
42
75
|
# Declare exclude condition. Which nodes you want to
|
43
76
|
# exclude (ignore them but not their relations) from your traversal?
|
44
|
-
def exclude(
|
45
|
-
tap { @exclude << condition(
|
77
|
+
def exclude(*nodes, &blk)
|
78
|
+
tap { @exclude << condition(*nodes, &blk) }
|
79
|
+
end
|
80
|
+
|
81
|
+
# Declare inverted exclude condition. Which nodes you want to keep?
|
82
|
+
def include_only(*nodes, &blk)
|
83
|
+
tap { @include_only << condition(*nodes, &blk) }
|
84
|
+
end
|
85
|
+
alias exclude_unless include_only
|
86
|
+
|
87
|
+
# Declare which nodes you want to expand. Others will be pruned.
|
88
|
+
def expand_only(*nodes, &blk)
|
89
|
+
tap { @expand_only << condition(*nodes, &blk) }
|
46
90
|
end
|
47
91
|
|
48
92
|
# Declare prune condition. Which nodes relations you want to ignore?
|
@@ -50,28 +94,28 @@ module Traversal
|
|
50
94
|
# Example:
|
51
95
|
# traversal.follow(:children).
|
52
96
|
# prune { |node| node.name == "A" } # node "A" will be included, but not its children
|
53
|
-
def prune(
|
54
|
-
tap { @prune << condition(
|
97
|
+
def prune(*nodes, &blk)
|
98
|
+
tap { @prune << condition(*nodes, &blk) }
|
55
99
|
end
|
56
100
|
|
57
101
|
# Declare exclude AND prune condition.
|
58
102
|
# Matching node and its relations will be excluded from traversal.
|
59
|
-
def exclude_and_prune(
|
60
|
-
exclude(
|
61
|
-
prune(
|
103
|
+
def exclude_and_prune(*nodes, &blk)
|
104
|
+
exclude(*nodes, &blk)
|
105
|
+
prune(*nodes, &blk)
|
62
106
|
end
|
63
107
|
alias prune_and_exclude exclude_and_prune
|
64
108
|
|
65
109
|
# Declare +stop pre-condition+.
|
66
110
|
# When met, matched node will be excluded from traversal and iteration will be stopped.
|
67
|
-
def stop_before(
|
68
|
-
tap { @stop_before << condition(
|
111
|
+
def stop_before(*nodes, &blk)
|
112
|
+
tap { @stop_before << condition(*nodes, &blk) }
|
69
113
|
end
|
70
114
|
|
71
115
|
# Declare +stop post-condition+.
|
72
116
|
# When met, matched node will be included in traversal and iteration will be stopped.
|
73
|
-
def stop_after(
|
74
|
-
tap { @stop_after << condition(
|
117
|
+
def stop_after(*nodes, &blk)
|
118
|
+
tap { @stop_after << condition(*nodes, &blk) }
|
75
119
|
end
|
76
120
|
|
77
121
|
# Declare traversal order strategy as +depth first+
|
@@ -84,7 +128,14 @@ module Traversal
|
|
84
128
|
tap { @order = BREADTH_FIRST }
|
85
129
|
end
|
86
130
|
|
87
|
-
|
131
|
+
# Set uniqueness behaviour
|
132
|
+
# By default it is set to +true+
|
133
|
+
def uniq(v = true)
|
134
|
+
tap { @uniq = !!v }
|
135
|
+
end
|
136
|
+
|
137
|
+
# Iterate through nodes defined by DSL and optionally execute given +block+ for each node.
|
138
|
+
def each # :yields: node
|
88
139
|
assert_complete_description
|
89
140
|
|
90
141
|
iter = Traversal::Iterator.new(self)
|
@@ -105,29 +156,60 @@ module Traversal
|
|
105
156
|
(type == :after ? @stop_after : @stop_before).any? { |cond| cond[node] }
|
106
157
|
end
|
107
158
|
|
159
|
+
def include?(node) #:nodoc:
|
160
|
+
@include_only.all? { |cond| cond[node] } &&
|
161
|
+
@exclude.none? { |cond| cond[node] }
|
162
|
+
end
|
163
|
+
|
108
164
|
def exclude?(node) #:nodoc:
|
109
|
-
|
165
|
+
!include?(node)
|
166
|
+
end
|
167
|
+
|
168
|
+
def expand?(node) #:nodoc:
|
169
|
+
@expand_only.all? { |cond| cond[node] } &&
|
170
|
+
@prune.none? { |cond| cond[node] }
|
110
171
|
end
|
111
172
|
|
112
173
|
def prune?(node) #:nodoc:
|
113
|
-
|
174
|
+
!expand?(node)
|
114
175
|
end
|
115
176
|
|
116
177
|
def breadth_first? #:nodoc:
|
117
178
|
@order == BREADTH_FIRST
|
118
179
|
end
|
119
180
|
|
181
|
+
def uniq? #:nodoc:
|
182
|
+
@uniq
|
183
|
+
end
|
184
|
+
|
120
185
|
private
|
121
|
-
def condition(
|
122
|
-
|
123
|
-
|
186
|
+
def condition(*args, &blk) #:nodoc:
|
187
|
+
# on empty argument use given block
|
188
|
+
args << blk if block_given?
|
189
|
+
raise ArgumentError, 'arguments or block expected' if args.empty?
|
124
190
|
|
125
|
-
|
191
|
+
args.length == 1 ? arg_to_proc(args.first) : args_to_proc(args)
|
126
192
|
end
|
127
193
|
|
128
194
|
def assert_complete_description #:nodoc:
|
129
195
|
raise IncompleteDescription, "Traversal description should contain start node. Use #traverse method" unless @start_node
|
130
|
-
raise IncompleteDescription, "Traversal description should contain relation. Use #follow method"
|
196
|
+
raise IncompleteDescription, "Traversal description should contain relation(s). Use #follow method" if @relations.empty?
|
197
|
+
end
|
198
|
+
|
199
|
+
def args_to_proc(args)
|
200
|
+
procs = args.map { |arg| arg_to_proc(arg) }
|
201
|
+
proc { |node| procs.any? { |pr| pr[node] } }
|
202
|
+
end
|
203
|
+
|
204
|
+
# convert argument to callable proc
|
205
|
+
def arg_to_proc(arg) #:nodoc:
|
206
|
+
return arg.to_proc if arg.respond_to?(:to_proc)
|
207
|
+
|
208
|
+
[:===, :==, :eql?].each do |meth|
|
209
|
+
return arg.method(meth) if arg.respond_to?(meth)
|
210
|
+
end
|
211
|
+
|
212
|
+
raise TypeError, 'argument must respond to one of the following method: #to_proc, #===, #==, #eql?'
|
131
213
|
end
|
132
214
|
end
|
133
215
|
end
|
data/lib/traversal/iterator.rb
CHANGED
@@ -1,10 +1,17 @@
|
|
1
1
|
# coding: utf-8
|
2
2
|
|
3
3
|
require "enumerator"
|
4
|
+
require "forwardable"
|
4
5
|
|
5
6
|
module Traversal
|
6
7
|
# Traversal iterator.
|
7
|
-
class Iterator < Enumerator
|
8
|
+
class Iterator < Enumerator #:nodoc: all
|
9
|
+
extend Forwardable
|
10
|
+
|
11
|
+
def_delegators :to_ary!, :[], :at, :empty?,
|
12
|
+
:fetch, :find_index, :index,
|
13
|
+
:last, :reverse, :values_at
|
14
|
+
|
8
15
|
# Create new traversal iterator from traversal description
|
9
16
|
def initialize(description)
|
10
17
|
raise TypeError,
|
@@ -14,23 +21,33 @@ module Traversal
|
|
14
21
|
@description = description
|
15
22
|
start_node = @description.start_node
|
16
23
|
|
17
|
-
#
|
18
|
-
|
24
|
+
# Map of visited nodes
|
25
|
+
@visited = {}
|
26
|
+
|
27
|
+
# Create underlying Enumerator
|
28
|
+
@enumerator = Enumerator.new do |yielder|
|
19
29
|
@yielder = yielder
|
20
30
|
|
21
31
|
begin
|
22
32
|
yield_node(start_node)
|
23
33
|
|
24
|
-
expand_node(start_node)
|
34
|
+
expand_node(start_node) if @description.expand?(start_node)
|
25
35
|
rescue StopIteration
|
26
36
|
# ignore
|
27
37
|
end
|
28
38
|
end
|
39
|
+
|
40
|
+
# Wrap underlying enumerator
|
41
|
+
super() do |y|
|
42
|
+
@enumerator.each { |e| y << e }
|
43
|
+
end
|
29
44
|
end
|
30
45
|
|
31
46
|
private
|
32
|
-
def push(
|
33
|
-
@
|
47
|
+
def push(node) #:nodoc:
|
48
|
+
@visited[node] = true if @description.uniq? # memo visited node
|
49
|
+
|
50
|
+
@yielder.yield(node)
|
34
51
|
end
|
35
52
|
|
36
53
|
def yield_node(node) #:nodoc:
|
@@ -38,7 +55,7 @@ module Traversal
|
|
38
55
|
raise StopIteration if @description.stop?(node, :before)
|
39
56
|
|
40
57
|
# do yield
|
41
|
-
push(node) unless @description.exclude?(node)
|
58
|
+
push(node) unless @description.exclude?(node) || visited?(node)
|
42
59
|
|
43
60
|
# check stop post-condition
|
44
61
|
raise StopIteration if @description.stop?(node, :after)
|
@@ -82,9 +99,28 @@ module Traversal
|
|
82
99
|
|
83
100
|
# Expand relations for node
|
84
101
|
def relations_for(node) #:nodoc:
|
85
|
-
|
102
|
+
Enumerator.new do |yielder|
|
103
|
+
@description.relations.each do |relation_accessor|
|
104
|
+
begin
|
105
|
+
relations = relation_accessor[node]
|
106
|
+
enumerable = relations.is_a?(Enumerable) ? relations : [relations].compact
|
107
|
+
|
108
|
+
enumerable.each { |e| yielder << e unless visited?(e) }
|
109
|
+
rescue NoMethodError
|
110
|
+
# ignore errors on relation_accessor[node]
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def visited?(node) #:nodoc:
|
117
|
+
@description.uniq? && @visited.key?(node)
|
118
|
+
end
|
86
119
|
|
87
|
-
|
120
|
+
# convert underlying enumerator to array
|
121
|
+
def to_ary! #:nodoc:
|
122
|
+
@enumerator = @enumerator.to_a unless @enumerator.is_a?(Array)
|
123
|
+
@enumerator
|
88
124
|
end
|
89
|
-
end
|
90
|
-
end
|
125
|
+
end # module Iterator
|
126
|
+
end # module Traversal
|
data/lib/traversal/version.rb
CHANGED
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 0
|
7
7
|
- 0
|
8
|
-
-
|
9
|
-
version: 0.0.
|
8
|
+
- 2
|
9
|
+
version: 0.0.2
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Alexey Mikhaylov
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2012-01-
|
17
|
+
date: 2012-01-26 00:00:00 +06:00
|
18
18
|
default_executable:
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
@@ -53,18 +53,11 @@ extensions: []
|
|
53
53
|
extra_rdoc_files: []
|
54
54
|
|
55
55
|
files:
|
56
|
-
- .gitignore
|
57
|
-
- Gemfile
|
58
|
-
- README.rdoc
|
59
|
-
- Rakefile
|
60
56
|
- lib/traversal.rb
|
61
57
|
- lib/traversal/acts_as_traversable.rb
|
62
58
|
- lib/traversal/description.rb
|
63
59
|
- lib/traversal/iterator.rb
|
64
60
|
- lib/traversal/version.rb
|
65
|
-
- spec/test_helper.rb
|
66
|
-
- spec/traversal_spec.rb
|
67
|
-
- traversal.gemspec
|
68
61
|
has_rdoc: true
|
69
62
|
homepage: https://github.com/take-five/traversal
|
70
63
|
licenses: []
|
data/.gitignore
DELETED
data/Gemfile
DELETED
data/README.rdoc
DELETED
@@ -1,96 +0,0 @@
|
|
1
|
-
== Synopsys
|
2
|
-
Simple traversal API for pure Ruby objects. Also it can be used it with ActiveRecord or DataMapper (or any other ORM).
|
3
|
-
|
4
|
-
== Installation
|
5
|
-
Install it via rubygems:
|
6
|
-
gem install traversal
|
7
|
-
|
8
|
-
In your Gemfile:
|
9
|
-
gem 'traversal'
|
10
|
-
|
11
|
-
== Usage
|
12
|
-
Imagine tree(-ish) structure:
|
13
|
-
plants:
|
14
|
-
vegetables:
|
15
|
-
- cucumber
|
16
|
-
- tomato
|
17
|
-
fruits:
|
18
|
-
- apple
|
19
|
-
- banana
|
20
|
-
|
21
|
-
In Ruby it'll look like this:
|
22
|
-
class Node
|
23
|
-
attr_reader :name, :children
|
24
|
-
|
25
|
-
def initialize(name)
|
26
|
-
@name = name
|
27
|
-
@children = []
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
# lets recreate tree structure from above
|
32
|
-
root = Node.new('plants')
|
33
|
-
veg = Node.new('vegetables')
|
34
|
-
fruits = Node.new('fruits')
|
35
|
-
cucumber = Node.new('cucumber')
|
36
|
-
tomato = Node.new('tomato')
|
37
|
-
apple = Node.new('apple')
|
38
|
-
banana = Node.new('banana')
|
39
|
-
|
40
|
-
root.children << veg << fruits
|
41
|
-
veg.children << cucumber << tomato
|
42
|
-
fruits.children << apple << banana
|
43
|
-
|
44
|
-
So, we have a simple tree with <code>root</code> element on the top of it.
|
45
|
-
Now let's create a <b>traversal description</b>.
|
46
|
-
require 'traversal'
|
47
|
-
|
48
|
-
traversal = Traversal::Description.new
|
49
|
-
traversal.traverse(root). # start from root node
|
50
|
-
follow(:children) # move forward via children relations
|
51
|
-
|
52
|
-
It's a minimal traversal description. It has <b>start node</b> and <b>relation</b> pointer (<code>children</code>, in this case).
|
53
|
-
Traversal description is <code>Enumerable</code> object. Let's examine our traverse:
|
54
|
-
traversal.map { |n| n.name } # should be equal to [root, vegetables, cucumber, tomato, fruits, apple, banana]
|
55
|
-
|
56
|
-
Let's look closer:
|
57
|
-
1. We are starting from <code>root</code> node. It's first element.
|
58
|
-
2. Traversal cursor moves to the first child of <code>root</code>: <code>vegetables</code>
|
59
|
-
3. By default cursor moves deeper, to first child of <code>vegetables</code> node: <code>cucumber</code>
|
60
|
-
4. <code>cucumber</code> has no children, traversal cursor moves to the next child of <code>vegetables</code>: <code>tomato</code>
|
61
|
-
5. Traversal cursor moves to the next child of <code>root</code> - <code>fruits</code>
|
62
|
-
6. Traversal cursor visits children of <code>fruits</code>: <code>apple</code> and <code>banana</code>
|
63
|
-
7. All nodes are visited, cursor closed.
|
64
|
-
|
65
|
-
If you want the cursor to visit all children before visiting grandchildren, then you have to declare <code>breadth_first</code> traversal visiting strategy:
|
66
|
-
traversal.breadth_first # in opposite of traversal.depth_first
|
67
|
-
|
68
|
-
You can exclude nodes (but allow cursor to follow relations) from final result:
|
69
|
-
traversal.exclude { |node| node.children.length > 0 } # all nodes with children will be excluded from result
|
70
|
-
|
71
|
-
You can prune away any node children (but leave the node in final result):
|
72
|
-
traversal.prune { |node| node.name == "vegetables" }.
|
73
|
-
map(&:name) # will produce [root, vegetables, fruits, apple, banana]
|
74
|
-
|
75
|
-
Or, you can exclude node and prune away it's children:
|
76
|
-
traversal.prune_and_exclude { |node| node.name == "vegetables" }.
|
77
|
-
map(&:name) # will produce [root, fruits, apple, banana]
|
78
|
-
|
79
|
-
Also, you can mark any node as "loop terminator":
|
80
|
-
traversal.stop_before { |node| node.name == "vegetables" }.to_a # will produce only [root]
|
81
|
-
traversal.stop_after { |node| node.name == "vegetables" }.to_a # will produce [root, vegetables]
|
82
|
-
|
83
|
-
== Real world example
|
84
|
-
require "traversable"
|
85
|
-
|
86
|
-
class Page < ActiveRecord::Base
|
87
|
-
acts_as_tree
|
88
|
-
acts_as_traversable
|
89
|
-
end
|
90
|
-
|
91
|
-
lim = 15
|
92
|
-
|
93
|
-
Page.root.traverse(:children).
|
94
|
-
exclude(:root?).
|
95
|
-
exclude_and_prune(:is_deleted?).
|
96
|
-
stop_after { (lmt -= 1) == 0 }
|
data/Rakefile
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
require "bundler/gem_tasks"
|
data/spec/test_helper.rb
DELETED
data/spec/traversal_spec.rb
DELETED
@@ -1,151 +0,0 @@
|
|
1
|
-
require File.expand_path('../test_helper', __FILE__)
|
2
|
-
|
3
|
-
# Tree structure:
|
4
|
-
#
|
5
|
-
# +root+
|
6
|
-
# / \
|
7
|
-
# +a+ +b+
|
8
|
-
# / \ \
|
9
|
-
# +c+ +d+ +f+
|
10
|
-
# / / \
|
11
|
-
# +e+ +g+ +h+
|
12
|
-
|
13
|
-
class Node
|
14
|
-
attr_accessor :children
|
15
|
-
attr_accessor :level
|
16
|
-
attr_accessor :id
|
17
|
-
acts_as_traversable
|
18
|
-
|
19
|
-
def initialize(id, level)
|
20
|
-
@id = id
|
21
|
-
@level = level
|
22
|
-
@children = []
|
23
|
-
end
|
24
|
-
|
25
|
-
def inspect
|
26
|
-
"#<Node: #{id.to_s}>"
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
# create tree
|
31
|
-
root = Node.new(0, 0)
|
32
|
-
a = Node.new(1, 1)
|
33
|
-
b = Node.new(2, 1)
|
34
|
-
c = Node.new(3, 2)
|
35
|
-
d = Node.new(4, 2)
|
36
|
-
e = Node.new(5, 3)
|
37
|
-
f = Node.new(6, 2)
|
38
|
-
g = Node.new(7, 3)
|
39
|
-
h = Node.new(8, 3)
|
40
|
-
|
41
|
-
# link nodes
|
42
|
-
root.children << a << b
|
43
|
-
a.children << c << d
|
44
|
-
c.children << e
|
45
|
-
b.children << f
|
46
|
-
f.children << g << h
|
47
|
-
|
48
|
-
describe Traversal do
|
49
|
-
let(:iter) { Traversal::Description.new }
|
50
|
-
|
51
|
-
it "should traverse all descendants" do
|
52
|
-
# traverse whole tree, with default strategy (depth first)
|
53
|
-
iter.follow(:children).
|
54
|
-
traverse(root).
|
55
|
-
to_a.
|
56
|
-
should eq([root, a, c, e, d, b, f, g, h])
|
57
|
-
end
|
58
|
-
|
59
|
-
it "should exclude some nodes" do
|
60
|
-
# this traverse will exclude only +c+ node, but not it's children
|
61
|
-
iter.traverse(root).
|
62
|
-
follow(:children).
|
63
|
-
exclude { |node| node == c }.
|
64
|
-
to_a.should eq([root, a, e, d, b, f, g, h])
|
65
|
-
end
|
66
|
-
|
67
|
-
it "should disjunct excludes" do
|
68
|
-
# this traverse will exclude +c+ and +d+ nodes, but not their children
|
69
|
-
iter.traverse(root).
|
70
|
-
follow(:children).
|
71
|
-
exclude { |node| node == c }.
|
72
|
-
exclude { |node| node == d }.
|
73
|
-
to_a.should eq([root, a, e, b, f, g, h])
|
74
|
-
end
|
75
|
-
|
76
|
-
it "should prune some nodes" do
|
77
|
-
# this traverse will exclude all +c+ children
|
78
|
-
iter.traverse(root).
|
79
|
-
follow(:children).
|
80
|
-
prune { |node| node == c }.
|
81
|
-
to_a.should eq([root, a, c, d, b, f, g, h])
|
82
|
-
end
|
83
|
-
|
84
|
-
it "should disjunct prunes" do
|
85
|
-
# this traverse will exclude all +c+ and +f+ children
|
86
|
-
iter.traverse(root).
|
87
|
-
follow(:children).
|
88
|
-
prune { |node| node == c }.
|
89
|
-
prune { |node| node == f }.
|
90
|
-
to_a.should eq([root, a, c, d, b, f])
|
91
|
-
end
|
92
|
-
|
93
|
-
it "should exclude and prune some nodes" do
|
94
|
-
# this traverse will exclude +c+ and its children
|
95
|
-
iter.traverse(root).
|
96
|
-
follow(:children).
|
97
|
-
exclude_and_prune { |node| node == c }.
|
98
|
-
to_a.should eq([root, a, d, b, f, g, h])
|
99
|
-
end
|
100
|
-
|
101
|
-
it "should stop traversal after some condition met" do
|
102
|
-
# this traversal will stop after visiting +d+ node
|
103
|
-
iter.traverse(root).
|
104
|
-
follow(:children).
|
105
|
-
stop_after { |node| node == d }.
|
106
|
-
to_a.should eq([root, a, c, e, d])
|
107
|
-
end
|
108
|
-
|
109
|
-
it "should stop traversal before some condition met" do
|
110
|
-
# this traversal will stop before visiting +d+ node
|
111
|
-
iter.traverse(root).
|
112
|
-
follow(:children).
|
113
|
-
stop_before { |node| node == d }.
|
114
|
-
to_a.should eq([root, a, c, e])
|
115
|
-
end
|
116
|
-
|
117
|
-
it "should traverse with depth_first strategy" do
|
118
|
-
iter.traverse(root).
|
119
|
-
follow(:children).
|
120
|
-
depth_first.
|
121
|
-
exclude { |node| node.level == 3 }.
|
122
|
-
to_a.should eq([root, a, c, d, b, f])
|
123
|
-
end
|
124
|
-
|
125
|
-
it "should traverse with breadth_first strategy" do
|
126
|
-
iter.traverse(root).
|
127
|
-
follow(:children).
|
128
|
-
breadth_first.
|
129
|
-
exclude { |node| node.level == 3 }.
|
130
|
-
to_a.should eq([root, a, b, c, d, f])
|
131
|
-
end
|
132
|
-
|
133
|
-
it "should raise exception when no start node given" do
|
134
|
-
lambda { iter.follow(:children).to_a }.should raise_error(Traversal::IncompleteDescription)
|
135
|
-
end
|
136
|
-
|
137
|
-
it "should raise exception when no relations given" do
|
138
|
-
lambda { iter.traverse(root).to_a }.should raise_error(Traversal::IncompleteDescription)
|
139
|
-
end
|
140
|
-
|
141
|
-
it "should have shortcut" do
|
142
|
-
root.should respond_to(:traverse)
|
143
|
-
|
144
|
-
root.traverse(:children).count.should eq(9)
|
145
|
-
end
|
146
|
-
|
147
|
-
it "should return Enumerable when called without block" do
|
148
|
-
iter.traverse(root).follow(:children).each.should be_a(Enumerable)
|
149
|
-
|
150
|
-
end
|
151
|
-
end
|
data/traversal.gemspec
DELETED
@@ -1,24 +0,0 @@
|
|
1
|
-
# -*- encoding: utf-8 -*-
|
2
|
-
$:.push File.expand_path("../lib", __FILE__)
|
3
|
-
require "traversal/version"
|
4
|
-
|
5
|
-
Gem::Specification.new do |s|
|
6
|
-
s.name = "traversal"
|
7
|
-
s.version = Traversal::VERSION
|
8
|
-
s.authors = ["Alexey Mikhaylov"]
|
9
|
-
s.email = ["amikhailov83@gmail.com"]
|
10
|
-
s.homepage = "https://github.com/take-five/traversal"
|
11
|
-
s.summary = %q{Simple traversal API for pure Ruby objects}
|
12
|
-
s.date = "2012-01-22"
|
13
|
-
|
14
|
-
s.rubyforge_project = "traversal"
|
15
|
-
|
16
|
-
s.files = `git ls-files`.split("\n")
|
17
|
-
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
-
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
-
s.require_paths = ["lib"]
|
20
|
-
|
21
|
-
s.add_development_dependency "rspec"
|
22
|
-
s.add_development_dependency "simplecov"
|
23
|
-
# s.add_runtime_dependency "rest-client"
|
24
|
-
end
|