Class: Trivet::Node
- Inherits:
-
Object
- Object
- Trivet::Node
- Extended by:
- Forwardable
- Includes:
- Querier
- Defined in:
- lib/trivet.rb
Overview
Objects of this class represent a single node in a hierarchy.
Constant Summary collapse
- INDEX_WITHIN =
Values for positioning a node within its parent. These values are used internally only.
%w{first last}
- INDEX_WITHOUT =
Values for positioning a node before or after a sibling. These values are used internally only.
%w{before after}
Instance Attribute Summary collapse
-
#children ⇒ Object
readonly
A Trivet::ChildSet object containing the children.
-
#id ⇒ Object
Returns the id of the node, or nil if it does not have an id.
-
#misc ⇒ Object
readonly
A hash of any miscellaneous information you want to attach to the node.
-
#parent ⇒ Object
Returns the parent object of the node, or nil if there is no parent.
Class Method Summary collapse
-
.no_children ⇒ Object
This method provides a concise way to override Trivet::Node#allow_child? so that it always returns false.
Instance Method Summary collapse
-
#allow_child?(child) ⇒ Boolean
This method is called when a node or other object is added to a node.
-
#ancestors ⇒ Object
Returns an array of the node’s ancestors.
-
#child_class(*opts) ⇒ Object
Returns the class to use for new nodes in Trivet::Node.node().
-
#depth ⇒ Object
Returns the depth of the node.
-
#document ⇒ Object
Returns the Trivet::Document object that holds the tree.
-
#heritage ⇒ Object
Returns an array of the node’s ancestors plus the node itself.
-
#index ⇒ Object
Returns the index of the node within the parent.
-
#initialize(pod = nil) ⇒ Node
constructor
Creates a new Trivet::Node object.
-
#match?(qobj) ⇒ Boolean
This method is called for each node in a query.
-
#node(opts = {}) ⇒ Object
Create a node and positions the new node either within the calling node, before it, after it, or replaces it.
-
#node_by_id(qid) ⇒ Object
Searches for a node by its id.
-
#query(qobj, opts = {}) ⇒ Object
Runs a query on the tree, yielding and returning nodes that match the given query.
-
#root ⇒ Object
Returns the root node of the tree.
-
#set_parent(new_parent, opts = {}) ⇒ Object
Sets a new parent for the node.
-
#to_s ⇒ Object
Returns the id if there is one.
-
#to_tree(opts = {}) ⇒ Object
This method is mainly for development and debugging.
-
#trace(new_node) ⇒ Object
Checks if a node is about to become nested within itself.
-
#traverse(opts = {}, &block) ⇒ Object
Traverses the tree starting with the children of the node.
-
#unlink(opts = {}) ⇒ Object
Removes the node from the tree.
-
#unwrap ⇒ Object
Moves the child nodes into the node’s parent and deletes self.
Methods included from Querier
Constructor Details
#initialize(pod = nil) ⇒ Node
Creates a new Trivet::Node object. The first param can be a parent node, the id of the new node, or nil.
83 84 85 86 87 88 89 90 91 92 93 94 95 |
# File 'lib/trivet.rb', line 83 def initialize(pod=nil) @id = nil @parent = nil @children = Trivet::Childset.new(self) @misc = {} # if parent object send if pod.is_a?(Trivet::Node) or pod.is_a?(Trivet::Document) self.parent = pod elsif pod.is_a?(String) self.id = pod end end |
Instance Attribute Details
#children ⇒ Object (readonly)
A Trivet::ChildSet object containing the children. This property can generally be treated like an array, but it has a few other features as well.
111 112 113 |
# File 'lib/trivet.rb', line 111 def children @children end |
#id ⇒ Object
Returns the id of the node, or nil if it does not have an id.
117 118 119 |
# File 'lib/trivet.rb', line 117 def id @id end |
#misc ⇒ Object (readonly)
A hash of any miscellaneous information you want to attach to the node.
114 115 116 |
# File 'lib/trivet.rb', line 114 def misc @misc end |
#parent ⇒ Object
Returns the parent object of the node, or nil if there is no parent.
106 107 108 |
# File 'lib/trivet.rb', line 106 def parent @parent end |
Class Method Details
.no_children ⇒ Object
This method provides a concise way to override Trivet::Node#allow_child? so that it always returns false. Just add this to your class:
self.no_children()
858 859 860 861 862 863 864 |
# File 'lib/trivet.rb', line 858 def self.no_children() self.child_levels = [] self.define_method('allow_child?') do return false end end |
Instance Method Details
#allow_child?(child) ⇒ Boolean
This method is called when a node or other object is added to a node. By default, always returns true. Override this method to create custom rules.
876 877 878 |
# File 'lib/trivet.rb', line 876 def allow_child?(child) return true end |
#ancestors ⇒ Object
Returns an array of the node’s ancestors.
591 592 593 594 595 596 597 |
# File 'lib/trivet.rb', line 591 def ancestors if @parent.is_a?(Trivet::Node) return @parent.heritage() else return [] end end |
#child_class(*opts) ⇒ Object
Returns the class to use for new nodes in Trivet::Node.node(). By default, this method returns the same class as the calling node. Override this method to create different rules for the class to use.
532 533 534 |
# File 'lib/trivet.rb', line 532 def child_class(*opts) return self.class end |
#depth ⇒ Object
Returns the depth of the node. The root note returns 0, its children return 1, etc.
927 928 929 |
# File 'lib/trivet.rb', line 927 def depth return ancestors.length end |
#document ⇒ Object
Returns the Trivet::Document object that holds the tree. Returns nil if there is no document.
890 891 892 893 |
# File 'lib/trivet.rb', line 890 def document() # $tm.hrm return root.parent end |
#heritage ⇒ Object
Returns an array of the node’s ancestors plus the node itself.
608 609 610 611 612 613 614 |
# File 'lib/trivet.rb', line 608 def heritage if @parent.is_a?(Trivet::Node) return @parent.heritage() + [self] else return [self] end end |
#index ⇒ Object
Returns the index of the node within the parent. Returns nil if the node has no parent.
391 392 393 394 395 396 397 |
# File 'lib/trivet.rb', line 391 def index if @parent return @parent.children.find_index(self) else return nil end end |
#match?(qobj) ⇒ Boolean
This method is called for each node in a query. If this method return true then the node is yielded/returned in the query. By default, this method always returns true. See Trivet::Node#query() for more details.
761 762 763 |
# File 'lib/trivet.rb', line 761 def match?(qobj) return true end |
#node(opts = {}) ⇒ Object
Create a node and positions the new node either within the calling node, before it, after it, or replaces it. By default, the new node is positioned as the last child node of the caller.
In its simplest use, node() creates a new node, yields it if given a block, and returns it. You can build structures by calling node() within node blocks. If a single string is given as a param, that string is used for the ‘id` for the new node.
food = Trivet::Node.new()
food.id = 'food'
food.node('spices') do |spices|
spices.node 'paprika'
spices.node('pepper') do |pepper|
pepper.node 'java'
pepper.node 'matico'
pepper.node 'cubeb'
end
end
option: index
The index option indicates where the new node should be positioned. This option can have one of the following values:
-
first: The new node becomes the first child of the caller object.
-
last: The new node becomes the last child of the caller object. This is the default behavior.
-
before: The new node is placed before the caller object.
-
after: The new node is placed after the caller object.
-
replace: The node node replaces the caller object and the caller is removed from the tree.
-
[integer]: The new node is placed at the index of the given integer.
298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 |
# File 'lib/trivet.rb', line 298 def node(opts={}) # normalize opts if opts.is_a?(String) opts = {'id'=>opts} end # $tm.hrm idx = opts['index'] || 'last' # create child object new_node = child_class(opts).new() # id if sent if opts['id'] new_node.id = opts['id'] end # add to this node if INDEX_WITHIN.include?(idx) or idx.is_a?(Integer) new_node.set_parent self, 'index'=>idx # add to parent elsif INDEX_WITHOUT.include?(idx) if @parent if idx == 'before' new_node.set_parent @parent, 'index'=>self.index elsif idx == 'after' new_node.set_parent @parent, 'index'=>self.index + 1 else raise 'unrecognized-without-index: ' + idx.to_s end else raise 'cannot-set-before-or-after-if-no-parent' end # replace elsif idx == 'replace' new_node.set_parent @parent, 'index'=>self.index @children.to_a.each do |child| child.parent = new_node end unlink() else raise 'node-unknown-index: ' + idx.to_s end # yield if necessary if block_given? yield new_node end # return return new_node end |
#node_by_id(qid) ⇒ Object
Searches for a node by its id.
827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 |
# File 'lib/trivet.rb', line 827 def node_by_id(qid) if @id == qid return self end # check children @children.each do |child| if child.is_a?(Trivet::Node) if node = child.node_by_id(qid) return node end end end # didn't find the node return nil end |
#query(qobj, opts = {}) ⇒ Object
Runs a query on the tree, yielding and returning nodes that match the given query. This method is really only useful if you subclass Trivet::Node and override Trivet::Node.match?(). By default, match? always returns true. The query param can be any kind of object you want it to be. That param will be passed to match? for each node. If match? returns true then the query yields/returns that node.
In these examples, we’ll assume a tree like this:
food
spices
paprika
pepper
java
matico
cubeb
fruit
red
cherry
apple
For example, you could override match? so that it returns true if a given string is within a node’s id.
class MyNode < Trivet::Node
def match?(qobj)
if @id
return @id.match(/#{qobj}/mu)
else
return false
end
end
end
You could then query for nodes that have ‘o’ in their id:
food = MyNode.new('food')
# ... add a bunch of child nodes
food.query('o') do |node|
puts node.id
end
That query returns one node, ‘matico’
option: self
By default, the query does not include the node itself, only its descendents. To include the node itself, use the ‘self’ option.
food.query('o', 'self'=>true) do |node|
puts node.id
end
That query will return ‘food’ and ‘matico’.
option: recurse
The recurse option indicates how far into the tree the query should recurse. The default is Trivet::ALWAYS, which means to recurse all the way down.
Trivet::UNTIL_MATCH means to not recurse into nodes that match the query. So if a node matches, its children are not included in the query.
Trivet::STOP_ON_FIRST means that the query is stopped completely after the first match is found. Using this option means that the query always returns either zero or one matches.
717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 |
# File 'lib/trivet.rb', line 717 def query(qobj, opts={}) ctl = opts['recurse'] || Trivet::ALWAYS rv = [] # traverse self.traverse(opts) do |node, recurse| if node.match?(qobj) # add to return array rv.push node if ctl == Trivet::UNTIL_MATCH recurse.prune elsif ctl == Trivet::STOP_ON_FIRST recurse.stop end end end # yield if block_given? rv.each do |node| yield node end end # return return rv end |
#root ⇒ Object
Returns the root node of the tree. Returns self if the node has no parent.
546 547 548 549 550 551 552 |
# File 'lib/trivet.rb', line 546 def root if @parent return @parent.root else return self end end |
#set_parent(new_parent, opts = {}) ⇒ Object
Sets a new parent for the node. The new parent can be either a Trivet::Node object or a Trivet::Document object.
For example, we’ll start with the standard tree we’ve been using:
food
spices
paprika
pepper
java
matico
cubeb
fruit
red
cherry
apple
Now we’ll set fruit’s parent to the pepper node:
fruit = food.node_by_id('fruit')
pepper = food.node_by_id('pepper')
fruit.set_parent pepper
That moves the fruit node and all its descendents into pepper:
food
spices
paprika
pepper
java
matico
cubeb
fruit
red
cherry
apple
option: index
The index option indicates which position within the parent the node should be moved to. This option has no meaning if the new parent is a Trivet::Document object.
Set index to ‘first’ to move it to the first position:
fruit = food.node_by_id('fruit')
pepper = food.node_by_id('pepper')
fruit.set_parent pepper, 'index'=>'first'
Set index to an integer to move the node to that index within the parent.
fruit = food.node_by_id('fruit')
pepper = food.node_by_id('pepper')
fruit.set_parent pepper, 'index'=>1
nil
If the parent param is nil then the object is unlinked from its parent.
200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 |
# File 'lib/trivet.rb', line 200 def set_parent(new_parent, opts={}) # $tm.hrm @id opts = {'recurse'=>true}.merge(opts) index = opts['index'] || 'last' # add to new_parent if new_parent # add to another node if new_parent.is_a?(Trivet::Node) new_parent.trace(self) unlink() @parent = new_parent # add to parent's children if opts['recurse'] if index == 'last' @parent.children.push self, 'recurse'=>false elsif index == 'first' @parent.children.unshift self, 'recurse'=>false elsif index.is_a?(Integer) @parent.children.insert index, self, 'recurse'=>false else raise 'set-parent-unknown-index: ' + index.to_s end end # new parent is a document elsif new_parent.is_a?(Trivet::Document) unlink() @parent = new_parent # set as document's root if opts['recurse'] new_parent.set_root self, 'recurse'=>false end # else raise exception else raise 'unknown-class-for-parent: ' + new_parent.class.to_s end # unlink node because it does not have a parent anymore. else unlink() end end |
#to_s ⇒ Object
Returns the id if there is one. Otherwise returns Object#to_s.
774 775 776 777 778 779 780 |
# File 'lib/trivet.rb', line 774 def to_s if @id return @id else return super() end end |
#to_tree(opts = {}) ⇒ Object
This method is mainly for development and debugging. Returns a string consisting of the node and all its descending arranged as an indented tree. Each node’s Trivet::Node#to_s method is used to display the node. Descendents that are not Trivet::Node objects are not displayed.
794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 |
# File 'lib/trivet.rb', line 794 def to_tree(opts={}) opts = {'depth'=>0}.merge(opts) rv = ' ' * opts['depth'] + self.to_s # indent if opts['indent'] rv = opts['indent'] + rv end # send_opts send_opts = opts.clone send_opts['depth'] += 1 # recurse @children.each do |child| if child.is_a?(Trivet::Node) rv += "\n" + child.to_tree(send_opts) end end # return return rv end |
#trace(new_node) ⇒ Object
Checks if a node is about to become nested within itself. This method is used by Trivet::Node.set_parent to prevent circular references. Generally you don’t need to call this method.
627 628 629 630 631 632 633 634 635 636 |
# File 'lib/trivet.rb', line 627 def trace(new_node) if new_node == self raise 'circular-reference' end # trace to parent if @parent and not(@parent.is_a?(Trivet::Document)) @parent.trace(new_node) end end |
#traverse(opts = {}, &block) ⇒ Object
Traverses the tree starting with the children of the node. Each node in the tree is yielded to the block if there is one. Returns and array of descendents.
food.traverse() do |node|
puts (' ' * node.depth) + node.id
end
That gives us output similar to that of the to_tree() method.
spices
paprika
pepper
java
matico
cubeb
fruit
red
cherry
apple
By default, the node on which you call this method itself is not traversed. You can include that node with the ‘self’ option:
food.traverse('self'=>true) do |node|
puts (' ' * node.depth) + node.id
end
The first parameter for the yield block is the node, as in the examples above. The second param is a Trivet::TraverseControl object which can be used to control the recursion.
Prune recursion
You can indicate that the traversal should not recurse into a node’s children with Trivet::TraverseControl.prune. For example, the following code doesn’t traverse into the spices node:
food.traverse('self'=>true) do |node, ctl|
puts (' ' * node.depth) + node.id
if node.id == 'spices'
ctl.prune
end
end
Giving us this output:
food
spices
fruit
red
cherry
apple
Stop recursion
You can stop the recursion by calling Trivet::TraverseControl.stop. For example, suppose you want to stop the traversal completely when you get to the “java” node. You could do that like this:
food.traverse('self'=>true) do |node, ctl|
puts (' ' * node.depth) + node.id
if node.id == 'java'
ctl.stop
end
end
That gives us output like this:
food
spices
paprika
pepper
java
482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 |
# File 'lib/trivet.rb', line 482 def traverse(opts={}, &block) ctrl = opts['ctrl'] || Trivet::TraverseControl.new() rv = [] # yield self if opts['self'] # yield if block_given? yield self, ctrl end # add to return array rv.push self # return if stopped ctrl.stopped and return rv end # if pruned, don't recurse into children, but continue to next sibling node if ctrl.pruned ctrl.pruned = false # recurse into children else # puts "--- #{self}" @children.to_a.each do |child| if child.is_a?(Trivet::Node) rv += child.traverse('self'=>true, 'ctrl'=>ctrl, &block) ctrl.stopped and return rv ctrl.pruned = false end end end # return return rv end |
#unlink(opts = {}) ⇒ Object
Removes the node from the tree.
563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 |
# File 'lib/trivet.rb', line 563 def unlink(opts={}) # $tm.hrm opts = {'recurse'=>true}.merge(opts) # remove from parent if @parent and opts['recurse'] if @parent.is_a?(Trivet::Document) @parent.set_root nil, 'recurse'=>false elsif @parent.is_a?(Trivet::Node) @parent.remove_child self else raise 'unlink-unknown-parent-class: ' + @parent.class.to_ end end # set parent to nil @parent = nil end |
#unwrap ⇒ Object
Moves the child nodes into the node’s parent and deletes self.
904 905 906 907 908 909 910 911 912 913 914 915 |
# File 'lib/trivet.rb', line 904 def unwrap() # $tm.hrm pchildren = @parent.children # move children @children.to_a.each do |child| pchildren.insert self.index, child end # unlink self unlink() end |