trivet 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.
- checksums.yaml +7 -0
- data/README.md +79 -0
- data/lib/trivet.rb +1418 -0
- metadata +45 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 816c724387a4e6931697972dc59868bce4032a3cc7df3064e84f9cf9d6dc9b87
|
4
|
+
data.tar.gz: 33e2980a16a1e6575816635ee34c2273935bf185330c7ef2a190b399bc3a53ef
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 15ad703f9a3977dad38e21b0e12159048fb348dc52beeeb79e03ce5bbd835b76bcaf887ee24e4ba18c1f846fac17fa027b643ce1cf1ea4553edf285f57dc442c
|
7
|
+
data.tar.gz: 1fad0c2a2c2ca47839e48ced488964b5a13ac3474b6207f985b0d2a7de68b30f0320f63428709da92f01d6cb670e356854359a38b8eebdd8d9c5ca00d73c3f43
|
data/README.md
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
# Trivet
|
2
|
+
|
3
|
+
Trivet is a generic class for organizing a hierarchy.
|
4
|
+
|
5
|
+
## Basic usage
|
6
|
+
|
7
|
+
1. food = Trivet::Node.new('food')
|
8
|
+
2.
|
9
|
+
3. food.node('spices') do |spices|
|
10
|
+
4. spices.node 'paprika'
|
11
|
+
5.
|
12
|
+
6. spices.node('pepper') do |pepper|
|
13
|
+
7. pepper.node 'java'
|
14
|
+
8. pepper.node 'matico'
|
15
|
+
9. pepper.node 'cubeb'
|
16
|
+
10. end
|
17
|
+
11. end
|
18
|
+
12.
|
19
|
+
13. food.node('fruit') do |fruit|
|
20
|
+
14. fruit.node('red') do |red|
|
21
|
+
15. red.node 'cherry'
|
22
|
+
16. red.node 'apple'
|
23
|
+
17. end
|
24
|
+
18. end
|
25
|
+
19.
|
26
|
+
20. puts food.to_tree
|
27
|
+
|
28
|
+
Line 1 creates a `Trivet::Node` object and assigns it the id "food".
|
29
|
+
Lines 3 to 18 use the `node()` method to create child nodes.
|
30
|
+
|
31
|
+
Line 20 uses the `to_tree()` method to display the tree as text, which
|
32
|
+
looks like this:
|
33
|
+
|
34
|
+
food
|
35
|
+
spices
|
36
|
+
paprika
|
37
|
+
pepper
|
38
|
+
java
|
39
|
+
matico
|
40
|
+
cubeb
|
41
|
+
fruit
|
42
|
+
red
|
43
|
+
cherry
|
44
|
+
apple
|
45
|
+
|
46
|
+
The tree can include non Trivet::Node objects.
|
47
|
+
|
48
|
+
food = Trivet::Node.new('food')
|
49
|
+
|
50
|
+
food.node('spices') do |spices|
|
51
|
+
spices.node('paprika') do |paprika|
|
52
|
+
paprika.children.push 'By from Fred'
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
See documentation for `Trivet::Node`, `Trivet::Document`, and `Trivet::ChildSet`
|
57
|
+
for more documentation.
|
58
|
+
|
59
|
+
## Install
|
60
|
+
|
61
|
+
```
|
62
|
+
gem install codeblock
|
63
|
+
```
|
64
|
+
|
65
|
+
## Name
|
66
|
+
|
67
|
+
The name "Trivet" doesn't have any particular significance. My friend has a cat
|
68
|
+
named "Trivet". Meow.
|
69
|
+
|
70
|
+
## Author
|
71
|
+
|
72
|
+
Mike O'Sullivan
|
73
|
+
mike@idocs.com
|
74
|
+
|
75
|
+
## History
|
76
|
+
|
77
|
+
| version | date | notes |
|
78
|
+
|---------|---------------|-------------------------------------------------------|
|
79
|
+
| 1.0 | June 18, 2020 | Initial upload. |
|
data/lib/trivet.rb
ADDED
@@ -0,0 +1,1418 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
|
3
|
+
|
4
|
+
#===============================================================================
|
5
|
+
# Trivet
|
6
|
+
#
|
7
|
+
|
8
|
+
# The Trivet module itself doesn't do much. It holds the version and a few
|
9
|
+
# constants that are used in queries. You probably want to start with
|
10
|
+
# Trivet::Node to learn about this package.
|
11
|
+
module Trivet
|
12
|
+
# Version
|
13
|
+
VERSION = '1.0'
|
14
|
+
|
15
|
+
#---------------------------------------------------------------------------
|
16
|
+
# query control constants
|
17
|
+
#
|
18
|
+
|
19
|
+
# Always recurse in query.
|
20
|
+
ALWAYS = 0
|
21
|
+
|
22
|
+
# Don't recurse into matched nodes.
|
23
|
+
UNTIL_MATCH = 1
|
24
|
+
|
25
|
+
# Stop entire query when a match is found.
|
26
|
+
STOP_ON_FIRST = 2
|
27
|
+
|
28
|
+
#
|
29
|
+
# query control constants
|
30
|
+
#---------------------------------------------------------------------------
|
31
|
+
end
|
32
|
+
#
|
33
|
+
# Trivet
|
34
|
+
#===============================================================================
|
35
|
+
|
36
|
+
|
37
|
+
#===============================================================================
|
38
|
+
# Trivet::Querier
|
39
|
+
#
|
40
|
+
|
41
|
+
# This module provides the query_first() method for Trivet::Node and
|
42
|
+
# Trivet::Document.
|
43
|
+
module Trivet::Querier
|
44
|
+
# Works like query(), but only returns/yields the first find.
|
45
|
+
def query_first(qobj, opts={})
|
46
|
+
# run a query
|
47
|
+
query(qobj, opts) do |node|
|
48
|
+
# yield
|
49
|
+
if block_given?
|
50
|
+
yield node
|
51
|
+
end
|
52
|
+
|
53
|
+
# return
|
54
|
+
return node
|
55
|
+
end
|
56
|
+
|
57
|
+
# didn't find any such node
|
58
|
+
return nil
|
59
|
+
end
|
60
|
+
end
|
61
|
+
#
|
62
|
+
# Trivet::Querier
|
63
|
+
#===============================================================================
|
64
|
+
|
65
|
+
|
66
|
+
#===============================================================================
|
67
|
+
# Trivet::Node
|
68
|
+
#
|
69
|
+
|
70
|
+
# Objects of this class represent a single node in a hierarchy.
|
71
|
+
class Trivet::Node
|
72
|
+
# delegate
|
73
|
+
extend Forwardable
|
74
|
+
delegate %w(remove_child) => :@children
|
75
|
+
|
76
|
+
|
77
|
+
#---------------------------------------------------------------------------
|
78
|
+
# initialize
|
79
|
+
#
|
80
|
+
|
81
|
+
# Creates a new Trivet::Node object. The first param can be a parent node,
|
82
|
+
# the id of the new node, or nil.
|
83
|
+
def initialize(pod=nil)
|
84
|
+
@id = nil
|
85
|
+
@parent = nil
|
86
|
+
@children = Trivet::Childset.new(self)
|
87
|
+
@misc = {}
|
88
|
+
|
89
|
+
# if parent object send
|
90
|
+
if pod.is_a?(Trivet::Node) or pod.is_a?(Trivet::Document)
|
91
|
+
self.parent = pod
|
92
|
+
elsif pod.is_a?(String)
|
93
|
+
self.id = pod
|
94
|
+
end
|
95
|
+
end
|
96
|
+
#
|
97
|
+
# initialize
|
98
|
+
#---------------------------------------------------------------------------
|
99
|
+
|
100
|
+
|
101
|
+
#---------------------------------------------------------------------------
|
102
|
+
# readers
|
103
|
+
#
|
104
|
+
|
105
|
+
# Returns the parent object of the node, or nil if there is no parent.
|
106
|
+
attr_reader :parent
|
107
|
+
|
108
|
+
# A Trivet::ChildSet object containing the children. This property can
|
109
|
+
# generally be treated like an array, but it has a few other features as
|
110
|
+
# well.
|
111
|
+
attr_reader :children
|
112
|
+
|
113
|
+
# A hash of any miscellaneous information you want to attach to the node.
|
114
|
+
attr_reader :misc
|
115
|
+
|
116
|
+
# Returns the id of the node, or nil if it does not have an id.
|
117
|
+
attr_reader :id
|
118
|
+
|
119
|
+
#
|
120
|
+
# readers
|
121
|
+
#---------------------------------------------------------------------------
|
122
|
+
|
123
|
+
|
124
|
+
#---------------------------------------------------------------------------
|
125
|
+
# can_have_children?
|
126
|
+
#
|
127
|
+
|
128
|
+
# This method indicates if the node can have any children. By default this
|
129
|
+
# method always return true. Override this method in your own class to
|
130
|
+
# set more fine grained rules.
|
131
|
+
def can_have_children?
|
132
|
+
return true
|
133
|
+
end
|
134
|
+
#
|
135
|
+
# can_have_children?
|
136
|
+
#---------------------------------------------------------------------------
|
137
|
+
|
138
|
+
|
139
|
+
#---------------------------------------------------------------------------
|
140
|
+
# parent=
|
141
|
+
#
|
142
|
+
|
143
|
+
# This method is a shortcut for Trivet::Node#set_parent() without any
|
144
|
+
# options.
|
145
|
+
def parent=(new_parent)
|
146
|
+
return set_parent(new_parent)
|
147
|
+
end
|
148
|
+
#
|
149
|
+
# parent=
|
150
|
+
#---------------------------------------------------------------------------
|
151
|
+
|
152
|
+
|
153
|
+
#---------------------------------------------------------------------------
|
154
|
+
# set_parent
|
155
|
+
#
|
156
|
+
|
157
|
+
# Sets a new parent for the node. The new parent can be either a
|
158
|
+
# Trivet::Node object or a Trivet::Document object.
|
159
|
+
#
|
160
|
+
# For example, we'll start with the standard tree we've been using:
|
161
|
+
#
|
162
|
+
# food
|
163
|
+
# spices
|
164
|
+
# paprika
|
165
|
+
# pepper
|
166
|
+
# java
|
167
|
+
# matico
|
168
|
+
# cubeb
|
169
|
+
# fruit
|
170
|
+
# red
|
171
|
+
# cherry
|
172
|
+
# apple
|
173
|
+
#
|
174
|
+
# Now we'll set fruit's parent to the pepper node:
|
175
|
+
#
|
176
|
+
# fruit = food.node_by_id('fruit')
|
177
|
+
# pepper = food.node_by_id('pepper')
|
178
|
+
# fruit.set_parent pepper
|
179
|
+
#
|
180
|
+
# That moves the fruit node and all its descendents into pepper:
|
181
|
+
#
|
182
|
+
# food
|
183
|
+
# spices
|
184
|
+
# paprika
|
185
|
+
# pepper
|
186
|
+
# java
|
187
|
+
# matico
|
188
|
+
# cubeb
|
189
|
+
# fruit
|
190
|
+
# red
|
191
|
+
# cherry
|
192
|
+
# apple
|
193
|
+
#
|
194
|
+
# option: index
|
195
|
+
#
|
196
|
+
# The index option indicates which position within the parent the node
|
197
|
+
# should be moved to. This option has no meaning if the new parent is a
|
198
|
+
# Trivet::Document object.
|
199
|
+
#
|
200
|
+
# Set index to 'first' to move it to the first position:
|
201
|
+
#
|
202
|
+
# fruit = food.node_by_id('fruit')
|
203
|
+
# pepper = food.node_by_id('pepper')
|
204
|
+
# fruit.set_parent pepper, 'index'=>'first'
|
205
|
+
#
|
206
|
+
# Set index to an integer to move the node to that index within the parent.
|
207
|
+
#
|
208
|
+
# fruit = food.node_by_id('fruit')
|
209
|
+
# pepper = food.node_by_id('pepper')
|
210
|
+
# fruit.set_parent pepper, 'index'=>1
|
211
|
+
#
|
212
|
+
# nil
|
213
|
+
#
|
214
|
+
# If the parent param is nil then the object is unlinked from its parent.
|
215
|
+
def set_parent(new_parent, opts={})
|
216
|
+
# $tm.hrm @id
|
217
|
+
opts = {'recurse'=>true}.merge(opts)
|
218
|
+
index = opts['index'] || 'last'
|
219
|
+
|
220
|
+
# add to new_parent
|
221
|
+
if new_parent
|
222
|
+
# add to another node
|
223
|
+
if new_parent.is_a?(Trivet::Node)
|
224
|
+
new_parent.trace(self)
|
225
|
+
unlink()
|
226
|
+
@parent = new_parent
|
227
|
+
|
228
|
+
# add to parent's children
|
229
|
+
if opts['recurse']
|
230
|
+
if index == 'last'
|
231
|
+
@parent.children.push self, 'recurse'=>false
|
232
|
+
elsif index == 'first'
|
233
|
+
@parent.children.unshift self, 'recurse'=>false
|
234
|
+
elsif index.is_a?(Integer)
|
235
|
+
@parent.children.insert index, self, 'recurse'=>false
|
236
|
+
else
|
237
|
+
raise 'set-parent-unknown-index: ' + index.to_s
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
# new parent is a document
|
242
|
+
elsif new_parent.is_a?(Trivet::Document)
|
243
|
+
unlink()
|
244
|
+
@parent = new_parent
|
245
|
+
|
246
|
+
# set as document's root
|
247
|
+
if opts['recurse']
|
248
|
+
new_parent.set_root self, 'recurse'=>false
|
249
|
+
end
|
250
|
+
|
251
|
+
# else raise exception
|
252
|
+
else
|
253
|
+
raise 'unknown-class-for-parent: ' + new_parent.class.to_s
|
254
|
+
end
|
255
|
+
|
256
|
+
# unlink node because it does not have a parent anymore.
|
257
|
+
else
|
258
|
+
unlink()
|
259
|
+
end
|
260
|
+
end
|
261
|
+
#
|
262
|
+
# set_parent
|
263
|
+
#---------------------------------------------------------------------------
|
264
|
+
|
265
|
+
|
266
|
+
#---------------------------------------------------------------------------
|
267
|
+
# node
|
268
|
+
#
|
269
|
+
|
270
|
+
# Values for positioning a node within its parent. These values are used
|
271
|
+
# internally only.
|
272
|
+
INDEX_WITHIN = %w{first last}
|
273
|
+
|
274
|
+
# Values for positioning a node before or after a sibling. These values are
|
275
|
+
# used internally only.
|
276
|
+
INDEX_WITHOUT = %w{before after}
|
277
|
+
|
278
|
+
##
|
279
|
+
# Create a node and positions the new node either within the calling node,
|
280
|
+
# before it, after it, or replaces it. By default, the new node is
|
281
|
+
# positioned as the last child node of the caller.
|
282
|
+
#
|
283
|
+
# In its simplest use, node() creates a new node, yields it if given a
|
284
|
+
# block, and returns it. You can build structures by calling node() within
|
285
|
+
# node blocks. If a single string is given as a param, that string is used
|
286
|
+
# for the `id` for the new node.
|
287
|
+
#
|
288
|
+
# food = Trivet::Node.new()
|
289
|
+
# food.id = 'food'
|
290
|
+
#
|
291
|
+
# food.node('spices') do |spices|
|
292
|
+
# spices.node 'paprika'
|
293
|
+
#
|
294
|
+
# spices.node('pepper') do |pepper|
|
295
|
+
# pepper.node 'java'
|
296
|
+
# pepper.node 'matico'
|
297
|
+
# pepper.node 'cubeb'
|
298
|
+
# end
|
299
|
+
# end
|
300
|
+
#
|
301
|
+
# option: index
|
302
|
+
#
|
303
|
+
# The index option indicates where the new node should be positioned. This
|
304
|
+
# option can have one of the following values:
|
305
|
+
#
|
306
|
+
# - first: The new node becomes the first child of the caller object.
|
307
|
+
# - last: The new node becomes the last child of the caller object. This is the default behavior.
|
308
|
+
# - before: The new node is placed before the caller object.
|
309
|
+
# - after: The new node is placed after the caller object.
|
310
|
+
# - replace: The node node replaces the caller object and the caller is removed from the tree.
|
311
|
+
# - [integer]: The new node is placed at the index of the given integer.
|
312
|
+
#
|
313
|
+
def node(opts={})
|
314
|
+
# normalize opts
|
315
|
+
if opts.is_a?(String)
|
316
|
+
opts = {'id'=>opts}
|
317
|
+
end
|
318
|
+
|
319
|
+
# $tm.hrm
|
320
|
+
idx = opts['index'] || 'last'
|
321
|
+
|
322
|
+
# create child object
|
323
|
+
new_node = child_class(opts).new()
|
324
|
+
|
325
|
+
# id if sent
|
326
|
+
if opts['id']
|
327
|
+
new_node.id = opts['id']
|
328
|
+
end
|
329
|
+
|
330
|
+
# add to this node
|
331
|
+
if INDEX_WITHIN.include?(idx) or idx.is_a?(Integer)
|
332
|
+
new_node.set_parent self, 'index'=>idx
|
333
|
+
|
334
|
+
# add to parent
|
335
|
+
elsif INDEX_WITHOUT.include?(idx)
|
336
|
+
if @parent
|
337
|
+
if idx == 'before'
|
338
|
+
new_node.set_parent @parent, 'index'=>self.index
|
339
|
+
elsif idx == 'after'
|
340
|
+
new_node.set_parent @parent, 'index'=>self.index + 1
|
341
|
+
else
|
342
|
+
raise 'unrecognized-without-index: ' + idx.to_s
|
343
|
+
end
|
344
|
+
else
|
345
|
+
raise 'cannot-set-before-or-after-if-no-parent'
|
346
|
+
end
|
347
|
+
|
348
|
+
# replace
|
349
|
+
elsif idx == 'replace'
|
350
|
+
new_node.set_parent @parent, 'index'=>self.index
|
351
|
+
|
352
|
+
@children.to_a.each do |child|
|
353
|
+
child.parent = new_node
|
354
|
+
end
|
355
|
+
|
356
|
+
unlink()
|
357
|
+
else
|
358
|
+
raise 'node-unknown-index: ' + idx.to_s
|
359
|
+
end
|
360
|
+
|
361
|
+
# yield if necessary
|
362
|
+
if block_given?
|
363
|
+
yield new_node
|
364
|
+
end
|
365
|
+
|
366
|
+
# return
|
367
|
+
return new_node
|
368
|
+
end
|
369
|
+
#
|
370
|
+
# node
|
371
|
+
#---------------------------------------------------------------------------
|
372
|
+
|
373
|
+
|
374
|
+
#---------------------------------------------------------------------------
|
375
|
+
# id=
|
376
|
+
#
|
377
|
+
|
378
|
+
# Sets the id property of the node. If any other node already has that id
|
379
|
+
# then an exception is raised.
|
380
|
+
def id=(new_id)
|
381
|
+
# $tm.hrm
|
382
|
+
|
383
|
+
# look for another node with this id
|
384
|
+
if new_id
|
385
|
+
root.traverse('self'=>true) do |tag|
|
386
|
+
if (tag.id == new_id) and (tag != self)
|
387
|
+
raise 'redundant-id: ' + new_id.to_s
|
388
|
+
end
|
389
|
+
end
|
390
|
+
end
|
391
|
+
|
392
|
+
# set id
|
393
|
+
@id = new_id
|
394
|
+
end
|
395
|
+
#
|
396
|
+
# id=
|
397
|
+
#---------------------------------------------------------------------------
|
398
|
+
|
399
|
+
|
400
|
+
#---------------------------------------------------------------------------
|
401
|
+
# index
|
402
|
+
#
|
403
|
+
|
404
|
+
# Returns the index of the node within the parent. Returns nil if the node
|
405
|
+
# has no parent.
|
406
|
+
def index
|
407
|
+
if @parent
|
408
|
+
return @parent.children.find_index(self)
|
409
|
+
else
|
410
|
+
return nil
|
411
|
+
end
|
412
|
+
end
|
413
|
+
#
|
414
|
+
# index
|
415
|
+
#---------------------------------------------------------------------------
|
416
|
+
|
417
|
+
|
418
|
+
#---------------------------------------------------------------------------
|
419
|
+
# traverse
|
420
|
+
#
|
421
|
+
|
422
|
+
# Traverses the tree starting with the children of the node. Each node in
|
423
|
+
# the tree is yielded to the block if there is one. Returns and array of
|
424
|
+
# descendents.
|
425
|
+
#
|
426
|
+
# food.traverse() do |node|
|
427
|
+
# puts (' ' * node.depth) + node.id
|
428
|
+
# end
|
429
|
+
#
|
430
|
+
# That gives us output similar to that of the to_tree() method.
|
431
|
+
#
|
432
|
+
# spices
|
433
|
+
# paprika
|
434
|
+
# pepper
|
435
|
+
# java
|
436
|
+
# matico
|
437
|
+
# cubeb
|
438
|
+
# fruit
|
439
|
+
# red
|
440
|
+
# cherry
|
441
|
+
# apple
|
442
|
+
#
|
443
|
+
# By default, the node on which you call this method itself is not
|
444
|
+
# traversed. You can include that node with the 'self' option:
|
445
|
+
#
|
446
|
+
# food.traverse('self'=>true) do |node|
|
447
|
+
# puts (' ' * node.depth) + node.id
|
448
|
+
# end
|
449
|
+
#
|
450
|
+
# The first parameter for the yield block is the node, as in the examples
|
451
|
+
# above. The second param is a Trivet::TraverseControl object which can be
|
452
|
+
# used to control the recursion.
|
453
|
+
#
|
454
|
+
# Prune recursion
|
455
|
+
#
|
456
|
+
# You can indicate that the traversal should not recurse into a node's
|
457
|
+
# children with Trivet::TraverseControl.prune. For example, the following
|
458
|
+
# code doesn't traverse into the spices node:
|
459
|
+
# food.traverse('self'=>true) do |node, ctl|
|
460
|
+
# puts (' ' * node.depth) + node.id
|
461
|
+
#
|
462
|
+
# if node.id == 'spices'
|
463
|
+
# ctl.prune
|
464
|
+
# end
|
465
|
+
# end
|
466
|
+
#
|
467
|
+
# Giving us this output:
|
468
|
+
#
|
469
|
+
# food
|
470
|
+
# spices
|
471
|
+
# fruit
|
472
|
+
# red
|
473
|
+
# cherry
|
474
|
+
# apple
|
475
|
+
#
|
476
|
+
# Stop recursion
|
477
|
+
#
|
478
|
+
# You can stop the recursion by calling Trivet::TraverseControl.stop. For
|
479
|
+
# example, suppose you want to stop the traversal completely when you get to
|
480
|
+
# the "java" node. You could do that like this:
|
481
|
+
#
|
482
|
+
# food.traverse('self'=>true) do |node, ctl|
|
483
|
+
# puts (' ' * node.depth) + node.id
|
484
|
+
#
|
485
|
+
# if node.id == 'java'
|
486
|
+
# ctl.stop
|
487
|
+
# end
|
488
|
+
# end
|
489
|
+
#
|
490
|
+
# That gives us output like this:
|
491
|
+
#
|
492
|
+
# food
|
493
|
+
# spices
|
494
|
+
# paprika
|
495
|
+
# pepper
|
496
|
+
# java
|
497
|
+
def traverse(opts={}, &block)
|
498
|
+
ctrl = opts['ctrl'] || Trivet::TraverseControl.new()
|
499
|
+
rv = []
|
500
|
+
|
501
|
+
# yield self
|
502
|
+
if opts['self']
|
503
|
+
# yield
|
504
|
+
if block_given?
|
505
|
+
yield self, ctrl
|
506
|
+
end
|
507
|
+
|
508
|
+
# add to return array
|
509
|
+
rv.push self
|
510
|
+
|
511
|
+
# return if stopped
|
512
|
+
ctrl.stopped and return rv
|
513
|
+
end
|
514
|
+
|
515
|
+
# if pruned, don't recurse into children, but continue to next sibling node
|
516
|
+
if ctrl.pruned
|
517
|
+
ctrl.pruned = false
|
518
|
+
|
519
|
+
# recurse into children
|
520
|
+
else
|
521
|
+
# puts "--- #{self}"
|
522
|
+
|
523
|
+
@children.to_a.each do |child|
|
524
|
+
if child.is_a?(Trivet::Node)
|
525
|
+
rv += child.traverse('self'=>true, 'ctrl'=>ctrl, &block)
|
526
|
+
ctrl.stopped and return rv
|
527
|
+
ctrl.pruned = false
|
528
|
+
end
|
529
|
+
end
|
530
|
+
end
|
531
|
+
|
532
|
+
# return
|
533
|
+
return rv
|
534
|
+
end
|
535
|
+
#
|
536
|
+
# traverse
|
537
|
+
#---------------------------------------------------------------------------
|
538
|
+
|
539
|
+
|
540
|
+
#---------------------------------------------------------------------------
|
541
|
+
# child_class
|
542
|
+
#
|
543
|
+
|
544
|
+
# Returns the class to use for new nodes in Trivet::Node.node(). By default,
|
545
|
+
# this method returns the same class as the calling node. Override this
|
546
|
+
# method to create different rules for the class to use.
|
547
|
+
def child_class(*opts)
|
548
|
+
return self.class
|
549
|
+
end
|
550
|
+
#
|
551
|
+
# child_class
|
552
|
+
#---------------------------------------------------------------------------
|
553
|
+
|
554
|
+
|
555
|
+
#---------------------------------------------------------------------------
|
556
|
+
# root
|
557
|
+
#
|
558
|
+
|
559
|
+
# Returns the root node of the tree. Returns self if the node has no
|
560
|
+
# parent.
|
561
|
+
def root
|
562
|
+
if @parent
|
563
|
+
return @parent.root
|
564
|
+
else
|
565
|
+
return self
|
566
|
+
end
|
567
|
+
end
|
568
|
+
#
|
569
|
+
# root
|
570
|
+
#---------------------------------------------------------------------------
|
571
|
+
|
572
|
+
|
573
|
+
#---------------------------------------------------------------------------
|
574
|
+
# unlink
|
575
|
+
#
|
576
|
+
|
577
|
+
# Removes the node from the tree.
|
578
|
+
def unlink(opts={})
|
579
|
+
# $tm.hrm
|
580
|
+
opts = {'recurse'=>true}.merge(opts)
|
581
|
+
|
582
|
+
# remove from parent
|
583
|
+
if @parent and opts['recurse']
|
584
|
+
if @parent.is_a?(Trivet::Document)
|
585
|
+
@parent.set_root nil, 'recurse'=>false
|
586
|
+
elsif @parent.is_a?(Trivet::Node)
|
587
|
+
@parent.remove_child self
|
588
|
+
else
|
589
|
+
raise 'unlink-unknown-parent-class: ' + @parent.class.to_
|
590
|
+
end
|
591
|
+
end
|
592
|
+
|
593
|
+
# set parent to nil
|
594
|
+
@parent = nil
|
595
|
+
end
|
596
|
+
#
|
597
|
+
# unlink
|
598
|
+
#---------------------------------------------------------------------------
|
599
|
+
|
600
|
+
|
601
|
+
#---------------------------------------------------------------------------
|
602
|
+
# ancestors
|
603
|
+
#
|
604
|
+
|
605
|
+
# Returns an array of the node's ancestors.
|
606
|
+
def ancestors
|
607
|
+
if @parent.is_a?(Trivet::Node)
|
608
|
+
return @parent.heritage()
|
609
|
+
else
|
610
|
+
return []
|
611
|
+
end
|
612
|
+
end
|
613
|
+
#
|
614
|
+
# ancestors
|
615
|
+
#---------------------------------------------------------------------------
|
616
|
+
|
617
|
+
|
618
|
+
#---------------------------------------------------------------------------
|
619
|
+
# heritage
|
620
|
+
#
|
621
|
+
|
622
|
+
# Returns an array of the node's ancestors plus the node itself.
|
623
|
+
def heritage
|
624
|
+
if @parent.is_a?(Trivet::Node)
|
625
|
+
return @parent.heritage() + [self]
|
626
|
+
else
|
627
|
+
return [self]
|
628
|
+
end
|
629
|
+
end
|
630
|
+
#
|
631
|
+
# heritage
|
632
|
+
#---------------------------------------------------------------------------
|
633
|
+
|
634
|
+
|
635
|
+
#---------------------------------------------------------------------------
|
636
|
+
# trace
|
637
|
+
#
|
638
|
+
|
639
|
+
# Checks if a node is about to become nested within itself. This method is
|
640
|
+
# used by Trivet::Node.set_parent to prevent circular references. Generally
|
641
|
+
# you don't need to call this method.
|
642
|
+
def trace(new_node)
|
643
|
+
if new_node == self
|
644
|
+
raise 'circular-reference'
|
645
|
+
end
|
646
|
+
|
647
|
+
# trace to parent
|
648
|
+
if @parent and not(@parent.is_a?(Trivet::Document))
|
649
|
+
@parent.trace(new_node)
|
650
|
+
end
|
651
|
+
end
|
652
|
+
#
|
653
|
+
# trace
|
654
|
+
#---------------------------------------------------------------------------
|
655
|
+
|
656
|
+
|
657
|
+
#---------------------------------------------------------------------------
|
658
|
+
# query
|
659
|
+
#
|
660
|
+
|
661
|
+
# Runs a query on the tree, yielding and returning nodes that match the
|
662
|
+
# given query. This method is really only useful if you subclass
|
663
|
+
# Trivet::Node and override Trivet::Node.match?(). By default, match? always
|
664
|
+
# returns true. The query param can be any kind of object you want it to be.
|
665
|
+
# That param will be passed to match? for each node. If match? returns true
|
666
|
+
# then the query yields/returns that node.
|
667
|
+
#
|
668
|
+
# In these examples, we'll assume a tree like this:
|
669
|
+
#
|
670
|
+
# food
|
671
|
+
# spices
|
672
|
+
# paprika
|
673
|
+
# pepper
|
674
|
+
# java
|
675
|
+
# matico
|
676
|
+
# cubeb
|
677
|
+
# fruit
|
678
|
+
# red
|
679
|
+
# cherry
|
680
|
+
# apple
|
681
|
+
#
|
682
|
+
# For example, you could override match? so that it returns true if a given
|
683
|
+
# string is within a node's id.
|
684
|
+
#
|
685
|
+
# class MyNode < Trivet::Node
|
686
|
+
# def match?(qobj)
|
687
|
+
# if @id
|
688
|
+
# return @id.match(/#{qobj}/mu)
|
689
|
+
# else
|
690
|
+
# return false
|
691
|
+
# end
|
692
|
+
# end
|
693
|
+
# end
|
694
|
+
#
|
695
|
+
#
|
696
|
+
# You could then query for nodes that have 'o' in their id:
|
697
|
+
#
|
698
|
+
# food = MyNode.new('food')
|
699
|
+
#
|
700
|
+
# # ... add a bunch of child nodes
|
701
|
+
#
|
702
|
+
# food.query('o') do |node|
|
703
|
+
# puts node.id
|
704
|
+
# end
|
705
|
+
#
|
706
|
+
# That query returns one node, 'matico'
|
707
|
+
#
|
708
|
+
# option: self
|
709
|
+
#
|
710
|
+
# By default, the query does not include the node itself, only its
|
711
|
+
# descendents. To include the node itself, use the 'self' option.
|
712
|
+
#
|
713
|
+
# food.query('o', 'self'=>true) do |node|
|
714
|
+
# puts node.id
|
715
|
+
# end
|
716
|
+
#
|
717
|
+
# That query will return 'food' and 'matico'.
|
718
|
+
#
|
719
|
+
# option: recurse
|
720
|
+
#
|
721
|
+
# The recurse option indicates how far into the tree the query should
|
722
|
+
# recurse. The default is Trivet::ALWAYS, which means to recurse all the way
|
723
|
+
# down.
|
724
|
+
#
|
725
|
+
# Trivet::UNTIL_MATCH means to not recurse into nodes that match the query.
|
726
|
+
# So if a node matches, its children are not included in the query.
|
727
|
+
#
|
728
|
+
# Trivet::STOP_ON_FIRST means that the query is stopped completely after the
|
729
|
+
# first match is found. Using this option means that the query always
|
730
|
+
# returns either zero or one matches.
|
731
|
+
#
|
732
|
+
def query(qobj, opts={})
|
733
|
+
ctl = opts['recurse'] || Trivet::ALWAYS
|
734
|
+
rv = []
|
735
|
+
|
736
|
+
# traverse
|
737
|
+
self.traverse(opts) do |node, recurse|
|
738
|
+
if node.match?(qobj)
|
739
|
+
# add to return array
|
740
|
+
rv.push node
|
741
|
+
|
742
|
+
if ctl == Trivet::UNTIL_MATCH
|
743
|
+
recurse.prune
|
744
|
+
elsif ctl == Trivet::STOP_ON_FIRST
|
745
|
+
recurse.stop
|
746
|
+
end
|
747
|
+
end
|
748
|
+
end
|
749
|
+
|
750
|
+
# yield
|
751
|
+
if block_given?
|
752
|
+
rv.each do |node|
|
753
|
+
yield node
|
754
|
+
end
|
755
|
+
end
|
756
|
+
|
757
|
+
# return
|
758
|
+
return rv
|
759
|
+
end
|
760
|
+
#
|
761
|
+
# query
|
762
|
+
#---------------------------------------------------------------------------
|
763
|
+
|
764
|
+
|
765
|
+
# include method from querier
|
766
|
+
include Trivet::Querier
|
767
|
+
|
768
|
+
|
769
|
+
#---------------------------------------------------------------------------
|
770
|
+
# match?
|
771
|
+
#
|
772
|
+
|
773
|
+
# This method is called for each node in a query. If this method return true
|
774
|
+
# then the node is yielded/returned in the query. By default, this method
|
775
|
+
# always returns true. See Trivet::Node#query() for more details.
|
776
|
+
def match?(qobj)
|
777
|
+
return true
|
778
|
+
end
|
779
|
+
#
|
780
|
+
# match?
|
781
|
+
#---------------------------------------------------------------------------
|
782
|
+
|
783
|
+
|
784
|
+
#---------------------------------------------------------------------------
|
785
|
+
# to_s
|
786
|
+
#
|
787
|
+
|
788
|
+
# Returns the id if there is one. Otherwise returns Object#to_s.
|
789
|
+
def to_s
|
790
|
+
if @id
|
791
|
+
return @id
|
792
|
+
else
|
793
|
+
return super()
|
794
|
+
end
|
795
|
+
end
|
796
|
+
#
|
797
|
+
# to_s
|
798
|
+
#---------------------------------------------------------------------------
|
799
|
+
|
800
|
+
|
801
|
+
#---------------------------------------------------------------------------
|
802
|
+
# to_tree
|
803
|
+
#
|
804
|
+
|
805
|
+
# This method is mainly for development and debugging. Returns a string
|
806
|
+
# consisting of the node and all its descending arranged as an indented
|
807
|
+
# tree. Each node's Trivet::Node#to_s method is used to display the node.
|
808
|
+
# Descendents that are not Trivet::Node objects are not displayed.
|
809
|
+
def to_tree(opts={})
|
810
|
+
opts = {'depth'=>0}.merge(opts)
|
811
|
+
rv = ' ' * opts['depth'] + self.to_s
|
812
|
+
|
813
|
+
# indent
|
814
|
+
if opts['indent']
|
815
|
+
rv = opts['indent'] + rv
|
816
|
+
end
|
817
|
+
|
818
|
+
# send_opts
|
819
|
+
send_opts = opts.clone
|
820
|
+
send_opts['depth'] += 1
|
821
|
+
|
822
|
+
# recurse
|
823
|
+
@children.each do |child|
|
824
|
+
if child.is_a?(Trivet::Node)
|
825
|
+
rv += "\n" + child.to_tree(send_opts)
|
826
|
+
end
|
827
|
+
end
|
828
|
+
|
829
|
+
# return
|
830
|
+
return rv
|
831
|
+
end
|
832
|
+
#
|
833
|
+
# to_tree
|
834
|
+
#---------------------------------------------------------------------------
|
835
|
+
|
836
|
+
|
837
|
+
#---------------------------------------------------------------------------
|
838
|
+
# node_by_id
|
839
|
+
#
|
840
|
+
|
841
|
+
# Searches for a node by its id.
|
842
|
+
def node_by_id(qid)
|
843
|
+
if @id == qid
|
844
|
+
return self
|
845
|
+
end
|
846
|
+
|
847
|
+
# check children
|
848
|
+
@children.each do |child|
|
849
|
+
if child.is_a?(Trivet::Node)
|
850
|
+
if node = child.node_by_id(qid)
|
851
|
+
return node
|
852
|
+
end
|
853
|
+
end
|
854
|
+
end
|
855
|
+
|
856
|
+
# didn't find the node
|
857
|
+
return nil
|
858
|
+
end
|
859
|
+
#
|
860
|
+
# node_by_id
|
861
|
+
#---------------------------------------------------------------------------
|
862
|
+
|
863
|
+
|
864
|
+
#---------------------------------------------------------------------------
|
865
|
+
# can_have_child?
|
866
|
+
#
|
867
|
+
|
868
|
+
# This methd is called when a node or other object is to be set as the child
|
869
|
+
# of the current node. By default, always returns true. Override this method
|
870
|
+
# to create custom rules.
|
871
|
+
def can_have_child?(child)
|
872
|
+
return true
|
873
|
+
end
|
874
|
+
#
|
875
|
+
# can_have_child?
|
876
|
+
#---------------------------------------------------------------------------
|
877
|
+
|
878
|
+
|
879
|
+
#---------------------------------------------------------------------------
|
880
|
+
# document
|
881
|
+
#
|
882
|
+
|
883
|
+
# Returns the Trivet::Document object that holds the tree. Returns nil if
|
884
|
+
# there is no document.
|
885
|
+
def document()
|
886
|
+
# $tm.hrm
|
887
|
+
return root.parent
|
888
|
+
end
|
889
|
+
#
|
890
|
+
# document
|
891
|
+
#---------------------------------------------------------------------------
|
892
|
+
|
893
|
+
|
894
|
+
#---------------------------------------------------------------------------
|
895
|
+
# unwrap
|
896
|
+
#
|
897
|
+
|
898
|
+
# Moves the child nodes into the node's parent and deletes self.
|
899
|
+
def unwrap()
|
900
|
+
# $tm.hrm
|
901
|
+
pchildren = @parent.children
|
902
|
+
|
903
|
+
# move children
|
904
|
+
@children.to_a.each do |child|
|
905
|
+
pchildren.insert self.index, child
|
906
|
+
end
|
907
|
+
|
908
|
+
# unlink self
|
909
|
+
unlink()
|
910
|
+
end
|
911
|
+
#
|
912
|
+
# unwrap
|
913
|
+
#---------------------------------------------------------------------------
|
914
|
+
|
915
|
+
|
916
|
+
#---------------------------------------------------------------------------
|
917
|
+
# depth
|
918
|
+
#
|
919
|
+
|
920
|
+
# Returns the depth of the node. The root note returns 0, its children
|
921
|
+
# return 1, etc.
|
922
|
+
def depth
|
923
|
+
return ancestors.length
|
924
|
+
end
|
925
|
+
#
|
926
|
+
# depth
|
927
|
+
#---------------------------------------------------------------------------
|
928
|
+
end
|
929
|
+
#
|
930
|
+
# Trivet::Node
|
931
|
+
#===============================================================================
|
932
|
+
|
933
|
+
|
934
|
+
#===============================================================================
|
935
|
+
# Trivet::Childset
|
936
|
+
#
|
937
|
+
|
938
|
+
# Objects of this class act like an array of the children of a node. However,
|
939
|
+
# unlike an array, attempts to add children result in calling
|
940
|
+
# Trivet::Node#can_have_children? and Trivet::Node#can_have_child?
|
941
|
+
# to check if the child is allowed.
|
942
|
+
class Trivet::Childset
|
943
|
+
#---------------------------------------------------------------------------
|
944
|
+
# delegate
|
945
|
+
#
|
946
|
+
extend Forwardable
|
947
|
+
|
948
|
+
delegate %w(
|
949
|
+
[] each each_with_index length include? find_index reverse
|
950
|
+
all? at any? empty? reject + - collect cycle) => :@children;
|
951
|
+
#
|
952
|
+
# delegate
|
953
|
+
#---------------------------------------------------------------------------
|
954
|
+
|
955
|
+
|
956
|
+
#---------------------------------------------------------------------------
|
957
|
+
# initialize
|
958
|
+
#
|
959
|
+
|
960
|
+
# Accepts a Trivet::Node object to which this object will be attached.
|
961
|
+
def initialize(node)
|
962
|
+
@node = node
|
963
|
+
@children = []
|
964
|
+
end
|
965
|
+
#
|
966
|
+
# initialize
|
967
|
+
#---------------------------------------------------------------------------
|
968
|
+
|
969
|
+
|
970
|
+
#---------------------------------------------------------------------------
|
971
|
+
# reader
|
972
|
+
#
|
973
|
+
|
974
|
+
# Returns the Trivet::Node object that this object is attached to.
|
975
|
+
attr_reader :node
|
976
|
+
|
977
|
+
#
|
978
|
+
# reader
|
979
|
+
#---------------------------------------------------------------------------
|
980
|
+
|
981
|
+
|
982
|
+
#---------------------------------------------------------------------------
|
983
|
+
# method_missing
|
984
|
+
# I was going to delegate all missing methods to the @children array.
|
985
|
+
# However, I need to go through all of Array's methods to check if they need
|
986
|
+
# to be overridden to accomodate the rules for this class.
|
987
|
+
#
|
988
|
+
# Delegates all of an array's methods to @children, except those defined in
|
989
|
+
# this class.
|
990
|
+
# def method_missing(method, *args, &block)
|
991
|
+
# if @children.respond_to?(method)
|
992
|
+
# return @children.send(method, *args, &block)
|
993
|
+
# else
|
994
|
+
# return super(*args, &block)
|
995
|
+
# end
|
996
|
+
# end
|
997
|
+
#
|
998
|
+
# method_missing
|
999
|
+
#---------------------------------------------------------------------------
|
1000
|
+
|
1001
|
+
|
1002
|
+
#---------------------------------------------------------------------------
|
1003
|
+
# methods for adding a child to the array
|
1004
|
+
#
|
1005
|
+
|
1006
|
+
# Adds a child to the end of the array. Calls
|
1007
|
+
# Trivet::Node#can_have_children? and Trivet::Node#can_have_child? to
|
1008
|
+
# check if the child can be added. Does nothing if the node is already a
|
1009
|
+
# child.
|
1010
|
+
def push(new_child, opts={})
|
1011
|
+
return add_child new_child, 'last', opts
|
1012
|
+
end
|
1013
|
+
|
1014
|
+
# Adds a child to the end of the array. Calls
|
1015
|
+
# Trivet::Node#can_have_children? and Trivet::Node#can_have_child? to
|
1016
|
+
# check if the child can be added. Does nothing if the node is already a
|
1017
|
+
# child.
|
1018
|
+
def append(new_child, opts={})
|
1019
|
+
return add_child new_child, 'last', opts
|
1020
|
+
end
|
1021
|
+
|
1022
|
+
# Shortcut for append without any options.
|
1023
|
+
def <<(new_child)
|
1024
|
+
return append(new_child)
|
1025
|
+
end
|
1026
|
+
|
1027
|
+
# Adds a child to the beginning of the array. Calls
|
1028
|
+
# Trivet::Node#can_have_children? and Trivet::Node#can_have_child? to
|
1029
|
+
# check if the child can be added. Does nothing if the node is already a
|
1030
|
+
# child.
|
1031
|
+
def unshift(new_child, opts={})
|
1032
|
+
return add_child new_child, 'first', opts
|
1033
|
+
end
|
1034
|
+
|
1035
|
+
# Inserts the child at the position indicated by index. Calls
|
1036
|
+
# Trivet::Node#can_have_children? and Trivet::Node#can_have_child? to
|
1037
|
+
# check if the child can be added. Does nothing if the node is already a
|
1038
|
+
# child.
|
1039
|
+
def insert(index, new_child, opts={})
|
1040
|
+
return add_child new_child, index, opts
|
1041
|
+
end
|
1042
|
+
#
|
1043
|
+
# methods for adding a child to the array
|
1044
|
+
#---------------------------------------------------------------------------
|
1045
|
+
|
1046
|
+
|
1047
|
+
#---------------------------------------------------------------------------
|
1048
|
+
# remove_child
|
1049
|
+
#
|
1050
|
+
|
1051
|
+
# Removes the given child from the array of children. Does nothing if the
|
1052
|
+
# child isn't present.
|
1053
|
+
def remove_child(child, opts={})
|
1054
|
+
opts = {'recurse'=>true}.merge(opts)
|
1055
|
+
|
1056
|
+
@children.reject!() do |el|
|
1057
|
+
el == child
|
1058
|
+
end
|
1059
|
+
end
|
1060
|
+
|
1061
|
+
# Removes all children.
|
1062
|
+
def clear()
|
1063
|
+
@children.clone.each do |child|
|
1064
|
+
child.unlink
|
1065
|
+
end
|
1066
|
+
end
|
1067
|
+
#
|
1068
|
+
# remove_child
|
1069
|
+
#---------------------------------------------------------------------------
|
1070
|
+
|
1071
|
+
|
1072
|
+
#---------------------------------------------------------------------------
|
1073
|
+
# shift
|
1074
|
+
#
|
1075
|
+
|
1076
|
+
# Unlinks and returns the first child. Returns nil if there are no children.
|
1077
|
+
def shift()
|
1078
|
+
# remove first element
|
1079
|
+
removed = @children.shift
|
1080
|
+
|
1081
|
+
# if a node was removed, unlink it
|
1082
|
+
if removed.is_a?(Trivet::Node)
|
1083
|
+
removed.unlink 'recurse'=>false
|
1084
|
+
end
|
1085
|
+
|
1086
|
+
# return
|
1087
|
+
return removed
|
1088
|
+
end
|
1089
|
+
#
|
1090
|
+
# shift
|
1091
|
+
#---------------------------------------------------------------------------
|
1092
|
+
|
1093
|
+
|
1094
|
+
#---------------------------------------------------------------------------
|
1095
|
+
# pop
|
1096
|
+
#
|
1097
|
+
|
1098
|
+
# Unlinks and returns the last child. Returns nil if there are no children.
|
1099
|
+
def pop()
|
1100
|
+
# remove first element
|
1101
|
+
removed = @children.pop
|
1102
|
+
|
1103
|
+
# if a node was removed, unlink it
|
1104
|
+
if removed.is_a?(Trivet::Node)
|
1105
|
+
removed.unlink 'recurse'=>false
|
1106
|
+
end
|
1107
|
+
|
1108
|
+
# return
|
1109
|
+
return removed
|
1110
|
+
end
|
1111
|
+
#
|
1112
|
+
# pop
|
1113
|
+
#---------------------------------------------------------------------------
|
1114
|
+
|
1115
|
+
|
1116
|
+
#---------------------------------------------------------------------------
|
1117
|
+
# reject!
|
1118
|
+
#
|
1119
|
+
|
1120
|
+
# Acts like Array#reject!. Rejected children are unlinked.
|
1121
|
+
def reject!()
|
1122
|
+
# If no block, just return an enumerator. This mimics the behavior of
|
1123
|
+
# Array#reject!.
|
1124
|
+
if not block_given?
|
1125
|
+
return enum_for(:each)
|
1126
|
+
end
|
1127
|
+
|
1128
|
+
# $tm.hrm
|
1129
|
+
all = @children.clone
|
1130
|
+
|
1131
|
+
# loop through all
|
1132
|
+
all.each do |child|
|
1133
|
+
bool = yield(child)
|
1134
|
+
bool or remove_child(child)
|
1135
|
+
end
|
1136
|
+
|
1137
|
+
# return
|
1138
|
+
return self
|
1139
|
+
end
|
1140
|
+
#
|
1141
|
+
# reject!
|
1142
|
+
#---------------------------------------------------------------------------
|
1143
|
+
|
1144
|
+
|
1145
|
+
#---------------------------------------------------------------------------
|
1146
|
+
# to_a
|
1147
|
+
#
|
1148
|
+
|
1149
|
+
# Returns an array of the children.
|
1150
|
+
def to_a
|
1151
|
+
return @children.clone
|
1152
|
+
end
|
1153
|
+
#
|
1154
|
+
# to_a
|
1155
|
+
#---------------------------------------------------------------------------
|
1156
|
+
|
1157
|
+
|
1158
|
+
# private
|
1159
|
+
private
|
1160
|
+
|
1161
|
+
|
1162
|
+
#---------------------------------------------------------------------------
|
1163
|
+
# add_child
|
1164
|
+
#
|
1165
|
+
def add_child(new_child, index, opts)
|
1166
|
+
opts = {'recurse'=>true}.merge(opts)
|
1167
|
+
|
1168
|
+
# check if this parent is allowed to have this child
|
1169
|
+
if not @node.can_have_children?
|
1170
|
+
raise 'parent-cannot-have-children: ' + @node.to_s + ' / ' + new_child.to_s
|
1171
|
+
end
|
1172
|
+
|
1173
|
+
# check if this parent is allowed to have this child
|
1174
|
+
if not @node.can_have_child?(new_child)
|
1175
|
+
raise 'parent-cannot-have-this-child: ' + @node.to_s + ' / ' + new_child.to_s
|
1176
|
+
end
|
1177
|
+
|
1178
|
+
# dup_ok
|
1179
|
+
if new_child.is_a?(Trivet::Node)
|
1180
|
+
dup_ok = false
|
1181
|
+
else
|
1182
|
+
dup_ok = true
|
1183
|
+
end
|
1184
|
+
|
1185
|
+
# add to children if not already there
|
1186
|
+
if dup_ok or (not @children.include?(new_child))
|
1187
|
+
# add to start of array
|
1188
|
+
if index == 'first'
|
1189
|
+
@children.unshift new_child
|
1190
|
+
|
1191
|
+
# add to end of array
|
1192
|
+
elsif index == 'last'
|
1193
|
+
@children.push new_child
|
1194
|
+
|
1195
|
+
# insert at specific index
|
1196
|
+
elsif index.is_a?(Integer)
|
1197
|
+
# don't allow insert past end of array
|
1198
|
+
if index > @children.length
|
1199
|
+
raise 'add-child-cannot-insert-past-end'
|
1200
|
+
end
|
1201
|
+
|
1202
|
+
# insert
|
1203
|
+
@children.insert index, new_child
|
1204
|
+
|
1205
|
+
# else unknown index
|
1206
|
+
else
|
1207
|
+
puts 'add-child-invalid-index: ' + index.to_s
|
1208
|
+
end
|
1209
|
+
|
1210
|
+
# set node's parent if necessary
|
1211
|
+
if opts['recurse'] and new_child.is_a?(Trivet::Node)
|
1212
|
+
new_child.set_parent @node, 'recurse'=>false
|
1213
|
+
end
|
1214
|
+
end
|
1215
|
+
|
1216
|
+
# always return self
|
1217
|
+
return self
|
1218
|
+
end
|
1219
|
+
#
|
1220
|
+
# add_child
|
1221
|
+
#---------------------------------------------------------------------------
|
1222
|
+
end
|
1223
|
+
#
|
1224
|
+
# Trivet::Childset
|
1225
|
+
#===============================================================================
|
1226
|
+
|
1227
|
+
|
1228
|
+
#===============================================================================
|
1229
|
+
# Trivet::Document
|
1230
|
+
#
|
1231
|
+
|
1232
|
+
# A Trivet::Document object holds the root of a tree. It is not necessary to
|
1233
|
+
# have a Trivet::Document object to create a tree, but it can be handy to have
|
1234
|
+
# an object that holds the tree but is not part of the tree itself.
|
1235
|
+
class Trivet::Document
|
1236
|
+
# delegate
|
1237
|
+
extend Forwardable
|
1238
|
+
delegate %w(node_by_id to_tree) => :@root
|
1239
|
+
|
1240
|
+
|
1241
|
+
#---------------------------------------------------------------------------
|
1242
|
+
# initialize
|
1243
|
+
#
|
1244
|
+
|
1245
|
+
# Accepts an optional root node.
|
1246
|
+
def initialize(new_root=nil)
|
1247
|
+
# init
|
1248
|
+
@root = nil
|
1249
|
+
|
1250
|
+
# set new root
|
1251
|
+
if new_root
|
1252
|
+
if new_root.is_a?(Class)
|
1253
|
+
new_root = new_root.new()
|
1254
|
+
end
|
1255
|
+
|
1256
|
+
new_root.parent = self
|
1257
|
+
end
|
1258
|
+
end
|
1259
|
+
#
|
1260
|
+
# initialize
|
1261
|
+
#---------------------------------------------------------------------------
|
1262
|
+
|
1263
|
+
|
1264
|
+
#---------------------------------------------------------------------------
|
1265
|
+
# reader
|
1266
|
+
#
|
1267
|
+
|
1268
|
+
# Returns the root node. Returns nil if the document does not have a root.
|
1269
|
+
attr_reader :root
|
1270
|
+
|
1271
|
+
#
|
1272
|
+
# reader
|
1273
|
+
#---------------------------------------------------------------------------
|
1274
|
+
|
1275
|
+
|
1276
|
+
#---------------------------------------------------------------------------
|
1277
|
+
# root=
|
1278
|
+
#
|
1279
|
+
|
1280
|
+
# Sets a new root for the tree. This method is a shortcut for set_root().
|
1281
|
+
def root=(new_root)
|
1282
|
+
set_root new_root
|
1283
|
+
end
|
1284
|
+
#
|
1285
|
+
# root=
|
1286
|
+
#---------------------------------------------------------------------------
|
1287
|
+
|
1288
|
+
|
1289
|
+
#---------------------------------------------------------------------------
|
1290
|
+
# set_root
|
1291
|
+
#
|
1292
|
+
|
1293
|
+
# Sets the root node. The given object must be a Trivet::Node object or nil.
|
1294
|
+
def set_root(new_root, opts={})
|
1295
|
+
# $tm.hrm
|
1296
|
+
opts = {'recurse'=>true}.merge(opts)
|
1297
|
+
|
1298
|
+
# must be node
|
1299
|
+
unless new_root.is_a?(Trivet::Node) or new_root.nil?
|
1300
|
+
raise 'root-not-trivet-node-or-nil'
|
1301
|
+
end
|
1302
|
+
|
1303
|
+
# unlink old root
|
1304
|
+
if @root and opts['recurse']
|
1305
|
+
@root.unlink('recurse'=>false)
|
1306
|
+
end
|
1307
|
+
|
1308
|
+
# set root
|
1309
|
+
@root = new_root
|
1310
|
+
|
1311
|
+
# set parent
|
1312
|
+
if new_root and opts['recurse']
|
1313
|
+
new_root.parent = self
|
1314
|
+
end
|
1315
|
+
end
|
1316
|
+
#
|
1317
|
+
# set_root
|
1318
|
+
#---------------------------------------------------------------------------
|
1319
|
+
|
1320
|
+
|
1321
|
+
#---------------------------------------------------------------------------
|
1322
|
+
# traverse
|
1323
|
+
#
|
1324
|
+
|
1325
|
+
# Traverses the tree starting with the root node. See Trivet::Node#traverse
|
1326
|
+
# for details.
|
1327
|
+
def traverse(&block)
|
1328
|
+
# if no root
|
1329
|
+
if not @root
|
1330
|
+
raise 'cannot-traverse-without-root'
|
1331
|
+
end
|
1332
|
+
|
1333
|
+
# traverse
|
1334
|
+
@root.traverse 'self'=>true, &block
|
1335
|
+
end
|
1336
|
+
#
|
1337
|
+
# traverse
|
1338
|
+
#---------------------------------------------------------------------------
|
1339
|
+
|
1340
|
+
|
1341
|
+
#---------------------------------------------------------------------------
|
1342
|
+
# query
|
1343
|
+
#
|
1344
|
+
|
1345
|
+
# Runs a query starting with, and including, the root node. See
|
1346
|
+
# Trivet::Node#query for details.
|
1347
|
+
def query(qobj, opts={}, &block)
|
1348
|
+
opts = {'self'=>true}.merge(opts)
|
1349
|
+
return @root.query(qobj, opts, &block)
|
1350
|
+
end
|
1351
|
+
#
|
1352
|
+
# query
|
1353
|
+
#---------------------------------------------------------------------------
|
1354
|
+
|
1355
|
+
|
1356
|
+
# include method from querier
|
1357
|
+
include Trivet::Querier
|
1358
|
+
end
|
1359
|
+
#
|
1360
|
+
# Trivet::Document
|
1361
|
+
#===============================================================================
|
1362
|
+
|
1363
|
+
|
1364
|
+
#===============================================================================
|
1365
|
+
# Trivet::TraverseControl
|
1366
|
+
#
|
1367
|
+
|
1368
|
+
# Objects of this class control the Trivet::Node#traverse method. You generally
|
1369
|
+
# will not need to instantiate this object yourself. See
|
1370
|
+
# Trivet::Node#traverse for details.
|
1371
|
+
class Trivet::TraverseControl
|
1372
|
+
#---------------------------------------------------------------------------
|
1373
|
+
# initialize
|
1374
|
+
#
|
1375
|
+
def initialize
|
1376
|
+
@pruned = false
|
1377
|
+
@stopped = false
|
1378
|
+
end
|
1379
|
+
#
|
1380
|
+
# initialize
|
1381
|
+
#---------------------------------------------------------------------------
|
1382
|
+
|
1383
|
+
|
1384
|
+
#---------------------------------------------------------------------------
|
1385
|
+
# accessor, reader
|
1386
|
+
#
|
1387
|
+
|
1388
|
+
# Returns true if the traversal is being pruned.
|
1389
|
+
attr_accessor :pruned
|
1390
|
+
|
1391
|
+
# Returns true if the traversal has been stopped.
|
1392
|
+
attr_reader :stopped
|
1393
|
+
|
1394
|
+
#
|
1395
|
+
# accessor, reader
|
1396
|
+
#---------------------------------------------------------------------------
|
1397
|
+
|
1398
|
+
|
1399
|
+
#---------------------------------------------------------------------------
|
1400
|
+
# prune and stop
|
1401
|
+
#
|
1402
|
+
|
1403
|
+
# Prunes the traversal so that the process does not recurse into children.
|
1404
|
+
def prune
|
1405
|
+
@pruned = true
|
1406
|
+
end
|
1407
|
+
|
1408
|
+
# Stops the traversal completely.
|
1409
|
+
def stop
|
1410
|
+
@stopped = true
|
1411
|
+
end
|
1412
|
+
#
|
1413
|
+
# stop
|
1414
|
+
#---------------------------------------------------------------------------
|
1415
|
+
end
|
1416
|
+
#
|
1417
|
+
# Trivet::TraverseControl
|
1418
|
+
#===============================================================================
|