tree.rb 0.3.0

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 (58) hide show
  1. data/.gemtest +0 -0
  2. data/LICENSE.txt +20 -0
  3. data/README.md +160 -0
  4. data/Rakefile +12 -0
  5. data/bin/tree.rb +9 -0
  6. data/examples/directory_walker/directory_without_subdirectory.rb +37 -0
  7. data/examples/directory_walker/find_files.rb +18 -0
  8. data/examples/directory_walker/print_files.rb +12 -0
  9. data/examples/protovis/directory_to_json_visitor.rb +15 -0
  10. data/examples/protovis/index.html +87 -0
  11. data/examples/protovis/protovis-r3.2.js +277 -0
  12. data/examples/protovis/treevisitor.js +33 -0
  13. data/examples/protovis/treevisitor.png +0 -0
  14. data/lib/tree_visitor.rb +2 -0
  15. data/lib/treevisitor/abs_node.rb +144 -0
  16. data/lib/treevisitor/basic_tree_node_visitor.rb +44 -0
  17. data/lib/treevisitor/cli/cli_tree.rb +121 -0
  18. data/lib/treevisitor/directory_walker.rb +300 -0
  19. data/lib/treevisitor/leaf_node.rb +33 -0
  20. data/lib/treevisitor/tree_node.rb +296 -0
  21. data/lib/treevisitor/tree_node_visitor.rb +120 -0
  22. data/lib/treevisitor/util/dir_processor.rb +46 -0
  23. data/lib/treevisitor/version.rb +4 -0
  24. data/lib/treevisitor/visitors/block_tree_node_visitor.rb +21 -0
  25. data/lib/treevisitor/visitors/build_dir_tree_visitor.rb +57 -0
  26. data/lib/treevisitor/visitors/callback_tree_node_visitor2.rb +48 -0
  27. data/lib/treevisitor/visitors/clone_tree_node_visitor.rb +39 -0
  28. data/lib/treevisitor/visitors/depth_tree_node_visitor.rb +27 -0
  29. data/lib/treevisitor/visitors/directory_to_hash_visitor.rb +33 -0
  30. data/lib/treevisitor/visitors/flat_print_tree_node_visitors.rb +20 -0
  31. data/lib/treevisitor/visitors/print_dir_tree_visitor.rb +21 -0
  32. data/lib/treevisitor/visitors/print_tree_node_visitor.rb +40 -0
  33. data/lib/treevisitor.rb +40 -0
  34. data/lib/treevisitor_cli.rb +7 -0
  35. data/spec/fixtures/test_dir_1/.dir_with_dot/dummy.txt +0 -0
  36. data/spec/fixtures/test_dir_1/dir.1/dir.1.2/file.1.2.1 +0 -0
  37. data/spec/fixtures/test_dir_1/dir.1/file.1.1 +0 -0
  38. data/spec/fixtures/test_dir_1/dir.2/file.2.1 +0 -0
  39. data/spec/fixtures/test_dir_2/[Dsube]/sub/.gitkeep +0 -0
  40. data/spec/spec_helper.rb +27 -0
  41. data/spec/treevisitor/cli/cli_tree_spec.rb +69 -0
  42. data/spec/treevisitor/directory_walker_spec.rb +99 -0
  43. data/spec/treevisitor/tree_dsl_spec.rb +57 -0
  44. data/spec/treevisitor/tree_dsl_with_derived_class1_spec.rb +53 -0
  45. data/spec/treevisitor/tree_dsl_with_derived_class_spec.rb +51 -0
  46. data/spec/treevisitor/tree_node_paths_spec.rb +70 -0
  47. data/spec/treevisitor/tree_node_spec.rb +135 -0
  48. data/spec/treevisitor/tree_node_visitor_delegate_spec.rb +35 -0
  49. data/spec/treevisitor/tree_node_visitor_dsl_spec.rb +66 -0
  50. data/spec/treevisitor/util/dir_processor_spec.rb +13 -0
  51. data/spec/treevisitor/visitors/block_tree_node_visitor_spec.rb +25 -0
  52. data/spec/treevisitor/visitors/callback_tree_node_visitor2_spec.rb +38 -0
  53. data/spec/treevisitor/visitors/depth_tree_node_visitor_spec.rb +28 -0
  54. data/spec/treevisitor/visitors/tree_node_visitors_spec.rb +27 -0
  55. data/tasks/rspec.rake +34 -0
  56. data/tasks/yard.rake +36 -0
  57. data/tree.rb.gemspec +70 -0
  58. metadata +231 -0
