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

- old
+ new

@@ -1,19 +1,19 @@ module CSL - + module Treelike - + attr_accessor :parent attr_reader :children attr_writer :nodename - + protected :parent= - + def self.included(base) base.extend(ClassMethods) end - + # @return [String] the node's name. def nodename @nodename ||= self.class.name.split(/::/)[-1].gsub(/([[:lower:]])([[:upper:]])/, '\1-\2').downcase end @@ -23,71 +23,70 @@ self else enum_for :each_child end end - + def delete_children(*nodes) nodes.each do |node| delete_child node end end + alias delete delete_children # Deletes child nodes that are equal to the passed-in node. Returns all # deleted children. If no children were deleted, returns nil. If the # optional block is given, returns the result block if no children were # deleted. def delete_child(child) deleted = children.delete child - + case when deleted.nil? && block_given? yield when deleted.nil? nil else - [*deleted].each do |node| - node.parent = nil - - deleted_child node - node.deleted_from self - end - + deleted.parent = nil + + deleted_child deleted + deleted.deleted_from self + deleted end rescue => e # TODO rollback raise e end - + def add_children(*nodes) nodes.each do |node| add_child node end self end - + def add_child(node) node.unlink - + node.parent = self children << node - + added_child node node.added_to self - + node rescue => e # TODO rollback raise e end - + def <<(node) add_child node self end - + # Returns the first immediate child node whose nodename matches the # passed-in name/pattern and attribute conditions. # # @param name [String,Regexp] the node name to match # @param conditions [Hash] the attributes to match @@ -97,11 +96,11 @@ children.detect do |child| child.match?(name, conditions) end end alias > find_child - + # Returns all immediate child nodes whose nodename matches the passed-in # name/pattern and attribute conditions; returns an empty array if there # is no match. # # @param name [String,Regexp] the node name to match @@ -122,98 +121,98 @@ # @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 # the old parent node or nil. def unlink return nil if root? - + other = parent other.delete_child self - + self.parent = nil other end - + def each_sibling if block_given? unless root? parent.children.each do |node| yield node unless node.equal?(self) end end - + self else enum_for :each_sibling end end - + 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 + yield child child.each_descendant(&Proc.new) end - + self else enum_for :each_descendant end end - + # Returns all descendants of the node. See {#descendants!} # for a memoized version. def descendants @descendants = each_descendant.to_a end - + def each_ancestor if block_given? p = parent - + until p.nil? yield p p = p.parent end - + self else enum_for :each_ancestor end end - + # @returns this node's ancestors as an array def ancestors @ancestors = each_ancestor.to_a end - + # @return [Fixnum] the node's current depth in the tree def depth @depth = ancestors.length end - + # @return [Node] the root node def root @root = root? ? self : parent.root! end - + # @returns [Boolean] whether or not the node is the tree's root node def root? parent.nil? end - + # Add memoized methods. When processing citations, styles will # typically remain stable; therefore cite processors may opt # to use memoized versions of the following methods. These # versions are marked with an exclamation mark as a reminder # that the return values are cached and potentially outdated. @@ -221,69 +220,69 @@ ivar = "@#{name}" define_method("#{name}!") do instance_variable_get(ivar) || send(name) end end - + protected - + # @abstract # Called after the node was added to another node. def added_to(node) end - + # @abstract # Called when the node was deleted from an other node. def deleted_from(node) end - + private - + # @abstract # Called when the passed-in node was added to this node as a child. def added_child(node) end - + # @abstract # Called when the passed-in node was deleted from this node's child nodes. def deleted_child(node) end - + module ClassMethods - + # Returns a new instance of an Array or Struct to manage the Node's # children. This method is called automatically by the Node's # constructor. def create_children if const?(:Children) const_get(:Children).new else [] 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 } - + if const_defined?(klass) - const_get(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 @@ -293,11 +292,11 @@ # # 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, reader, writer, predicate = attr_child_names_for(name) define_method(reader) do children[name] @@ -310,52 +309,52 @@ unless method_defined?(writer) define_method(writer) do |value| begin klass = self.class.constantize_nodename(name) - + if klass value = klass.new(value) else # try to force convert value value = (value.is_a?(String) ? TextNode : Node).new(value) value.nodename = name.to_s end - + 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 unless method_defined?(:"#{reader}=") end end - + const_set(:Children, Struct.new(*names) { - + # 1.8 Compatibility @keys = members.map(&:to_sym).freeze - + class << self attr_reader :keys end - + def initialize(attrs = {}) super(*attrs.symbolize_keys.values_at(*keys)) end # @return [<Symbol>] a list of symbols representing the names/keys # of the attribute variables. def 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 @@ -371,108 +370,108 @@ end else to_enum end end - + def empty? all?(&:nil?) end - + # Adds the node as a child node. Raises ValidationError if none # of the Struct members matches the node's name. If there is # already a node set with this node name, the node will be pushed # to an array for that name. def push(node) unless node.respond_to?(:nodename) && keys.include?(node.nodename.to_sym) raise ValidationError, "not allowed to add #{node.inspect} to #{inspect}" end - + current = self[node.nodename] case current when Array current.push(node) when nil self[node.nodename] = node else self[node.nodename] = [current, node] end - + self end - + alias << push - + # Delete items from self that are equal to node. If any items are # found, returns the deleted items. If the items is not found, # returns nil. If the optional code block is given, returns the # result og block if the item is not found. def delete(node) return nil unless node.respond_to?(:nodename) - + deleted = resolve(node.nodename) if deleted.kind_of?(Array) deleted = deleted.delete(node) else if deleted == node self[node.nodename] = nil else deleted = nil end end - + if deleted.nil? && block_given? yield else deleted end end - + def fetch(name, default = nil) - if block_given? + if block_given? resolve(name) || yield(key) else resolve(name) || default end end - + private - + def resolve(nodename) keys.include?(nodename.to_sym) && send(nodename) end }) end - + 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 + def has_no_children undef_method :add_child undef_method :added_child undef_method :add_children undef_method :<< undef_method :delete_child undef_method :deleted_child undef_method :delete_children - + private :children - + define_method(:has_children?) do false end define_method(:empty?) do true end end - + end - + end end \ No newline at end of file