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.
- data/.document +4 -0
- data/.rspec +1 -0
- data/Gemfile +10 -0
- data/Gemfile.lock +30 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +65 -0
- data/Rakefile +52 -0
- data/VERSION +1 -0
- data/active_component.gemspec +104 -0
- data/init.rb +1 -0
- data/lib/active_component.rb +196 -0
- data/lib/active_component/base.rb +930 -0
- data/lib/active_component/components/block.rb +20 -0
- data/lib/active_component/components/empty_tag.rb +26 -0
- data/lib/active_component/components/heading.rb +60 -0
- data/lib/active_component/components/inline_tag.rb +24 -0
- data/lib/active_component/components/section.rb +39 -0
- data/lib/active_component/components/table.rb +61 -0
- data/lib/active_component/config.rb +33 -0
- data/lib/active_component/core_extensions.rb +99 -0
- data/lib/active_component/template_handler.rb +20 -0
- data/pkg/active_component-0.1.2.gem +0 -0
- data/spec/active_component_spec.rb +156 -0
- data/spec/active_component_spec_helper.rb +10 -0
- data/spec/base_spec.rb +154 -0
- data/spec/components/block_spec.rb +51 -0
- data/spec/components/heading_spec.rb +95 -0
- data/spec/components/section_spec.rb +35 -0
- data/spec/components/table_spec.rb +88 -0
- data/spec/core_extensions_spec.rb +62 -0
- data/spec/factories.rb +23 -0
- data/spec/rcov.opts +2 -0
- data/spec/spec.opts +4 -0
- data/spec/spec_helper.rb +15 -0
- metadata +166 -0
@@ -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
|