@@ -0,0 +1,144 @@
1
+ # -*- coding: utf-8 -*-
2
+ module TreeVisitor
3
+
4
+ #
5
+ # @abstract Subclass to implement a concrete Node (Leaf or Tree).
6
+ #
7
+ # Abstract Node
8
+ # Class hierarchy
9
+ #
10
+ # AbsNode has a name, a parent
11
+ # ^ and define a path i.e. concatenation ancestor names
12
+ # |
13
+ # |-- LeafNode
14
+ # |
15
+ # `-- TreeNode
16
+ #
17
+ # Object diagram
18
+ #
19
+ # TreeNode (parent: nil)
20
+ # |
21
+ # |--->[ LeafNode, LeafNode, LeafNode ]
22
+ # |
23
+ # |--->[ TreeNode, TreeNode ]
24
+ # |
25
+ # |--> [LeafNode]
26
+ # |
27
+ # `--> [TreeNode, TreeNode]
28
+ class AbsNode
29
+
30
+ class << self
31
+ attr_accessor :path_separator
32
+ end
33
+ self.path_separator = File::SEPARATOR
34
+
35
+ attr_reader :parent
36
+ attr_reader :content
37
+
38
+ attr_accessor :prev
39
+ attr_accessor :next
40
+
41
+ #
42
+ # Create a new AbsNode
43
+ #
44
+ # @param content of node
45
+ #
46
+ def initialize(content)
47
+ @parent = nil
48
+ @content = content
49
+ @prefix_path = nil
50
+ invalidate
51
+ end
52
+
53
+ #
54
+ # invalidate cached path info
55
+ #
56
+ def invalidate
57
+ @path = nil
58
+ @path_with_prefix = nil
59
+ @depth = nil
60
+ @root = nil
61
+ end
62
+
63
+ #
64
+ # Root node could have assigned a path
65
+ #
66
+ def prefix_path
67
+ raise "Not root!!" unless @parent.nil?
68
+ @prefix_path
69
+ end
70
+
71
+ def prefix_path=(prefix)
72
+ raise "Not root!!" unless @parent.nil?
73
+ if prefix != @prefix_path
74
+ @prefix_path = prefix
75
+ if prefix and prefix !~ /\/$/
76
+ @prefix_path += AbsNode::path_separator
77
+ end
78
+ invalidate
79
+ end
80
+ end
81
+
82
+ #
83
+ # Return the root of this node
84
+ #
85
+ # @return AbsNode
86
+ #
87
+ def root
88
+ return @root if @root
89
+ @root = parent.nil? ? self : parent.root
90
+ end
91
+
92
+ #
93
+ # @return [String] path to this node
94
+ #
95
+ def path
96
+ return @path unless @path.nil?
97
+ if @parent.nil?
98
+ @path = @content
99
+ else
100
+ @path = @parent.path + AbsNode::path_separator + @content
101
+ end
102
+ @path
103
+ end
104
+
105
+ #
106
+ # @return [String] path to this node with prefix
107
+ #
108
+ def path_with_prefix
109
+ return @path_with_prefix if @path_with_prefix
110
+ if root.prefix_path
111
+ @path_with_prefix = root.prefix_path + path
112
+ else
113
+ @path_with_prefix = path
114
+ end
115
+ end
116
+
117
+ #
118
+ # @return [FixNum] depth of this node
119
+ #
120
+ def depth
121
+ return @depth unless @depth.nil?
122
+ @depth = @parent.nil? ? 1 : @parent.depth + 1
123
+ @depth
124
+ end
125
+
126
+ #
127
+ # Accept a node visitor
128
+ #
129
+ def accept(visitor)
130
+ not_implemented
131
+ end
132
+
133
+ def to_s
134
+ @content.to_s
135
+ end
136
+
137
+ protected
138
+
139
+ def parent=(parent)
140
+ @parent = parent
141
+ end
142
+
143
+ end
144
+ end
@@ -0,0 +1,44 @@
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 when deriving subclass
6
+ #
7
+ class BasicTreeNodeVisitor
8
+
9
+ #
10
+ # called on tree node at start of the visit i.e. we start to visit the subtree
11
+ #
12
+ def enter_node( tree_node )
13
+ end
14
+
15
+ #
16
+ # called when the tree node is not accessible or an exception is raise when the node is accessed
17
+ #
18
+ def cannot_enter_node( tree_node, error)
19
+ end
20
+
21
+ # alias :enter_tree_node :enter_node
22
+
23
+ #
24
+ # called on tree node at end of the visit i.e. oll subtree are visited
25
+ #
26
+ def exit_node( tree_node )
27
+ end
28
+
29
+ # alias :exit_tree_node :exit_node
30
+
31
+ #
32
+ # called when visit leaf node
33
+ #
34
+ def visit_leaf( leaf_node )
35
+ end
36
+
37
+ #
38
+ # called when the leaf node is not accessible or an exception is raise when the node is accessed
39
+ #
40
+ def cannot_visit_leaf( tree_node, error)
41
+ end
42
+ end
43
+
44
+ end
@@ -0,0 +1,121 @@
1
+ # -*- coding: utf-8 -*-
2
+ module TreeVisitor
3
+ #
4
+ #
5
+ #
6
+ class CliTree
7
+
8
+ def self.run
9
+ self.new.parse_args(ARGV)
10
+ end
11
+
12
+ def parse_args(argv)
13
+
14
+ options = {:verbose => true, :force => false, :algo => 'build-dir'}
15
+
16
+ opts = OptionParser.new
17
+ opts.banner = "Usage: tree.rb [options] [directory]"
18
+ opts.separator "list contents of directories in a tree-like format"
19
+ opts.separator "this is a almost :-) a clone of tree unix command written in ruby"
20
+ opts.separator "Code https://github.com/tokiro/treevisitor. Feedback to tokiro.oyama@gmail.com"
21
+
22
+ opts.separator ""
23
+ opts.separator "options: "
24
+
25
+ opts.on("-h", "--help", "Show this message") do
26
+ puts opts
27
+ return 0
28
+ end
29
+
30
+ opts.on("--version", "Show the version") do
31
+ puts "tree.rb version #{TreeVisitor::VERSION}"
32
+ return 0
33
+ end
34
+
35
+ opts.on("-a", "All file are listed") do |v|
36
+ options[:all_files] = true
37
+ end
38
+
39
+ opts.on("-d", "List directories only") do |v|
40
+ options[:only_directories] = true
41
+ end
42
+
43
+ opts.on("-v", "--[no-]verbose", "Run verbosely") do |v|
44
+ options[:verbose] = v
45
+ end
46
+
47
+ opts.on("-q", "--quiet", "quiet mode as --no-verbose") do |v|
48
+ options[:verbose] = false
49
+ end
50
+
51
+ algos = %w[build-dir print-dir json yaml]
52
+ algo_aliases = {"b" => "build-dir", "v" => "print-dir", "j" => "json", "y" => "yaml"}
53
+
54
+ algo_list = (algo_aliases.keys + algos).join(',')
55
+ opts.on("-f", "--format ALGO", algos, algo_aliases, "select an algo", " (#{algo_list})") do |algo|
56
+ options[:algo] = algo
57
+ end
58
+
59
+ begin
60
+ rest = opts.parse(argv)
61
+ rescue OptionParser::InvalidOption => e
62
+ $stderr.puts e.to_s
63
+ $stderr.puts "try -h for help"
64
+ return false
65
+ end
66
+
67
+ if rest.length < 1
68
+ dirname = Dir.pwd
69
+ else
70
+ dirname = rest[0]
71
+ end
72
+
73
+ dirname = File.expand_path(dirname)
74
+
75
+ dtw = DirTreeWalker.new(dirname)
76
+ unless options[:all_files]
77
+ dtw.ignore(/^\.[^.]+/) # ignore all file starting with "."
78
+ end
79
+
80
+ dtw.visit_file = !options[:only_directories]
81
+
82
+ case options[:algo]
83
+
84
+ when 'build-dir'
85
+ # TODO: capture CTRL^C
86
+ # http://ruby-doc.org/core-1.9.3/Kernel.html#method-i-trap
87
+ Kernel.trap('INT') { put "User interrupted exit"; exit; }
88
+
89
+ visitor = BuildDirTreeVisitor.new
90
+ dtw.run(visitor)
91
+
92
+ # colors also in windows
93
+ if $stdout.isatty
94
+ puts visitor.root.to_str('', true) #use color
95
+ else
96
+ puts visitor.root.to_str
97
+ end
98
+ puts
99
+ puts "#{visitor.nr_directories} directories, #{visitor.nr_files} files"
100
+
101
+ when 'print-dir'
102
+ visitor = PrintDirTreeVisitor.new
103
+ dtw.run(visitor)
104
+
105
+ when 'json'
106
+ root = dtw.run(DirectoryToHashVisitor.new(dirname)).root
107
+ puts JSON.pretty_generate(root)
108
+
109
+ when 'yaml'
110
+ root = dtw.run(DirectoryToHashVisitor.new(dirname)).root
111
+ puts root.to_yaml
112
+
113
+ else
114
+ puts "unknown algo #{options[:algo]} specified"
115
+ end
116
+
117
+ 0
118
+ end
119
+
120
+ end
121
+ end
@@ -0,0 +1,300 @@
1
+ # -*- coding: utf-8 -*-
2
+ module TreeVisitor
3
+
4
+ #
5
+ # Visit a file system directory
6
+ #
7
+ class DirTreeWalker
8
+
9
+ # Build a tree walker.
10
+ # Visit a director starting from dirname
11
+ # if dirname is missing, must be supplied when invoking run
12
+ #
13
+ # @yield [a, b, c] TreeNodeVisitor
14
+ # @yieldparam [optional, types, ...] argname description
15
+ # @yieldreturn [optional, types, ...] description
16
+ #
17
+ # @overload initialize(dirname)
18
+ # @param [String] dirname the root of the tree (top level directory)
19
+ #
20
+ # @overload initialize(dirname, options)
21
+ # @param [Hash] options
22
+ # @option options [String,Regex, Array<String,Regexp>] :ignore list of ignore pattern
23
+ # @option options [String] :ignore_dir
24
+ # @option options [String] :ignore_file
25
+ # @option options [String] :match
26
+ #
27
+ #
28
+ # @example Print the contents of tmp directory
29
+ # DirTreeWalker.new("/tmp") do
30
+ # on_visit_leaf_node { |pathname| puts pathname }
31
+ # end.run
32
+ #
33
+ def initialize(dirname = nil, options = nil)
34
+ #
35
+ # arg detection
36
+ #
37
+ if dirname and dirname.respond_to?(:key?)
38
+ options = dirname
39
+ dirname = nil
40
+ end
41
+
42
+ if dirname
43
+ @dirname = dirname
44
+ unless File.directory?(dirname)
45
+ raise "#{dirname} is not a directory!"
46
+ end
47
+ end
48
+
49
+ @visitor = nil
50
+
51
+ #
52
+ # pattern
53
+ #
54
+ @ignore_dir_patterns = []
55
+ @ignore_file_patterns = []
56
+ @match_file_patterns = []
57
+
58
+ if options and options[:ignore]
59
+ unless options[:ignore].respond_to?(:at)
60
+ options[:ignore] = [options[:ignore]]
61
+ end
62
+ options[:ignore].each { |p| ignore(p) }
63
+ end
64
+
65
+ if options and options[:ignore_dir]
66
+ unless options[:ignore_dir].respond_to?(:at)
67
+ options[:ignore_dir] = [options[:ignore_dir]]
68
+ end
69
+ options[:ignore_dir].each { |p| ignore_dir(p) }
70
+ end
71
+
72
+ if options and options[:ignore_file]
73
+ unless options[:ignore_file].respond_to?(:at)
74
+ options[:ignore_file] = [options[:ignore_file]]
75
+ end
76
+ options[:ignore_file].each { |p| ignore_file(p) }
77
+ end
78
+
79
+ if options and options[:match]
80
+ unless options[:match].respond_to?(:at)
81
+ options[:match] = [options[:match]]
82
+ end
83
+ options[:match].each { |p| match(p) }
84
+ end
85
+
86
+ #
87
+ # options
88
+ #
89
+ @visit_file = true
90
+ end
91
+
92
+ ##########################################################################
93
+ # Pattern
94
+
95
+ #
96
+ # Ignore a node (leaf/Tree) matching pattern
97
+ # @param [RegEx] pattern
98
+ #
99
+ def ignore(pattern)
100
+ @ignore_dir_patterns << pattern
101
+ @ignore_file_patterns << pattern
102
+ self
103
+ end
104
+
105
+ #
106
+ # Ignore a directory (Tree) matching pattern
107
+ # @param [RegEx] pattern
108
+ #
109
+ def ignore_dir(pattern)
110
+ @ignore_dir_patterns << pattern
111
+ self
112
+ end
113
+
114
+ #
115
+ # Ignore a file (Leaf) matching pattern
116
+ # @param [RegEx] pattern
117
+ #
118
+ def ignore_file(pattern)
119
+ @ignore_file_patterns << pattern
120
+ self
121
+ end
122
+
123
+ #
124
+ # Just the opposite of ignore
125
+ # directory/file matching pattern will be visited
126
+ #
127
+ # @param [RegEx] pattern
128
+ #
129
+ def match(pattern)
130
+ @match_file_patterns << pattern
131
+ self
132
+ end
133
+
134
+ ##########################################################################
135
+ # Options
136
+
137
+ #
138
+ # it is true to visit file
139
+ #
140
+ attr_accessor :visit_file
141
+
142
+ ##########################################################################
143
+
144
+ #
145
+ # Test directory ignore pattern
146
+ #
147
+ # @param [String] directory name
148
+ # @return [boolean] if dirname match almost one pattern
149
+ #
150
+ def ignore_dir?(dirname)
151
+ _include?(@ignore_dir_patterns, File.basename(dirname))
152
+ end
153
+
154
+ #
155
+ # Test file ignore pattern
156
+ #
157
+ # @param [String] file name
158
+ # @return [boolean] if filename match almost one pattern
159
+ #
160
+ def ignore_file?(filename)
161
+ _include?(@ignore_file_patterns, File.basename(filename))
162
+ end
163
+
164
+ #
165
+ # Test common ignore pattern
166
+ #
167
+ # @param [String] file name
168
+ # @return [boolean] if filename match almost one pattern
169
+ #
170
+ def match?(filename)
171
+ return true if @match_file_patterns.empty?
172
+ _include?(@match_file_patterns, File.basename(filename))
173
+ end
174
+
175
+ #
176
+ # Run the visitor through the directory tree
177
+ #
178
+ # @overload run
179
+ #
180
+ # @overload run(dirname)
181
+ # @param [String] dirname
182
+ # @yield define TreeNodeVisitor
183
+ #
184
+ # @overload run(tree_node_visitor)
185
+ # @param [TreeNodeVisitor]
186
+ # @yield define TreeNodeVisitor
187
+ #
188
+ # @overload run(dirname, tree_node_visitor)
189
+ # @param [String] dirname
190
+ # @param [TreeNodeVisitor]
191
+ # @yield define TreeNodeVisitor
192
+ #
193
+ # @return [TreeNodeVisitor] the visitor
194
+ #
195
+ # @example Print the contents of tmp directory
196
+ # w = DirTreeWalker.new
197
+ # w.run("/tmp") do
198
+ # on_visit_leaf_node { |pathname| puts pathname }
199
+ # end.run
200
+ #
201
+ def run(dirname = nil, tree_node_visitor = nil, &block)
202
+
203
+ #
204
+ # args detection
205
+ #
206
+ if dirname and dirname.respond_to?(:enter_node)
207
+ tree_node_visitor = dirname
208
+ dirname = nil
209
+ end
210
+
211
+ #
212
+ # check dirname
213
+ #
214
+ if @dirname.nil? and dirname.nil?
215
+ raise "missing starting directory"
216
+ end
217
+ @dirname = dirname if dirname
218
+
219
+ #
220
+ # check visitor
221
+ #
222
+ if tree_node_visitor and block
223
+ raise "cannot use block and parameter together"
224
+ end
225
+
226
+ if tree_node_visitor
227
+ @visitor = tree_node_visitor
228
+ end
229
+
230
+ if block
231
+ @visitor = TreeNodeVisitor.new(&block)
232
+ end
233
+
234
+ unless @visitor
235
+ raise "missing visitor"
236
+ end
237
+
238
+ #
239
+ # finally starts to process
240
+ #
241
+ process_directory(File.expand_path(@dirname))
242
+ @visitor
243
+ end
244
+
245
+ private
246
+
247
+ def _include?(patterns, basename)
248
+ # return false if the patters.empty?
249
+ patterns.find { |pattern|
250
+ if pattern.kind_of? Regexp
251
+ pattern.match(basename)
252
+ else
253
+ basename == pattern
254
+ end
255
+ }
256
+ end
257
+
258
+ #
259
+ # recurse on other directories
260
+ #
261
+ def process_directory(dirname)
262
+ # return if ignore_dir?( dirname )
263
+
264
+ begin
265
+ entries = Dir.entries(dirname).sort
266
+ rescue Errno::EACCES => e
267
+ $stderr.puts e
268
+ @visitor.cannot_enter_node(dirname, e)
269
+ return
270
+ rescue Errno::EPERM => e
271
+ $stderr.puts e
272
+ @visitor.cannot_enter_node(dirname, e)
273
+ return
274
+ end
275
+
276
+
277
+ @visitor.enter_node(dirname)
278
+ entries.each { |basename|
279
+ begin
280
+ next if basename == "." or basename == ".." # ignore always "." and ".."
281
+ pathname = File.join(dirname, basename)
282
+
283
+ if File.directory?(pathname)
284
+ process_directory(pathname) unless ignore_dir?(basename)
285
+ else
286
+ if !!@visit_file && match?(basename) && !ignore_file?(basename)
287
+ @visitor.visit_leaf(pathname)
288
+ end
289
+ end
290
+ rescue Errno::EACCES => e
291
+ $stderr.puts e
292
+ rescue Errno::EPERM
293
+ $stderr.puts e
294
+ end
295
+ }
296
+
297
+ @visitor.exit_node(dirname)
298
+ end
299
+ end
300
+ 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( self )
29
+ visitor
30
+ end
31
+
32
+ end
33
+ end