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.
Files changed (42) hide show
  1. data/LICENSE.txt +20 -0
  2. data/README.rdoc +42 -0
  3. data/Rakefile +9 -0
  4. data/VERSION.yml +5 -0
  5. data/bin/tree.rb +9 -0
  6. data/examples/find_files.rb +17 -0
  7. data/examples/print_files.rb +15 -0
  8. data/lib/tree_visitor.rb +2 -0
  9. data/lib/treevisitor.rb +31 -0
  10. data/lib/treevisitor/abs_node.rb +146 -0
  11. data/lib/treevisitor/cli/cli_tree.rb +98 -0
  12. data/lib/treevisitor/dir_processor.rb +46 -0
  13. data/lib/treevisitor/dir_tree_walker.rb +164 -0
  14. data/lib/treevisitor/leaf_node.rb +33 -0
  15. data/lib/treevisitor/tree_node.rb +247 -0
  16. data/lib/treevisitor/tree_node_visitor.rb +29 -0
  17. data/lib/treevisitor/visitors/block_tree_node_visitor.rb +21 -0
  18. data/lib/treevisitor/visitors/build_dir_tree_visitor.rb +53 -0
  19. data/lib/treevisitor/visitors/callback_tree_node_visitor.rb +43 -0
  20. data/lib/treevisitor/visitors/callback_tree_node_visitor2.rb +61 -0
  21. data/lib/treevisitor/visitors/clone_tree_node_visitor.rb +39 -0
  22. data/lib/treevisitor/visitors/depth_tree_node_visitor.rb +27 -0
  23. data/lib/treevisitor/visitors/flat_print_tree_node_visitors.rb +20 -0
  24. data/lib/treevisitor/visitors/print_dir_tree_visitor.rb +21 -0
  25. data/lib/treevisitor/visitors/print_tree_node_visitor.rb +40 -0
  26. data/lib/treevisitor_cli.rb +7 -0
  27. data/spec/fixtures/test_dir/.dir_with_dot/dummy.txt +0 -0
  28. data/spec/fixtures/test_dir/dir.1/dir.1.2/file.1.2.1 +0 -0
  29. data/spec/fixtures/test_dir/dir.1/file.1.1 +0 -0
  30. data/spec/fixtures/test_dir/dir.2/file.2.1 +0 -0
  31. data/spec/spec_helper.rb +29 -0
  32. data/spec/treevisitor/cli/cli_tree_spec.rb +58 -0
  33. data/spec/treevisitor/dir_processor_spec.rb +15 -0
  34. data/spec/treevisitor/dir_tree_walker_spec.rb +33 -0
  35. data/spec/treevisitor/tree_node_dsl_spec.rb +153 -0
  36. data/spec/treevisitor/tree_node_spec.rb +153 -0
  37. data/spec/treevisitor/tree_node_visitor_spec.rb +70 -0
  38. data/tasks/jeweler.rake +55 -0
  39. data/tasks/rspec.rake +34 -0
  40. data/tasks/rubyforge.rake +28 -0
  41. data/tasks/yard.rake +36 -0
  42. 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