metasploit-framework/lib/rabal/tree.rb

245 lines
5.5 KiB
Ruby

#
# Rabal Tree,rb
# A basic Tree structure
#
class Tree
include Enumerable
# The name of this Tree
attr_accessor :name
# The parent of this node. If this is nil then this Tree is a
# root.
attr_accessor :parent
# The children of this node. If this Hash is empty, then this
# Tree is a leaf.
attr_accessor :children
# An abstract data point that can be utilized by child classes
# for whatever they like. If this is non-nil and responds to
# method calls it will be searched as part of the
# 'method_missing' protocol.
attr_accessor :parameters
#
# Create a new Tree with the given object.to_s as its +name+.
#
def initialize(name)
@name = name.to_s
@parent = nil
@children = {}
@parameters = nil
end
#
# Return true if this Tree has no children.
#
def is_leaf?
@children.empty?
end
#
# Return true if this Tree has no parent.
#
def is_root?
@parent.nil?
end
#
# Return the root node of the tree
#
def root
return self if is_root?
return @parent.root
end
#
# Return the distance from the root
#
def depth
return 0 if is_root?
return (1 + @parent.depth)
end
#
# Attach the given tree at the indicated path. The path given
# is always assumed to be from the root of the Tree being
# attached to.
#
# The path is given as a string separated by '/'. Each portion
# of the string is matched against the name of the particular
# tree.
#
# Given :
#
# a --- b --- c
# \
# d - e --- f
# \
# g - h
#
# * the path +a/b/c+ will match the path to Tree +c+
# * the path +d/e/g+ will _not_ match anything as the path must start at +a+ here
# * the path +a/d/e+ will _not_ match anytthin as +e+ is not a child of +d+
# * the path +a/d/e/g+ will match node +g+
#
# Leading and trailing '/' on the path are not necessary and removed.
#
def add_at_path(path,subtree)
parent_tree = tree_at_path(path)
parent_tree << subtree
return self
end
#
# Return the Tree that resides at the given path
#
def tree_at_path(path_str)
path_str = path_str.chomp("/").reverse.chomp("/").reverse
path = path_str.split("/")
# strip of the redundant first match if it is the same as
# the current node
find_subtree(path)
end
#
# Add the given object to the Tree as a child of this node. If
# the given object is not a Tree then wrap it with a Tree.
#
def <<(subtree)
# this should not generally be the case, but wrap things
# up to be nice.
if not subtree.kind_of?(Tree) then
subtree = Tree.new(subtree)
end
subtree.parent = self
# FIXME: techinically this should no longer be called 'post_add'
# but maybe 'add_hook'
subtree.post_add
# Don't overwrite any existing children with the same name,
# just put the new subtree's children into the children of
# the already existing subtree.
if children.has_key?(subtree.name) then
subtree.children.each_pair do |subtree_child_name,subtree_child_tree|
children[subtree.name] << subtree_child_tree
end
else
children[subtree.name] = subtree
end
return self
end
alias :add :<<
#
# Allow for Enumerable to be included. This just wraps walk.
#
def each
self.walk(self,lambda { |tree| yield tree })
end
#
# Count how many items are in the tree
#
def size
inject(0) { |count,n| count + 1 }
end
#
# Walk the tree in a depth first manner, visiting the Tree
# first, then its children
#
def walk(tree,method)
method.call(tree)
tree.children.each_pair do |name,child|
walk(child,method)
end
#for depth first
#method.call(tree)
end
#
# Allow for a method call to cascade up the tree looking for a
# Tree that responds to the call.
#
def method_missing(method_id,*params,&block)
if not parameters.nil? and parameters.respond_to?(method_id, true) then
return parameters.send(method_id, *params, &block)
elsif not is_root? then
@parent.send method_id, *params, &block
else
raise NoMethodError, "undefined method `#{method_id}' for #{name}:Tree"
end
end
#
# allow for a hook so newly added Tree objects may do custom
# processing after being added to the tree, but before the tree
# is walked or processed
#
def post_add
end
#
# find the current path of the tree from root to here, return it
# as a '/' separates string.
#
def current_path
#
# WMAP: removed the asterixs and modified the first return
# to just return a '/'
#
return "/" if is_root?
return ([name,parent.current_path]).flatten.reverse.join("/")
end
#
# Given the initial path to match as an Array find the Tree that
# matches that path.
#
def find_subtree(path)
if path.empty? then
return self
else
possible_child = path.shift
if children.has_key?(possible_child) then
children[possible_child].find_subtree(path)
else
raise PathNotFoundError, "`#{possible_child}' does not match anything at the current path in the Tree (#{current_path})"
end
end
end
#
# Return true of false if the given subtree exists or not
#
def has_subtree?(path)
begin
find_subtree(path)
return true
rescue PathNotFoundError
return false
end
end
#
# Allow for numerable to be included. This just wraps walk
#
def each
self.walk(self,lambda { |tree| yield tree })
end
def each_datum
self.walk(self,lambda { |tree| yield tree.data})
end
end