treevisitor 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/LICENSE.txt +20 -0
- data/README.rdoc +42 -0
- data/Rakefile +9 -0
- data/VERSION.yml +5 -0
- data/bin/tree.rb +9 -0
- data/examples/find_files.rb +17 -0
- data/examples/print_files.rb +15 -0
- data/lib/tree_visitor.rb +2 -0
- data/lib/treevisitor.rb +31 -0
- data/lib/treevisitor/abs_node.rb +146 -0
- data/lib/treevisitor/cli/cli_tree.rb +98 -0
- data/lib/treevisitor/dir_processor.rb +46 -0
- data/lib/treevisitor/dir_tree_walker.rb +164 -0
- data/lib/treevisitor/leaf_node.rb +33 -0
- data/lib/treevisitor/tree_node.rb +247 -0
- data/lib/treevisitor/tree_node_visitor.rb +29 -0
- data/lib/treevisitor/visitors/block_tree_node_visitor.rb +21 -0
- data/lib/treevisitor/visitors/build_dir_tree_visitor.rb +53 -0
- data/lib/treevisitor/visitors/callback_tree_node_visitor.rb +43 -0
- data/lib/treevisitor/visitors/callback_tree_node_visitor2.rb +61 -0
- data/lib/treevisitor/visitors/clone_tree_node_visitor.rb +39 -0
- data/lib/treevisitor/visitors/depth_tree_node_visitor.rb +27 -0
- data/lib/treevisitor/visitors/flat_print_tree_node_visitors.rb +20 -0
- data/lib/treevisitor/visitors/print_dir_tree_visitor.rb +21 -0
- data/lib/treevisitor/visitors/print_tree_node_visitor.rb +40 -0
- data/lib/treevisitor_cli.rb +7 -0
- data/spec/fixtures/test_dir/.dir_with_dot/dummy.txt +0 -0
- data/spec/fixtures/test_dir/dir.1/dir.1.2/file.1.2.1 +0 -0
- data/spec/fixtures/test_dir/dir.1/file.1.1 +0 -0
- data/spec/fixtures/test_dir/dir.2/file.2.1 +0 -0
- data/spec/spec_helper.rb +29 -0
- data/spec/treevisitor/cli/cli_tree_spec.rb +58 -0
- data/spec/treevisitor/dir_processor_spec.rb +15 -0
- data/spec/treevisitor/dir_tree_walker_spec.rb +33 -0
- data/spec/treevisitor/tree_node_dsl_spec.rb +153 -0
- data/spec/treevisitor/tree_node_spec.rb +153 -0
- data/spec/treevisitor/tree_node_visitor_spec.rb +70 -0
- data/tasks/jeweler.rake +55 -0
- data/tasks/rspec.rake +34 -0
- data/tasks/rubyforge.rake +28 -0
- data/tasks/yard.rake +36 -0
- metadata +192 -0
@@ -0,0 +1,164 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
module TreeVisitor
|
3
|
+
|
4
|
+
#
|
5
|
+
# Visit a file system directory
|
6
|
+
#
|
7
|
+
class DirTreeWalker
|
8
|
+
|
9
|
+
#
|
10
|
+
# @param [String] dirname the root of the tree (top level directory)
|
11
|
+
#
|
12
|
+
def initialize( dirname )
|
13
|
+
@dirname = dirname
|
14
|
+
unless File.directory?( dirname )
|
15
|
+
raise "#{dirname} is not a directory!"
|
16
|
+
end
|
17
|
+
|
18
|
+
@visitor = nil
|
19
|
+
|
20
|
+
#
|
21
|
+
# pattern
|
22
|
+
#
|
23
|
+
@ignore_dir_patterns = []
|
24
|
+
@ignore_file_patterns = []
|
25
|
+
|
26
|
+
@match_file_patterns = []
|
27
|
+
|
28
|
+
#
|
29
|
+
# options
|
30
|
+
#
|
31
|
+
@visit_leaf = true
|
32
|
+
end
|
33
|
+
|
34
|
+
##########################################################################
|
35
|
+
# Pattern
|
36
|
+
|
37
|
+
|
38
|
+
#
|
39
|
+
# Ignore a node (leaf/Tree) matching pattern
|
40
|
+
# @param [RegEx] pattern
|
41
|
+
#
|
42
|
+
def ignore(pattern)
|
43
|
+
@ignore_dir_patterns << pattern
|
44
|
+
@ignore_file_patterns << pattern
|
45
|
+
end
|
46
|
+
|
47
|
+
#
|
48
|
+
# Ignore a directory (Tree) matching pattern
|
49
|
+
# @param [RegEx] pattern
|
50
|
+
#
|
51
|
+
def ignore_dir( pattern )
|
52
|
+
@ignore_dir_patterns << pattern
|
53
|
+
end
|
54
|
+
|
55
|
+
#
|
56
|
+
# Ignore a file (Leaf) matching pattern
|
57
|
+
# @param [RegEx] pattern
|
58
|
+
#
|
59
|
+
def ignore_file( pattern )
|
60
|
+
@ignore_file_patterns << pattern
|
61
|
+
end
|
62
|
+
|
63
|
+
#
|
64
|
+
# Just the opposite of ignore
|
65
|
+
# directory/file matching pattern will be visited
|
66
|
+
#
|
67
|
+
# @param [RegEx] pattern
|
68
|
+
#
|
69
|
+
def match( pattern )
|
70
|
+
@match_file_patterns << pattern
|
71
|
+
end
|
72
|
+
|
73
|
+
##########################################################################
|
74
|
+
# Options
|
75
|
+
|
76
|
+
#
|
77
|
+
# boh
|
78
|
+
#
|
79
|
+
attr_accessor :visit_leaf
|
80
|
+
|
81
|
+
##########################################################################
|
82
|
+
|
83
|
+
|
84
|
+
#
|
85
|
+
# Test directory ignore pattern
|
86
|
+
#
|
87
|
+
# @param [String] directory name
|
88
|
+
# @return [boolean] if dirname match almost one pattern
|
89
|
+
#
|
90
|
+
def ignore_dir?( dirname )
|
91
|
+
_include?( @ignore_dir_patterns, File.basename( dirname ) )
|
92
|
+
end
|
93
|
+
|
94
|
+
#
|
95
|
+
# Test file ignore pattern
|
96
|
+
#
|
97
|
+
# @param [String] file name
|
98
|
+
# @return [boolean] if filename match almost one pattern
|
99
|
+
#
|
100
|
+
def ignore_file?( filename )
|
101
|
+
_include?( @ignore_file_patterns, File.basename( filename ) )
|
102
|
+
end
|
103
|
+
|
104
|
+
#
|
105
|
+
# Test common ignore pattern
|
106
|
+
#
|
107
|
+
# @param [String] file name
|
108
|
+
# @return [boolean] if filename match almost one pattern
|
109
|
+
#
|
110
|
+
def match?( filename )
|
111
|
+
return true if @match_file_patterns.empty?
|
112
|
+
_include?( @match_file_patterns, File.basename( filename ) )
|
113
|
+
end
|
114
|
+
|
115
|
+
#
|
116
|
+
# Run the visitor through the directory tree
|
117
|
+
#
|
118
|
+
# return the visitor
|
119
|
+
#
|
120
|
+
def run( tree_node_visitor )
|
121
|
+
@visitor = tree_node_visitor
|
122
|
+
process_directory( File.expand_path( @dirname ) )
|
123
|
+
tree_node_visitor
|
124
|
+
end
|
125
|
+
|
126
|
+
private
|
127
|
+
|
128
|
+
def _include?(patterns, basename)
|
129
|
+
# return false if the patters.empty?
|
130
|
+
patterns.find{ |pattern|
|
131
|
+
if pattern.respond_to?(:match) # or if pattern.kind_of? Regexp
|
132
|
+
pattern.match( basename )
|
133
|
+
else
|
134
|
+
basename == pattern
|
135
|
+
end
|
136
|
+
}
|
137
|
+
end
|
138
|
+
|
139
|
+
#
|
140
|
+
# recurse on other directories
|
141
|
+
#
|
142
|
+
def process_directory( dirname )
|
143
|
+
@visitor.enter_tree_node( dirname )
|
144
|
+
# return if ignore_dir?( dirname )
|
145
|
+
|
146
|
+
Dir.entries( dirname ).sort.each { |basename|
|
147
|
+
next if basename == "." or basename == ".." # ignore always "." and ".."
|
148
|
+
pathname = File.join( dirname, basename )
|
149
|
+
|
150
|
+
if File.directory?( pathname )
|
151
|
+
# directory
|
152
|
+
process_directory( pathname ) unless ignore_dir?( basename )
|
153
|
+
else
|
154
|
+
if @visit_leaf
|
155
|
+
if match?( basename ) && ! ignore_file?( basename )
|
156
|
+
@visitor.visit_leaf_node( pathname )
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
}
|
161
|
+
@visitor.exit_tree_node( dirname )
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
module TreeVisitor
|
3
|
+
|
4
|
+
#
|
5
|
+
# Represent a LeafNode
|
6
|
+
#
|
7
|
+
class LeafNode < AbsNode
|
8
|
+
|
9
|
+
#
|
10
|
+
# @param [Object] content of node
|
11
|
+
#
|
12
|
+
def initialize( content, parent = nil )
|
13
|
+
super( content )
|
14
|
+
parent.add_leaf(self) if parent
|
15
|
+
end
|
16
|
+
|
17
|
+
#
|
18
|
+
# @return false because a leaf_node cannot be a root
|
19
|
+
#
|
20
|
+
def root?
|
21
|
+
false
|
22
|
+
end
|
23
|
+
|
24
|
+
#
|
25
|
+
# @return [TreeNodeVisitor] the visitor
|
26
|
+
#
|
27
|
+
def accept( visitor )
|
28
|
+
visitor.visit_leaf_node( self )
|
29
|
+
visitor
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,247 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
module TreeVisitor
|
3
|
+
#
|
4
|
+
# TreeNode can contains other TreeNode (children)
|
5
|
+
# and can contains LeafNode (leaves)
|
6
|
+
#
|
7
|
+
# TreeNode @children -1---n-> TreeNode
|
8
|
+
# @leaves -1---n-> LeafNode
|
9
|
+
#
|
10
|
+
class TreeNode < AbsNode
|
11
|
+
|
12
|
+
class << self
|
13
|
+
|
14
|
+
def create(class1 = TreeNode, class2 = LeafNode, &block)
|
15
|
+
if class1.ancestors.include?(TreeNode) and class2.ancestors.include?(LeafNode)
|
16
|
+
@tree_node_class = class1
|
17
|
+
@leaf_node_class = class2
|
18
|
+
elsif class1.ancestors.include?(LeafNode) and class2 == LeafNode
|
19
|
+
@tree_node_class = self
|
20
|
+
@leaf_node_class = class1
|
21
|
+
end
|
22
|
+
|
23
|
+
if @tree_node_class.nil? || @leaf_node_class.nil?
|
24
|
+
raise "Must be specified class derived from TreeNode and LeafNode"
|
25
|
+
end
|
26
|
+
|
27
|
+
@scope_stack = []
|
28
|
+
class_eval(&block)
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def node(*args, &block)
|
34
|
+
parent_node = @scope_stack.length > 0 ? @scope_stack[-1] : nil
|
35
|
+
args << parent_node
|
36
|
+
tree_node = @tree_node_class.new(*args)
|
37
|
+
@scope_stack.push tree_node
|
38
|
+
if block
|
39
|
+
if block.arity == 0 || block.arity == -1
|
40
|
+
class_eval(&block)
|
41
|
+
elsif block.arity == 1
|
42
|
+
new_block = Proc.new { block.call(tree_node) }
|
43
|
+
class_eval(&new_block)
|
44
|
+
else
|
45
|
+
raise "block take too much arguments #{block.arity}"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
@scope_stack.pop
|
49
|
+
end
|
50
|
+
|
51
|
+
def leaf(*args, &block)
|
52
|
+
tree_node = @scope_stack[-1]
|
53
|
+
args << tree_node
|
54
|
+
leaf_node = @leaf_node_class.new(*args)
|
55
|
+
if block
|
56
|
+
if block.arity == 0 || block.arity == -1
|
57
|
+
class_eval(&block)
|
58
|
+
elsif block.arity == 1
|
59
|
+
new_block = Proc.new { block.call(leaf_node) }
|
60
|
+
class_eval(&new_block)
|
61
|
+
else
|
62
|
+
raise "block take too much arguments #{block.arity}"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
leaf_node
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
attr_reader :leaves
|
70
|
+
attr_reader :children
|
71
|
+
|
72
|
+
#
|
73
|
+
# @param [Object] content of this node
|
74
|
+
#
|
75
|
+
def initialize(content, parent = nil)
|
76
|
+
@leaves = []
|
77
|
+
@children = []
|
78
|
+
super(content)
|
79
|
+
parent.add_child(self) if parent
|
80
|
+
end
|
81
|
+
|
82
|
+
#
|
83
|
+
# Test if is a root
|
84
|
+
#
|
85
|
+
def root?
|
86
|
+
@parent.nil?
|
87
|
+
end
|
88
|
+
|
89
|
+
#
|
90
|
+
# invalidate cached info
|
91
|
+
# invalidate propagates form parent to children and leaves
|
92
|
+
#
|
93
|
+
def invalidate
|
94
|
+
super
|
95
|
+
@children.each { |c| c.invalidate }
|
96
|
+
@leaves.each { |l| l.invalidate }
|
97
|
+
end
|
98
|
+
|
99
|
+
#
|
100
|
+
# @return [FixNum] total number of nodes
|
101
|
+
#
|
102
|
+
def nr_nodes
|
103
|
+
nr = @leaves.length + @children.length
|
104
|
+
@children.inject(nr) { |sum, c| sum + c.nr_nodes }
|
105
|
+
end
|
106
|
+
|
107
|
+
#
|
108
|
+
# @return [FixNum] total number of leaves
|
109
|
+
#
|
110
|
+
def nr_leaves
|
111
|
+
@leaves.length + @children.inject(0) { |sum, child| sum + child.nr_leaves }
|
112
|
+
end
|
113
|
+
|
114
|
+
#
|
115
|
+
# @return [FixNum] total number of children
|
116
|
+
#
|
117
|
+
def nr_children
|
118
|
+
@children.length + @children.inject(0) { |sum, child| sum + child.nr_children }
|
119
|
+
end
|
120
|
+
|
121
|
+
#
|
122
|
+
# Add a Leaf
|
123
|
+
# @param [LeafNode]
|
124
|
+
#
|
125
|
+
# @return self
|
126
|
+
#
|
127
|
+
def add_leaf(leaf)
|
128
|
+
return if leaf.parent == self
|
129
|
+
if not leaf.parent.nil?
|
130
|
+
leaf.remove_from_parent
|
131
|
+
end
|
132
|
+
leaf.parent = self
|
133
|
+
if @leaves.length > 0
|
134
|
+
@leaves.last.next = leaf
|
135
|
+
leaf.prev = @leaves.last
|
136
|
+
else
|
137
|
+
leaf.prev = nil
|
138
|
+
end
|
139
|
+
leaf.next = nil
|
140
|
+
leaf.invalidate
|
141
|
+
@leaves << leaf
|
142
|
+
self
|
143
|
+
end
|
144
|
+
|
145
|
+
#
|
146
|
+
# Add a Tree
|
147
|
+
# @param [LeafNode]
|
148
|
+
#
|
149
|
+
# @return self
|
150
|
+
#
|
151
|
+
def add_child(tree_node)
|
152
|
+
return if tree_node.parent == self
|
153
|
+
if not tree_node.parent.nil?
|
154
|
+
tree_node.remove_from_parent
|
155
|
+
else
|
156
|
+
tree_node.prefix_path = nil
|
157
|
+
end
|
158
|
+
tree_node.invalidate
|
159
|
+
tree_node.parent = self
|
160
|
+
if @children.length > 0
|
161
|
+
@children.last.next = tree_node
|
162
|
+
tree_node.prev = @children.last
|
163
|
+
else
|
164
|
+
tree_node.prev = nil
|
165
|
+
end
|
166
|
+
tree_node.next = nil
|
167
|
+
@children << tree_node
|
168
|
+
self
|
169
|
+
end
|
170
|
+
|
171
|
+
#
|
172
|
+
# Find a node down the hierarchy with content
|
173
|
+
# @param [Object] content of searched node
|
174
|
+
# @return [Object, nil] nil if no
|
175
|
+
#
|
176
|
+
def find(content = nil, &block)
|
177
|
+
if content and block_given?
|
178
|
+
raise "TreeNode::find - passed content AND block"
|
179
|
+
end
|
180
|
+
|
181
|
+
if content
|
182
|
+
block = proc { |c| c == content }
|
183
|
+
end
|
184
|
+
return self if block.call(self.content)
|
185
|
+
|
186
|
+
leaf = @leaves.find { |l| block.call(l.content) }
|
187
|
+
return leaf if leaf
|
188
|
+
|
189
|
+
@children.each do |child|
|
190
|
+
node = child.find &block
|
191
|
+
return node if node
|
192
|
+
end
|
193
|
+
nil
|
194
|
+
end
|
195
|
+
|
196
|
+
#
|
197
|
+
# return the visitor
|
198
|
+
#
|
199
|
+
def accept(visitor)
|
200
|
+
visitor.enter_tree_node(self)
|
201
|
+
@leaves.each { |leaf|
|
202
|
+
leaf.accept(visitor)
|
203
|
+
}
|
204
|
+
@children.each { |child|
|
205
|
+
child.accept(visitor)
|
206
|
+
}
|
207
|
+
visitor.exit_tree_node(self)
|
208
|
+
visitor
|
209
|
+
end
|
210
|
+
|
211
|
+
#
|
212
|
+
# Format the content of tree
|
213
|
+
#
|
214
|
+
def to_str(prefix= "")
|
215
|
+
str = ""
|
216
|
+
|
217
|
+
if root?
|
218
|
+
str << to_s << "\n"
|
219
|
+
else
|
220
|
+
str << prefix
|
221
|
+
if self.next
|
222
|
+
str << '|-- '
|
223
|
+
else
|
224
|
+
str << '`-- '
|
225
|
+
end
|
226
|
+
str << to_s << "\n"
|
227
|
+
prefix += self.next ? "| " : " "
|
228
|
+
end
|
229
|
+
|
230
|
+
@leaves.each do |leaf|
|
231
|
+
str << prefix
|
232
|
+
if !leaf.next.nil? or !@children.empty?
|
233
|
+
str << '|-- '
|
234
|
+
else
|
235
|
+
str << '`-- '
|
236
|
+
end
|
237
|
+
str << leaf.to_s << "\n"
|
238
|
+
end
|
239
|
+
|
240
|
+
@children.each do |child|
|
241
|
+
str << child.to_str(prefix)
|
242
|
+
end
|
243
|
+
str
|
244
|
+
end
|
245
|
+
|
246
|
+
end
|
247
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
module TreeVisitor
|
3
|
+
#
|
4
|
+
# Callback methods used to visit a tree
|
5
|
+
# Are empty so it is possible to define only a subset
|
6
|
+
#
|
7
|
+
class TreeNodeVisitor
|
8
|
+
|
9
|
+
#
|
10
|
+
# called on tree node at start of the visit i.e. we start to visit the subtree
|
11
|
+
#
|
12
|
+
def enter_tree_node( tree_node )
|
13
|
+
end
|
14
|
+
|
15
|
+
#
|
16
|
+
# called on tree node at end of the visit i.e. oll subtree are visited
|
17
|
+
#
|
18
|
+
def exit_tree_node( tree_node )
|
19
|
+
end
|
20
|
+
|
21
|
+
#
|
22
|
+
# called when visit leaf node
|
23
|
+
#
|
24
|
+
def visit_leaf_node( leaf_node )
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|