forked from rapid7/metasploit-framework
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathtree.rb
244 lines (213 loc) · 5.52 KB
/
tree.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
#
# 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