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.
- data/.gemtest +0 -0
- data/LICENSE.txt +20 -0
- data/README.md +160 -0
- data/Rakefile +12 -0
- data/bin/tree.rb +9 -0
- data/examples/directory_walker/directory_without_subdirectory.rb +37 -0
- data/examples/directory_walker/find_files.rb +18 -0
- data/examples/directory_walker/print_files.rb +12 -0
- data/examples/protovis/directory_to_json_visitor.rb +15 -0
- data/examples/protovis/index.html +87 -0
- data/examples/protovis/protovis-r3.2.js +277 -0
- data/examples/protovis/treevisitor.js +33 -0
- data/examples/protovis/treevisitor.png +0 -0
- data/lib/tree_visitor.rb +2 -0
- data/lib/treevisitor/abs_node.rb +144 -0
- data/lib/treevisitor/basic_tree_node_visitor.rb +44 -0
- data/lib/treevisitor/cli/cli_tree.rb +121 -0
- data/lib/treevisitor/directory_walker.rb +300 -0
- data/lib/treevisitor/leaf_node.rb +33 -0
- data/lib/treevisitor/tree_node.rb +296 -0
- data/lib/treevisitor/tree_node_visitor.rb +120 -0
- data/lib/treevisitor/util/dir_processor.rb +46 -0
- data/lib/treevisitor/version.rb +4 -0
- data/lib/treevisitor/visitors/block_tree_node_visitor.rb +21 -0
- data/lib/treevisitor/visitors/build_dir_tree_visitor.rb +57 -0
- data/lib/treevisitor/visitors/callback_tree_node_visitor2.rb +48 -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/directory_to_hash_visitor.rb +33 -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.rb +40 -0
- data/lib/treevisitor_cli.rb +7 -0
- data/spec/fixtures/test_dir_1/.dir_with_dot/dummy.txt +0 -0
- data/spec/fixtures/test_dir_1/dir.1/dir.1.2/file.1.2.1 +0 -0
- data/spec/fixtures/test_dir_1/dir.1/file.1.1 +0 -0
- data/spec/fixtures/test_dir_1/dir.2/file.2.1 +0 -0
- data/spec/fixtures/test_dir_2/[Dsube]/sub/.gitkeep +0 -0
- data/spec/spec_helper.rb +27 -0
- data/spec/treevisitor/cli/cli_tree_spec.rb +69 -0
- data/spec/treevisitor/directory_walker_spec.rb +99 -0
- data/spec/treevisitor/tree_dsl_spec.rb +57 -0
- data/spec/treevisitor/tree_dsl_with_derived_class1_spec.rb +53 -0
- data/spec/treevisitor/tree_dsl_with_derived_class_spec.rb +51 -0
- data/spec/treevisitor/tree_node_paths_spec.rb +70 -0
- data/spec/treevisitor/tree_node_spec.rb +135 -0
- data/spec/treevisitor/tree_node_visitor_delegate_spec.rb +35 -0
- data/spec/treevisitor/tree_node_visitor_dsl_spec.rb +66 -0
- data/spec/treevisitor/util/dir_processor_spec.rb +13 -0
- data/spec/treevisitor/visitors/block_tree_node_visitor_spec.rb +25 -0
- data/spec/treevisitor/visitors/callback_tree_node_visitor2_spec.rb +38 -0
- data/spec/treevisitor/visitors/depth_tree_node_visitor_spec.rb +28 -0
- data/spec/treevisitor/visitors/tree_node_visitors_spec.rb +27 -0
- data/tasks/rspec.rake +34 -0
- data/tasks/yard.rake +36 -0
- data/tree.rb.gemspec +70 -0
- 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
|