module CSL::Treelike::ClassMethods

Attributes

keys[R]

Public Class Methods

new(attrs = {}) click to toggle source
Calls superclass method
# File lib/csl/treelike.rb, line 355
def initialize(attrs = {})
  super(*attrs.symbolize_keys.values_at(*keys))
  @order = []
end

Public Instance Methods

constantize_nodename(name) click to toggle source
# File lib/csl/treelike.rb, line 275
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)
  else
    nil
  end
end
create_children() click to toggle source

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.

# File lib/csl/treelike.rb, line 267
def create_children
  if const?(:Children)
    const_get(:Children).new
  else
    []
  end
end

Private Instance Methods

alias_child(new_name, old_name) click to toggle source
# File lib/csl/treelike.rb, line 471
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
attr_child_names_for(name) click to toggle source
# File lib/csl/treelike.rb, line 290
def attr_child_names_for(name)
  reader = name.to_s.downcase.tr('-', '_')
  [name.to_sym, reader, "set_child_#{reader}", "has_#{reader}?"]
end
attr_children(*names) click to toggle source

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 its children that way can only contain nodes of the given types.

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.

# File lib/csl/treelike.rb, line 307
def attr_children(*names)

  names.each do |name|
    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)

          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)

        add_child 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))
      @order = []
    end

    def index(node, &block)
      @order.index(node, &block)
    end

    # @return [<Symbol>] a list of symbols representing the names/keys
    #   of the attribute variables.
    def keys
      __class__.keys
    end


    def count
      values.reject { |c| c.nil? || c.is_a?(Array) && c.empty? }.length
    end

    alias original_each each

    # Iterates through all children. Nil values are skipped and Arrays
    # expanded.
    def each(&block)
      if block_given?
        order.each(&block)
        self
      else
        to_enum
      end
    end

    def empty?
      all?(&:nil?)
    end

    def select(&block)
      each.select(&block)
    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

      # Add to @order to keep track of node ordering
      @order << node

      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

      # Delete node from ordered list as well
      @order.delete(node)

      if deleted.nil? && block_given?
        yield
      else
        deleted
      end
    end

    def fetch(name, default = nil)
      if block_given?
        resolve(name) || yield(key)
      else
        resolve(name) || default
      end
    end

    protected

    attr_reader :order

    private

    def resolve(nodename)
      keys.include?(nodename.to_sym) && self[nodename]
    end
  })
end
count() click to toggle source
# File lib/csl/treelike.rb, line 371
def count
  values.reject { |c| c.nil? || c.is_a?(Array) && c.empty? }.length
end
delete(node) { || ... } click to toggle source

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.

# File lib/csl/treelike.rb, line 427
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

  # Delete node from ordered list as well
  @order.delete(node)

  if deleted.nil? && block_given?
    yield
  else
    deleted
  end
end
each(&block) click to toggle source

Iterates through all children. Nil values are skipped and Arrays expanded.

# File lib/csl/treelike.rb, line 379
def each(&block)
  if block_given?
    order.each(&block)
    self
  else
    to_enum
  end
end
empty?() click to toggle source
# File lib/csl/treelike.rb, line 388
def empty?
  all?(&:nil?)
end
fetch(name, default = nil) { |key| ... } click to toggle source
# File lib/csl/treelike.rb, line 451
def fetch(name, default = nil)
  if block_given?
    resolve(name) || yield(key)
  else
    resolve(name) || default
  end
end
has_no_children() click to toggle source

Turns the node into a leaf-node.

# File lib/csl/treelike.rb, line 478
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
index(node, &block) click to toggle source
# File lib/csl/treelike.rb, line 360
def index(node, &block)
  @order.index(node, &block)
end
keys() click to toggle source

@return [<Symbol>] a list of symbols representing the names/keys

of the attribute variables.
# File lib/csl/treelike.rb, line 366
def keys
  __class__.keys
end
push(node) click to toggle source

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.

# File lib/csl/treelike.rb, line 400
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

  # Add to @order to keep track of node ordering
  @order << node

  self
end
resolve(nodename) click to toggle source
# File lib/csl/treelike.rb, line 465
def resolve(nodename)
  keys.include?(nodename.to_sym) && self[nodename]
end
select(&block) click to toggle source
# File lib/csl/treelike.rb, line 392
def select(&block)
  each.select(&block)
end