trivet 1.0

Sign up to get free protection for your applications and to get access to all the features.
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
+ #===============================================================================