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.
Files changed (4) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +79 -0
  3. data/lib/trivet.rb +1418 -0
  4. metadata +45 -0
@@ -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
@@ -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. |
@@ -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
+ #===============================================================================