active_component 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,930 @@
1
+ # encoding: utf-8
2
+
3
+ module ActiveComponent
4
+ class Base
5
+ include ActiveComponent
6
+ include Haml::Helpers
7
+ include Enumerable
8
+
9
+ # Rails XSS protection support
10
+ include Haml::Helpers::XssMods
11
+ #require 'forwardable'
12
+ #extend ::Forwardable
13
+
14
+ attr_accessor :attributes, :title
15
+
16
+ # Initializes component by fetching arguments of a flexible method call as well as initializing the node and buffer
17
+ # *Example*
18
+ # def initialize(*args, &content_block)
19
+ # fetch_args(args, [:content, :title, :special_param, :attributes], &content_block)
20
+ #
21
+ # # Set defaults afterwards
22
+ # @attributes ||= {:class => @title}
23
+ # end
24
+ #
25
+ # Arguments may be non-hash objects with certain order.
26
+ # Then, the arguments will be set to instance variables with the var_names entry at the same index.
27
+ # Though, it is always possible use a hash for assigning parameters to keywords (e.g. :title => "Blumenkübel");
28
+ # As always, parenthesis can be omitted for this last hash.
29
+ #
30
+ # The list of variable names will be iterated in order.
31
+ # The first element becomes an instance variable that gets the block assigned (if passed along).
32
+ # If the list of variable names iteration is complete, remaining key-value pairs of the Hash part of the arguments list are merged into @attributes.
33
+ #
34
+ # Thus, all of the following signatures are legal for the **sender of fetch_args**:
35
+ # *Example 1*
36
+ # new("content", "title", :class => "api")
37
+ #
38
+ # *Example 2*
39
+ # new(:class => "api", :title => "title") { content }
40
+ #
41
+ # *Example 3*
42
+ # new("content", {:attributes => {:class => "api"}, :title => "title"})
43
+ #
44
+ # @param args [Array<Object>] Argument list where to fetch from
45
+ # @param var_names [Array<Symbol>] Ordered list of instance variables to fetch. First one gets assigned to block (if given).
46
+ # @param &content_block [#to_proc] The given block; will be assigned to variable named first in +var_names+.
47
+ def init_component(args, var_names = [:content, :title, :attributes], &content_block)
48
+
49
+ init_node
50
+ init_buffer
51
+
52
+ # Fetch arguments
53
+ non_hash_args = []
54
+ args_hash = {}
55
+ # Collect all non-hash args and merge all hashs together
56
+ for arg in args
57
+ arg.is_a?(Hash) ? args_hash.merge!(arg) : non_hash_args << arg
58
+ end
59
+
60
+ # var_names.first is set to block if block given
61
+ send(var_names.shift.to_s + "=", content_block.call) if content_block
62
+
63
+ for var_name in var_names
64
+ # Each value is extracted from args_hash, if resp. var_name present, otherwise the next non-hash argument is taken
65
+ send var_name.to_s + "=", (args_hash.delete(var_name) or non_hash_args.shift)
66
+ end
67
+
68
+ @attributes ||= {}
69
+ # All args in args_hash that have not been used for setting an instance variable become attributes.
70
+ @attributes.set_defaults!(args_hash)
71
+ # The class attribute will contain the component title and class_name (unless component is a html tag wrapper)
72
+ @attributes[:class] = (html_class + [@attributes[:class]].flatten).compact.uniq
73
+ end
74
+
75
+ def content=(cont)
76
+ @content = cont
77
+ # Add content as a child if it is also a component
78
+ cont.transmogrify do |c|
79
+ self << c if c.is_a? ActiveComponent
80
+ end
81
+ end
82
+
83
+ def content
84
+ # If content is not given yet, return node children
85
+ @content || children
86
+ end
87
+
88
+ def html_class
89
+ class_arr = []
90
+ class_arr << @title.hyphenize unless @title.blank?
91
+ class_arr << class_name unless is_html_tag_wrapper?
92
+ class_arr.uniq
93
+ end
94
+
95
+ def class_name
96
+ self.class.to_s.hyphenize
97
+ end
98
+
99
+ def to_html
100
+ raise NotImplementedError, "to_html has to be implemented for every component that inherits from ActiveComponent::Base"
101
+ end
102
+
103
+ def to_s
104
+ to_html
105
+ end
106
+
107
+ def is_html_tag_wrapper?
108
+ ActiveComponent::HTML5_ELEMENTS.each_value {|category| break true if category.include?(class_name.to_sym)} == true
109
+ end
110
+
111
+ def self.inherited(component_class)
112
+ def_component_helper(component_class) unless component_class.to_s =~ /#/
113
+ end
114
+
115
+ def self.def_component_helper(component_class)
116
+ raise ArgumentError, "Anonymous classes are not allowed because a name is needed." if component_class.to_s =~ /#/
117
+
118
+ ActiveComponent.class_eval do
119
+ # New way of defining methods with a block parameter (1.9 only)
120
+ # Attention: evaluation context seems to differ!
121
+ #define_method(component_class.to_s.underscore) do |*args, &block|
122
+ #component_class.new(*args, &block)
123
+ #end
124
+
125
+ # Old way of defining methods with a block parameter (supported by 1.8)
126
+ eval %(
127
+ def #{component_class.to_s.underscore}(*args, &block)
128
+ #{component_class}.new(*args, &block)
129
+ end
130
+ )
131
+ end
132
+ end
133
+
134
+ # This helper creates HTML wrapper components that become sub classes of the given super_class (e.g. Section)
135
+ def self.def_html_sub_components(names, super_class)
136
+ for name in names
137
+ # Creating an anonymous subclass and set according constant
138
+ new_component = Object.const_set(name.to_s.camelize, Class.new(super_class))
139
+ # Register component instantiation helper manually with the class constant
140
+ def_component_helper(new_component)
141
+
142
+ new_component.class_eval do
143
+ # FIXME: Remove eval wrap as soon as Ruby 1.9.2 support can be dropped
144
+ # Problem: "super from singleton method that is defined to multiple classes is not supported; this will be fixed in 1.9.3 or later"
145
+ # See https://gist.github.com/455547
146
+ eval %(
147
+ def initialize(*args, &block)
148
+ args << {:tag_type => self.class.to_s.underscore.to_sym}
149
+ super *args, &block
150
+ end
151
+ )
152
+ end
153
+ end
154
+ end
155
+
156
+ #----------------------------------------
157
+ # NODE METHODS COPIED FROM Tree::TreeNode
158
+ # An own, delegatable Tree Library has to
159
+ # be written. Until then, the methods are
160
+ # contained here as they make heavy use
161
+ # of self.
162
+ #----------------------------------------
163
+
164
+ # Overridden / own methods
165
+ #--------------------
166
+ # Adds the specified child node to the receiver node.
167
+ #
168
+ # This method can also be used for *grafting* a subtree into the receiver node's tree, if the specified child node
169
+ # is the root of a subtree (i.e., has child nodes under it).
170
+ #
171
+ # The receiver node becomes parent of the node passed in as the argument, and
172
+ # the child is added as the last child ("right most") in the current set of
173
+ # children of the receiver node.
174
+ #
175
+ # @param [Tree::TreeNode] child The child node to add.
176
+ #
177
+ # @return [Tree::TreeNode] The added child node.
178
+ #
179
+ # @raise [RuntimeError] This exception is raised if another child node with the same
180
+ # node_name exists.
181
+ # @raise [ArgumentError] This exception is raised if a +nil+ node is passed as the argument.
182
+ #
183
+ # @see #<<
184
+ def add(child, prepend = false)
185
+ raise ArgumentError, "Attempting to add a nil node" unless child
186
+ raise "Child #{child.node_name} already added!" if @childrenHash.has_key?(child.node_name)
187
+
188
+ @childrenHash[child.node_name] = child
189
+ prepend ? @children.unshift(child) : (@children << child)
190
+ raise "Great Scott! I just added a ghost child!" if !(@children.include?(child)) || @children.empty?
191
+ child.parent = self
192
+ child
193
+ end
194
+
195
+ def prepend(child)
196
+ add(child, true)
197
+ end
198
+
199
+ # Original Methods
200
+ #--------------------
201
+
202
+ # node_name of this node. Expected to be unique within the tree.
203
+ attr_accessor :node_name
204
+
205
+ # node_content of this node. Can be +nil+.
206
+ attr_accessor :node_content
207
+
208
+ # TODO: was not necessary to provide in Tree::TreeNode. Why here?
209
+ attr_accessor :childrenHash
210
+
211
+ # Parent of this node. Will be +nil+ for a root node.
212
+ attr_accessor :parent
213
+
214
+
215
+ # Creates a new node with a node_name and optional node_content.
216
+ # The node node_name is expected to be unique within the tree.
217
+ #
218
+ # The node_content can be of any type, and defaults to +nil+.
219
+ #
220
+ # @param [Object] node_name node_name of the node. Usual usage is to pass a String.
221
+ # @param [Object] node_content node_content of the node.
222
+ #
223
+ # @raise [ArgumentError] Raised if the node node_name is empty.
224
+ def init_node(node_name = object_id, node_content = nil)
225
+ raise ArgumentError, "Node node_name HAS to be provided!" if node_name == nil
226
+ @node_name, @node_content = node_name, node_content
227
+
228
+ self.setAsRoot!
229
+ @childrenHash = Hash.new
230
+ @children = []
231
+ end
232
+
233
+ # Returns a copy of the receiver node, with its parent and children links removed.
234
+ # The original node remains attached to its tree.
235
+ #
236
+ # @return [Tree::TreeNode] A copy of the receiver node.
237
+ def detached_copy
238
+ Tree::TreeNode.new(@node_name, @node_content ? @node_content.clone : nil)
239
+ end
240
+
241
+
242
+ # Returns an array of ancestors of the receiver node in reversed order
243
+ # (the first element is the immediate parent of the receiver).
244
+ #
245
+ # Returns +nil+ if the receiver is a root node.
246
+ #
247
+ # @return [Array, nil] An array of ancestors of the receiver node, or +nil+ if this is a root node.
248
+ def parentage
249
+ return nil if isRoot?
250
+
251
+ parentageArray = []
252
+ prevParent = self.parent
253
+ while (prevParent)
254
+ parentageArray << prevParent
255
+ prevParent = prevParent.parent
256
+ end
257
+
258
+ parentageArray
259
+ end
260
+
261
+ # Protected method to set the parent node for the receiver node.
262
+ # This method should *NOT* be invoked by client code.
263
+ #
264
+ # @param [Tree::TreeNode] parent The parent node.
265
+ #
266
+ # @return [Tree::TreeNode] The parent node.
267
+ def parent=(parent) # :nodoc:
268
+ @parent = parent
269
+ end
270
+
271
+ # Convenience synonym for {Tree::TreeNode#add} method.
272
+ #
273
+ # This method allows an easy mechanism to add node hierarchies to the tree
274
+ # on a given path via chaining the method calls to successive child nodes.
275
+ #
276
+ # @example Add a child and grand-child to the root
277
+ # root << child << grand_child
278
+ #
279
+ # @param [Tree::TreeNode] child the child node to add.
280
+ #
281
+ # @return [Tree::TreeNode] The added child node.
282
+ #
283
+ # @see Tree::TreeNode#add
284
+ def <<(child)
285
+ add(child)
286
+ end
287
+
288
+ # Adds the specified child node to the receiver node.
289
+ #
290
+ # This method can also be used for *grafting* a subtree into the receiver node's tree, if the specified child node
291
+ # is the root of a subtree (i.e., has child nodes under it).
292
+ #
293
+ # The receiver node becomes parent of the node passed in as the argument, and
294
+ # the child is added as the last child ("right most") in the current set of
295
+ # children of the receiver node.
296
+ #
297
+ # @param [Tree::TreeNode] child The child node to add.
298
+ #
299
+ # @return [Tree::TreeNode] The added child node.
300
+ #
301
+ # @raise [RuntimeError] This exception is raised if another child node with the same
302
+ # node_name exists.
303
+ # @raise [ArgumentError] This exception is raised if a +nil+ node is passed as the argument.
304
+ #
305
+ # @see #<<
306
+ # def add(child)
307
+ # raise ArgumentError, "Attempting to add a nil node" unless child
308
+ # raise "Child #{child.node_name} already added!" if @childrenHash.has_key?(child.node_name)
309
+ #
310
+ # @childrenHash[child.node_name] = child
311
+ # @children << child
312
+ # child.parent = self
313
+ # return child
314
+ # end
315
+
316
+ # Removes the specified child node from the receiver node.
317
+ #
318
+ # This method can also be used for *pruning* a sub-tree, in cases where the removed child node is
319
+ # the root of the sub-tree to be pruned.
320
+ #
321
+ # The removed child node is orphaned but accessible if an alternate reference exists. If accessible via
322
+ # an alternate reference, the removed child will report itself as a root node for its sub-tree.
323
+ #
324
+ # @param [Tree::TreeNode] child The child node to remove.
325
+ #
326
+ # @return [Tree::TreeNode] The removed child node, or +nil+ if a +nil+ was passed in as argument.
327
+ #
328
+ # @see #removeFromParent!
329
+ # @see #removeAll!
330
+ def remove!(child)
331
+ return nil unless child
332
+
333
+ @childrenHash.delete(child.node_name)
334
+ @children.delete(child)
335
+ child.setAsRoot!
336
+ child
337
+ end
338
+
339
+ # Removes the receiver node from its parent. The reciever node becomes the new root for its subtree.
340
+ #
341
+ # If this is the root node, then does nothing.
342
+ #
343
+ # @return [Tree:TreeNode] +self+ (the removed receiver node) if the operation is successful, +nil+ otherwise.
344
+ #
345
+ # @see #removeAll!
346
+ def removeFromParent!
347
+ @parent.remove!(self) unless isRoot?
348
+ end
349
+
350
+ # Removes all children from the receiver node. If an indepedent reference exists to the child
351
+ # nodes, then these child nodes report themselves as roots after this operation.
352
+ #
353
+ # @return [Tree::TreeNode] The receiver node (+self+)
354
+ #
355
+ # @see #remove!
356
+ # @see #removeFromParent!
357
+ def removeAll!
358
+ for child in @children
359
+ child.setAsRoot!
360
+ end
361
+ @childrenHash.clear
362
+ @children.clear
363
+ self
364
+ end
365
+
366
+ # Returns +true+ if the receiver node has node_content.
367
+ #
368
+ # @return [Boolean] +true+ if the node has node_content.
369
+ def hasnode_content?
370
+ @node_content != nil
371
+ end
372
+
373
+ # Protected method which sets the receiver node as a root node.
374
+ #
375
+ # @return +nil+.
376
+ def setAsRoot! # :nodoc:
377
+ @parent = nil
378
+ end
379
+
380
+ # Returns +true+ if the receiver is a root node. Note that
381
+ # orphaned children will also be reported as root nodes.
382
+ #
383
+ # @return [Boolean] +true+ if this is a root node.
384
+ def is_root?
385
+ @parent.nil?
386
+ end
387
+
388
+ alias :isRoot? :is_root?
389
+
390
+ # Returns +true+ if the receiver node has any child node.
391
+ #
392
+ # @return [Boolean] +true+ if child nodes exist.
393
+ #
394
+ # @see #isLeaf?
395
+ def hasChildren?
396
+ @children.length != 0
397
+ end
398
+
399
+ # Returns +true+ if the receiver node is a 'leaf' - i.e., one without
400
+ # any children.
401
+ #
402
+ # @return [Boolean] +true+ if this is a leaf node.
403
+ #
404
+ # @see #hasChildren?
405
+ def isLeaf?
406
+ !hasChildren?
407
+ end
408
+
409
+ # Returns an array of all the immediate children of the receiver node. The child nodes are ordered
410
+ # "left-to-right" in the returned array.
411
+ #
412
+ # If a block is given, yields each child node to the block traversing from left to right.
413
+ #
414
+ # @yield [child] Each child is passed to the block, if given
415
+ # @yieldparam [Tree::TreeNode] child Each child node.
416
+ #
417
+ # @return [Array<Tree::TreeNode>] An array of the child nodes, if no block is given.
418
+ def children
419
+ if block_given?
420
+ @children.each {|child| yield child}
421
+ else
422
+ @children
423
+ end
424
+ end
425
+
426
+ # Returns the first child of the receiver node.
427
+ #
428
+ # Will return +nil+ if no children are present.
429
+ #
430
+ # @return [Tree::TreeNode] The first child, or +nil+ if none is present.
431
+ def firstChild
432
+ children.first
433
+ end
434
+
435
+ # Returns the last child of the receiver node.
436
+ #
437
+ # Will return +nil+ if no children are present.
438
+ #
439
+ # @return [Tree::TreeNode] The last child, or +nil+ if none is present.
440
+ def lastChild
441
+ children.last
442
+ end
443
+
444
+ # Traverses each node (including the receiver node) of the (sub)tree rooted at this node
445
+ # by yielding the nodes to the specified block.
446
+ #
447
+ # The traversal is *depth-first* and from *left-to-right* in pre-ordered sequence.
448
+ #
449
+ # @yield [child] Each node is passed to the block.
450
+ # @yieldparam [Tree::TreeNode] child Each node.
451
+ #
452
+ # @see #preordered_each
453
+ # @see #breadth_each
454
+ def each(&block) # :yields: node
455
+ yield self
456
+ children { |child| child.each(&block) }
457
+ end
458
+
459
+ # Traverses the (sub)tree rooted at the receiver node in pre-ordered sequence.
460
+ # This is a synonym of {Tree::TreeNode#each}.
461
+ #
462
+ # @yield [child] Each child is passed to the block.
463
+ # @yieldparam [Tree::TreeNode] node Each node.
464
+ #
465
+ # @see #each
466
+ # @see #breadth_each
467
+ def preordered_each(&block) # :yields: node
468
+ each(&block)
469
+ end
470
+
471
+ # Performs breadth-first traversal of the (sub)tree rooted at the receiver node. The
472
+ # traversal at a given level is from *left-to-right*. The receiver node itself is the first
473
+ # node to be traversed.
474
+ #
475
+ # @yield [child] Each node is passed to the block.
476
+ # @yieldparam [Tree::TreeNode] node Each node.
477
+ #
478
+ # @see #preordered_each
479
+ # @see #breadth_each
480
+ def breadth_each(&block)
481
+ node_queue = [self] # Create a queue with self as the initial entry
482
+
483
+ # Use a queue to do breadth traversal
484
+ until node_queue.empty?
485
+ node_to_traverse = node_queue.shift
486
+ yield node_to_traverse
487
+ # Enqueue the children from left to right.
488
+ node_to_traverse.children { |child| node_queue.push child }
489
+ end
490
+ end
491
+
492
+ # Yields every leaf node of the (sub)tree rooted at the receiver node to the specified block.
493
+ #
494
+ # May yield this node as well if this is a leaf node.
495
+ # Leaf traversal is *depth-first* and *left-to-right*.
496
+ #
497
+ # @yield [node] Each leaf node is passed to the block.
498
+ # @yieldparam [Tree::TreeNode] node Each leaf node.
499
+ #
500
+ # @see #each
501
+ # @see #breadth_each
502
+ def each_leaf &block
503
+ self.each { |node| yield(node) if node.isLeaf? }
504
+ end
505
+
506
+ # Returns the requested node from the set of immediate children.
507
+ #
508
+ # If the argument is _numeric_, then the in-sequence array of children is accessed using
509
+ # the argument as the *index* (zero-based).
510
+ #
511
+ # If the argument is *NOT* _numeric_, then it is taken to be the *node_name* of the child node to be returned.
512
+ #
513
+ # An ArgumentError exception is raised if neither node_name nor an index is provided.
514
+ #
515
+ # @param [String|Number] node_name_or_index node_name of the child, or its positional index in the array of child nodes.
516
+ #
517
+ # @return [Tree::TreeNode] the requested child node. If the index in not in range, or the node_name is not
518
+ # present, then a +nil+ is returned.
519
+ #
520
+ # @raise [ArgumentError] Raised if neither node_name nor index is provided.
521
+ #
522
+ # @see #add
523
+ def [](node_name_or_index)
524
+ raise ArgumentError, "node_name_or_index needs to be provided!" if node_name_or_index == nil
525
+
526
+ if node_name_or_index.kind_of?(Integer)
527
+ @children[node_name_or_index] || @childrenHash[node_name_or_index]
528
+ else
529
+ @childrenHash[node_name_or_index]
530
+ end
531
+ end
532
+
533
+ # Returns the total number of nodes in this (sub)tree, including the receiver node.
534
+ #
535
+ # Size of the tree is defined as:
536
+ #
537
+ # Size:: Total number nodes in the subtree including the receiver node.
538
+ #
539
+ # @return [Number] Total number of nodes in this (sub)tree.
540
+ def size
541
+ @children.inject(1) {|sum, node| sum + node.size}
542
+ end
543
+
544
+ # Convenience synonym for {Tree::TreeNode#size}.
545
+ #
546
+ # @todo The semantic of length is probably unclear. Should return the node depth instead
547
+ # to reflect the path length.
548
+ #
549
+ # @deprecated This method node_name is ambiguous and may be removed. Use TreeNode#size instead.
550
+ #
551
+ # @return [Number] The total number of nodes in this (sub)tree.
552
+ # @see #size
553
+ def length
554
+ size()
555
+ end
556
+
557
+ # Pretty prints the (sub)tree rooted at the receiver node.
558
+ #
559
+ # @param [Number] level The indentation level (4 spaces) to start with.
560
+ def printTree(level = 0)
561
+
562
+ if isRoot?
563
+ print "*"
564
+ else
565
+ print "|" unless parent.isLastSibling?
566
+ print(' ' * (level - 1) * 4)
567
+ print(isLastSibling? ? "+" : "|")
568
+ print "---"
569
+ print(hasChildren? ? "+" : ">")
570
+ end
571
+
572
+ puts " #{node_name}"
573
+
574
+ children { |child| child.printTree(level + 1)}
575
+ end
576
+
577
+ # Returns root node for the (sub)tree to which the receiver node belongs.
578
+ #
579
+ # Note that a root node's root is itself (*beware* of any loop construct that may become infinite!)
580
+ #
581
+ # @todo We should perhaps return nil as root's root.
582
+ #
583
+ # @return [Tree::TreeNode] Root of the (sub)tree.
584
+ def root
585
+ root = self
586
+ root = root.parent while !root.isRoot?
587
+ root
588
+ end
589
+
590
+ # Returns the first sibling of the receiver node. If this is the root node, then returns
591
+ # itself.
592
+ #
593
+ # 'First' sibling is defined as follows:
594
+ # First sibling:: The left-most child of the receiver's parent, which may be the receiver itself
595
+ #
596
+ # @todo Fix the inconsistency of returning root as its first sibling, and returning
597
+ # a +nil+ array for siblings of the node.
598
+ #
599
+ # @return [Tree::TreeNode] The first sibling node.
600
+ #
601
+ # @see #isFirstSibling?
602
+ # @see #lastSibling
603
+ def firstSibling
604
+ isRoot? ? self : parent.children.first
605
+ end
606
+
607
+ # Returns +true+ if the receiver node is the first sibling at its level.
608
+ #
609
+ # @return [Boolean] +true+ if this is the first sibling.
610
+ #
611
+ # @see #isLastSibling?
612
+ # @see #firstSibling
613
+ def isFirstSibling?
614
+ firstSibling == self
615
+ end
616
+
617
+ # Returns the last sibling of the receiver node. If this is the root node, then returns
618
+ # itself.
619
+ #
620
+ # 'Last' sibling is defined as follows:
621
+ # Last sibling:: The right-most child of the receiver's parent, which may be the receiver itself
622
+ #
623
+ # @todo Fix the inconsistency of returning root as its last sibling, and returning
624
+ # a +nil+ array for siblings of the node.
625
+ #
626
+ # @return [Tree::TreeNode] The last sibling node.
627
+ #
628
+ # @see #isLastSibling?
629
+ # @see #firstSibling
630
+ def lastSibling
631
+ isRoot? ? self : parent.children.last
632
+ end
633
+
634
+ # Returns +true+ if the receiver node is the last sibling at its level.
635
+ #
636
+ # @return [Boolean] +true+ if this is the last sibling.
637
+ #
638
+ # @see #isFirstSibling?
639
+ # @see #lastSibling
640
+ def isLastSibling?
641
+ lastSibling == self
642
+ end
643
+
644
+ # Returns an array of siblings for the receiver node. The receiver node is excluded.
645
+ #
646
+ # If a block is provided, yields each of the sibling nodes to the block.
647
+ # The root always has +nil+ siblings.
648
+ #
649
+ # @todo Fix the inconsistency of returning root as its own first/last sibling, and returning
650
+ # a +nil+ array for siblings of the same root node.
651
+ # @todo Also fix the inconsistency of returning +nil+ for a root node, and an empty array for nodes
652
+ # which have no siblings.
653
+ #
654
+ # @yield [sibling] Each sibling is passed to the block.
655
+ # @yieldparam [Tree::TreeNode] sibling Each sibling node.
656
+ #
657
+ # @return [Array<Tree::TreeNode>] Array of siblings of this node.
658
+ #
659
+ # @see #firstSibling
660
+ # @see #lastSibling
661
+ def siblings
662
+ return nil if is_root?
663
+
664
+ if block_given?
665
+ for sibling in parent.children
666
+ yield sibling if sibling != self
667
+ end
668
+ else
669
+ siblings = []
670
+ parent.children {|my_sibling| siblings << my_sibling if my_sibling != self}
671
+ siblings
672
+ end
673
+ end
674
+
675
+ # Returns +true+ if the receiver node is the only child of its parent.
676
+ #
677
+ # As a special case, a root node will always return +true+.
678
+ #
679
+ # @return [Boolean] +true+ if this is the only child of its parent.
680
+ #
681
+ # @see #siblings
682
+ def isOnlyChild?
683
+ isRoot? ? true : parent.children.size == 1
684
+ end
685
+
686
+ # Returns the next sibling for the receiver node.
687
+ # The 'next' node is defined as the node to right of the receiver node.
688
+ #
689
+ # Will return +nil+ if no subsequent node is present, or if the receiver is a root node.
690
+ #
691
+ # @return [Tree::treeNode] the next sibling node, if present.
692
+ #
693
+ # @see #previousSibling
694
+ # @see #siblings
695
+ def nextSibling
696
+ return nil if isRoot?
697
+ if myidx = parent.children.index(self)
698
+ parent.children.at(myidx + 1)
699
+ end
700
+ end
701
+
702
+ # Returns the previous sibling of the receiver node.
703
+ # 'Previous' node is defined to be the node to left of the receiver node.
704
+ #
705
+ # Will return +nil+ if no predecessor node is present, or if the receiver is a root node.
706
+ #
707
+ # @return [Tree::treeNode] the previous sibling node, if present.
708
+ #
709
+ # @see #nextSibling
710
+ # @see #siblings
711
+ def previousSibling
712
+ return nil if isRoot?
713
+ if myidx = parent.children.index(self)
714
+ parent.children.at(myidx - 1) if myidx > 0
715
+ end
716
+ end
717
+
718
+ # Provides a comparision operation for the nodes.
719
+ #
720
+ # Comparision is based on the natural character-set ordering of the node node_name.
721
+ #
722
+ # @param [Tree::TreeNode] other The other node to compare against.
723
+ #
724
+ # @return [Number] +1 if this node is a 'successor', 0 if equal and -1 if this node is a 'predecessor'.
725
+ def <=>(other)
726
+ return +1 if other == nil
727
+ self.node_name <=> other.node_name
728
+ end
729
+
730
+ # Freezes all nodes in the (sub)tree rooted at the receiver node.
731
+ #
732
+ # The nodes become immutable after this operation. In effect, the entire tree's
733
+ # structure and node_contents become _read-only_ and cannot be changed.
734
+ def freezeTree!
735
+ each {|node| node.freeze}
736
+ end
737
+
738
+ # Returns a marshal-dump represention of the (sub)tree rooted at the receiver node.
739
+ def marshal_dump
740
+ self.collect { |node| node.createDumpRep }
741
+ end
742
+
743
+ # Creates a dump representation of the reciever node and returns the same as a hash.
744
+ def createDumpRep # :nodoc:
745
+ { :node_name => @node_name, :parent => (isRoot? ? nil : @parent.node_name), :node_content => Marshal.dump(@node_content)}
746
+ end
747
+
748
+ # Loads a marshalled dump of a tree and returns the root node of the
749
+ # reconstructed tree. See the Marshal class for additional details.
750
+ #
751
+ #
752
+ # @todo This method probably should be a class method. It currently clobbers self
753
+ # and makes itself the root.
754
+ #
755
+ def marshal_load(dumped_tree_array)
756
+ nodes = { }
757
+ for node_hash in dumped_tree_array do
758
+ node_name = node_hash[:node_name]
759
+ parent_node_name = node_hash[:parent]
760
+ node_content = Marshal.load(node_hash[:node_content])
761
+
762
+ if parent_node_name then
763
+ nodes[node_name] = current_node = Tree::TreeNode.new(node_name, node_content)
764
+ nodes[parent_node_name].add current_node
765
+ else
766
+ # This is the root node, hence initialize self.
767
+ initialize(node_name, node_content)
768
+
769
+ nodes[node_name] = self # Add self to the list of nodes
770
+ end
771
+ end
772
+ end
773
+
774
+ # Creates a JSON representation of this node including all it's children. This requires the JSON gem to be
775
+ # available, or else the operation fails with a warning message.
776
+ #
777
+ # @author Dirk Breuer (http://github.com/railsbros-dirk)
778
+ # @since 0.7.0
779
+ #
780
+ # @return The JSON representation of this subtree.
781
+ #
782
+ # @see Tree::TreeNode.json_create
783
+ # @see http://flori.github.com/json
784
+ def to_json(*a)
785
+ begin
786
+ require 'json'
787
+
788
+ json_hash = {
789
+ "node_name" => node_name,
790
+ "node_content" => node_content,
791
+ JSON.create_id => self.class.node_name
792
+ }
793
+
794
+ if hasChildren?
795
+ json_hash["children"] = children
796
+ end
797
+
798
+ return json_hash.to_json
799
+
800
+ rescue LoadError => e
801
+ warn "The JSON gem couldn't be loaded. Due to this we cannot serialize the tree to a JSON representation"
802
+ end
803
+ end
804
+
805
+ # Creates a Tree::TreeNode object instance from a given JSON Hash representation. This requires the JSON gem to be
806
+ # available, or else the operation fails with a warning message.
807
+ #
808
+ # @author Dirk Breuer (http://github.com/railsbros-dirk)
809
+ # @since 0.7.0
810
+ #
811
+ # @param [Hash] json_hash The JSON hash to convert from.
812
+ #
813
+ # @return [Tree::TreeNode] The created tree.
814
+ #
815
+ # @see #to_json
816
+ # @see http://flori.github.com/json
817
+ def self.json_create(json_hash)
818
+ begin
819
+ require 'json'
820
+
821
+ node = new(json_hash["node_name"], json_hash["node_content"])
822
+
823
+ json_hash["children"].each do |child|
824
+ node << child
825
+ end if json_hash["children"]
826
+
827
+ return node
828
+ rescue LoadError => e
829
+ warn "The JSON gem couldn't be loaded. Due to this we cannot serialize the tree to a JSON representation."
830
+ end
831
+ end
832
+
833
+ # Returns height of the (sub)tree from the receiver node. Height of a node is defined as:
834
+ #
835
+ # Height:: Length of the longest downward path to a leaf from the node.
836
+ #
837
+ # - Height from a root node is height of the entire tree.
838
+ # - The height of a leaf node is zero.
839
+ #
840
+ # @return [Number] Height of the node.
841
+ def nodeHeight
842
+ return 0 if isLeaf?
843
+ 1 + @children.collect { |child| child.nodeHeight }.max
844
+ end
845
+
846
+ # Returns depth of the receiver node in its tree. Depth of a node is defined as:
847
+ #
848
+ # Depth:: Length of the node's path to its root. Depth of a root node is zero.
849
+ #
850
+ # 'level' is an alias for this method.
851
+ #
852
+ # @return [Number] Depth of this node.
853
+ def nodeDepth
854
+ return 0 if isRoot?
855
+ 1 + parent.nodeDepth
856
+ end
857
+
858
+ alias level nodeDepth # Aliased level() method to the nodeDepth().
859
+
860
+ # Returns depth of the tree from the receiver node. A single leaf node has a depth of 1.
861
+ #
862
+ # This method is *DEPRECATED* and may be removed in the subsequent releases.
863
+ # Note that the value returned by this method is actually the:
864
+ #
865
+ # _height_ + 1 of the node, *NOT* the _depth_.
866
+ #
867
+ # For correct and conventional behavior, please use {Tree::TreeNode#nodeDepth} and
868
+ # {Tree::TreeNode#nodeHeight} methods instead.
869
+ #
870
+ # @return [Number] depth of the node.
871
+ # @deprecated This method returns an incorrect value. Use the 'nodeDepth' method instead.
872
+ #
873
+ # @see #nodeDepth
874
+ def depth
875
+ begin
876
+ require 'structured_warnings' # To enable a nice way of deprecating of the depth method.
877
+ warn DeprecatedMethodWarning, 'This method is deprecated. Please use nodeDepth() or nodeHeight() instead (bug # 22535)'
878
+ rescue LoadError
879
+ # Oh well. Will use the standard Kernel#warn. Behavior will be identical.
880
+ warn 'Tree::TreeNode#depth() method is deprecated. Please use nodeDepth() or nodeHeight() instead (bug # 22535)'
881
+ end
882
+
883
+ return 1 if isLeaf?
884
+ 1 + @children.collect { |child| child.depth }.max
885
+ end
886
+
887
+ # Returns breadth of the tree at the receiver node's level.
888
+ # A single node without siblings has a breadth of 1.
889
+ #
890
+ # Breadth is defined to be:
891
+ # Breadth:: Number of sibling nodes to this node + 1 (this node itself),
892
+ # i.e., the number of children the parent of this node has.
893
+ #
894
+ # @return [Number] breadth of the node's level.
895
+ def breadth
896
+ isRoot? ? 1 : parent.children.size
897
+ end
898
+
899
+ # Returns the incoming edge-count of the receiver node.
900
+ #
901
+ # In-degree is defined as:
902
+ # In-degree:: The number of edges arriving at the node (0 for root, 1 for all other nodes)
903
+ #
904
+ # - In-degree = 0 for a root or orphaned node
905
+ # - In-degree = 1 for a node which has a parent
906
+ #
907
+ # @return [Number] The in-degree of this node.
908
+ def in_degree
909
+ isRoot? ? 0 : 1
910
+ end
911
+
912
+ # Returns the outgoing edge-count of the receiver node.
913
+ #
914
+ # Out-degree is defined as:
915
+ # Out-degree:: The number of edges leaving the node (zero for leafs)
916
+ #
917
+ # @return [Number] The out-degree of this node.
918
+ def out_degree
919
+ isLeaf? ? 0 : children.size
920
+ end
921
+
922
+ protected :parent=, :setAsRoot!, :createDumpRep
923
+
924
+ protected
925
+ def buffer
926
+ @haml_buffer.buffer
927
+ end
928
+
929
+ end
930
+ end