lib/csl/treelike.rb in csl-1.0.0.pre2 vs lib/csl/treelike.rb in csl-1.0.0.pre3

- old
+ new

@@ -10,11 +10,11 @@ def self.included(base) base.extend(ClassMethods) end - # @eturn [String] the node's name. + # @return [String] the node's name. def nodename @nodename ||= self.class.name.split(/::/)[-1].gsub(/([[:lower:]])([[:upper:]])/, '\1-\2').downcase end def each_child @@ -61,10 +61,11 @@ def add_children(*nodes) nodes.each do |node| add_child node end + self end def add_child(node) node.unlink @@ -84,32 +85,44 @@ add_child node self end # Returns the first immediate child node whose nodename matches the - # passed-in name or pattern; returns nil there is no match. - def find_child_by_name(name) + # passed-in name/pattern and attribute conditions. + # + # @param name [String,Regexp] the node name to match + # @param conditions [Hash] the attributes to match + # + # @return [Node,nil] the first matching child node + def find_child(name, conditions = {}) children.detect do |child| - name === child.nodename + child.match?(name, conditions) end end - alias > find_child_by_name + alias > find_child # Returns all immediate child nodes whose nodename matches the passed-in - # name or pattern; returns an empty array if there is no match. - def find_children_by_name(name) + # name/pattern and attribute conditions; returns an empty array if there + # is no match. + # + # @param name [String,Regexp] the node name to match + # @param conditions [Hash] the attributes to match + # + # @return [Array<Node>] all matching child nodes + def find_children(name, conditions = {}) children.select do |child| - name === child.nodename + child.match?(name, conditions) end end - alias >> find_children_by_name + alias >> find_children - # Returns true if the node has child nodes; false otherwise. + # @return [Boolean] true if this node has child nodes; false otherwise. def has_children? !empty? end + # @return [Boolean] true if this node has no child nodes; false otherwise. def empty? children.empty? end # Unlinks the node and all its children from its parent node. Returns @@ -141,10 +154,11 @@ def siblings @siblings = each_sibling.to_a end + # Traverses the node's sub-tree in depth-first order. def each_descendant if block_given? each_child do |child| yield child child.each_descendant(&Proc.new) @@ -154,12 +168,12 @@ else enum_for :each_descendant end end - # Returns all descendants of the node. See #descendants1 for a memoized - # version. + # Returns all descendants of the node. See {#descendants!} + # for a memoized version. def descendants @descendants = each_descendant.to_a end def each_ancestor @@ -175,26 +189,26 @@ else enum_for :each_ancestor end end - # Returns this node's ancestors as an array. + # @returns this node's ancestors as an array def ancestors @ancestors = each_ancestor.to_a end - # Returns the node's current depth in the tree. + # @return [Fixnum] the node's current depth in the tree def depth @depth = ancestors.length end - # Returns the root node. + # @return [Node] the root node def root @root = root? ? self : parent.root! end - # Returns true if the node is a root node, or false. + # @returns [Boolean] whether or not the node is the tree's root node def root? parent.nil? end @@ -247,47 +261,55 @@ [] end end def constantize_nodename(name) + return constantize(name) if respond_to?(:constantize) + klass = name.to_s.capitalize.gsub(/(\w)-(\w)/) { [$1, $2.upcase].join } - - case - when respond_to?(:constantize) - constantize(klass) - when const_defined?(klass) - const_get(klass) + + if const_defined?(klass) + const_get(klass) else nil end end private + def attr_child_names_for(name) + reader = name.to_s.downcase.tr('-', '_') + [name.to_sym, reader, "set_child_#{reader}", "has_#{reader}?"] + end + # Creates a Struct for the passed-in child node names that will be # used internally by the Node to manage its children. The Struct # will be automatically initialized and is used similarly to the # standard Array that normally holds the child nodes. The benefit of # using the Struct is that all child nodes are accessible by name and # need not be looked up; this improves performance, however, note that # a node defining it's children that way can only contain nodes of the # given types. # - # This method also generates accessors for each child. + # This method also generates accessors for each child. The writer + # method will try to coerce the passed-in value into the correct + # node type automatically. def attr_children(*names) names.each do |name| - name = name.to_sym - reader = name.to_s.downcase.tr('-', '_') - writer = "set_child_#{reader}" - + name, reader, writer, predicate = attr_child_names_for(name) define_method(reader) do children[name] end unless method_defined?(reader) + define_method(predicate) do + c = children[name] + !(c.nil? || c.is_a?(Array) && c.empty?) + end unless method_defined?(predicate) + unless method_defined?(writer) define_method(writer) do |value| begin klass = self.class.constantize_nodename(name) @@ -302,14 +324,14 @@ rescue => e raise ArgumentError, "failed to convert #{value.inspect} to node: #{e.message}" end unless value.respond_to?(:nodename) children << value + value end - alias_method :"#{reader}=", writer - + alias_method :"#{reader}=", writer unless method_defined?(:"#{reader}=") end end const_set(:Children, Struct.new(*names) { @@ -325,15 +347,19 @@ end # @return [<Symbol>] a list of symbols representing the names/keys # of the attribute variables. def keys - self.class.keys + __class__.keys end alias original_each each + def count + values.reject { |c| c.nil? || c.empty? }.length + end + # Iterates through all children. Nil values are skipped and Arrays # expanded. def each if block_given? original_each do |node| @@ -415,11 +441,17 @@ keys.include?(nodename.to_sym) && send(nodename) end }) end - # Turns the Node into a leaf-node. + def alias_child(new_name, old_name) + attr_child_names_for(new_name).zip(attr_child_names_for(old_name)).each do |nn, on| + alias_method nn, on if method_defined?(on) + end + end + + # Turns the node into a leaf-node. def has_no_children undef_method :add_child undef_method :added_child undef_method :add_children undef_method :<< @@ -430,9 +462,13 @@ private :children define_method(:has_children?) do false + end + + define_method(:empty?) do + true end end end \ No newline at end of file