file-visitor 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Gemfile ADDED
@@ -0,0 +1,12 @@
1
+ source "http://rubygems.org"
2
+ # Add dependencies required to use your gem here.
3
+ # Example:
4
+ # gem "activesupport", ">= 2.3.5"
5
+
6
+ # Add dependencies to develop your gem here.
7
+ # Include everything needed to run rake, tests, features, etc.
8
+ group :development do
9
+ gem "rspec", "~> 2.8.0"
10
+ gem "rdoc", "~> 3.12"
11
+ gem "jeweler", "~> 1.8.4"
12
+ end
@@ -0,0 +1,30 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ diff-lcs (1.1.3)
5
+ git (1.2.5)
6
+ jeweler (1.8.4)
7
+ bundler (~> 1.0)
8
+ git (>= 1.2.5)
9
+ rake
10
+ rdoc
11
+ json (1.7.5)
12
+ rake (0.9.2.2)
13
+ rdoc (3.12)
14
+ json (~> 1.4)
15
+ rspec (2.8.0)
16
+ rspec-core (~> 2.8.0)
17
+ rspec-expectations (~> 2.8.0)
18
+ rspec-mocks (~> 2.8.0)
19
+ rspec-core (2.8.0)
20
+ rspec-expectations (2.8.0)
21
+ diff-lcs (~> 1.1.2)
22
+ rspec-mocks (2.8.0)
23
+
24
+ PLATFORMS
25
+ ruby
26
+
27
+ DEPENDENCIES
28
+ jeweler (~> 1.8.4)
29
+ rdoc (~> 3.12)
30
+ rspec (~> 2.8.0)
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2013 bonar
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,19 @@
1
+ = file-visitor
2
+
3
+ Description goes here.
4
+
5
+ == Contributing to file-visitor
6
+
7
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
8
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it.
9
+ * Fork the project.
10
+ * Start a feature/bugfix branch.
11
+ * Commit and push until you are happy with your contribution.
12
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
13
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
14
+
15
+ == Copyright
16
+
17
+ Copyright (c) 2013 bonar. See LICENSE.txt for
18
+ further details.
19
+
@@ -0,0 +1,49 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+ begin
6
+ Bundler.setup(:default, :development)
7
+ rescue Bundler::BundlerError => e
8
+ $stderr.puts e.message
9
+ $stderr.puts "Run `bundle install` to install missing gems"
10
+ exit e.status_code
11
+ end
12
+ require 'rake'
13
+
14
+ require 'jeweler'
15
+ Jeweler::Tasks.new do |gem|
16
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
17
+ gem.name = "file-visitor"
18
+ gem.homepage = "http://github.com/bonar/file-visitor"
19
+ gem.license = "MIT"
20
+ gem.summary = %Q{file path collecting utility}
21
+ gem.description = %Q{you can collect file paths by mtime/name/ext/block filters.}
22
+ gem.email = "bonar@me.com"
23
+ gem.authors = ["bonar"]
24
+ # dependencies defined in Gemfile
25
+ end
26
+ Jeweler::RubygemsDotOrgTasks.new
27
+
28
+ require 'rspec/core'
29
+ require 'rspec/core/rake_task'
30
+ RSpec::Core::RakeTask.new(:spec) do |spec|
31
+ spec.pattern = FileList['spec/**/*_spec.rb']
32
+ end
33
+
34
+ RSpec::Core::RakeTask.new(:rcov) do |spec|
35
+ spec.pattern = 'spec/**/*_spec.rb'
36
+ spec.rcov = true
37
+ end
38
+
39
+ task :default => :spec
40
+
41
+ require 'rdoc/task'
42
+ Rake::RDocTask.new do |rdoc|
43
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
44
+
45
+ rdoc.rdoc_dir = 'rdoc'
46
+ rdoc.title = "file-visitor #{version}"
47
+ rdoc.rdoc_files.include('README*')
48
+ rdoc.rdoc_files.include('lib/**/*.rb')
49
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,128 @@
1
+
2
+ require 'file/visitor/filter'
3
+ require 'file/visitor/filter_dispatcher'
4
+ require 'file/visitor/filter/proc'
5
+
6
+ class File
7
+ class Visitor
8
+
9
+ attr_reader :filters
10
+ attr_accessor :visit_dot_dir
11
+
12
+ FILTER_NS_BASE = File::Visitor::Filter
13
+
14
+ def initialize
15
+ @filters = []
16
+
17
+ @visit_dot_dir = false
18
+ end
19
+
20
+ def visit(dir, &handler)
21
+ visit_with_mode(dir, :file, &handler)
22
+ end
23
+
24
+ def visit_dir(dir, &handler)
25
+ visit_with_mode(dir, :dir, &handler)
26
+ end
27
+
28
+ def file_list(dir)
29
+ files = []
30
+ visit(dir) { |path| files << path }
31
+ files
32
+ end
33
+
34
+ def dir_list(dir)
35
+ dirs = []
36
+ visit_dir(dir) { |path| dirs << path }
37
+ dirs
38
+ end
39
+
40
+ # 3 ways to register filter
41
+ #
42
+ # 1. built-in filter
43
+ # filter.add_filter(:mtime, :passed, 30, :days)
44
+ #
45
+ # 2. custom filter
46
+ # filter.add_filter(my_filter)
47
+ # (my_filter must implements match?(path) method)
48
+ #
49
+ # 3. block filter
50
+ # filter.add_filter do |path|
51
+ # # filter operations
52
+ # end
53
+ #
54
+ def add_filter(*args, &block)
55
+ # 3. block filter
56
+ if block_given?
57
+ filter = File::Visitor::Filter::Proc.new(block)
58
+ @filters.push(filter)
59
+ return true
60
+ end
61
+
62
+ # 2. custom filter
63
+ if (1 == args.size)
64
+ custom_filter = args.shift
65
+ unless (custom_filter.respond_to?(:match?))
66
+ raise ArgumentError,
67
+ "custom_filter must implement match?()"
68
+ end
69
+ @filters.push(custom_filter)
70
+ return true
71
+ end
72
+
73
+ # 1. built-in filter
74
+ filter_class = File::Visitor::FilterDispatcher.dispatch(args.shift)
75
+ @filters.push(filter_class.new(*args))
76
+ true
77
+ end
78
+
79
+ def target?(path)
80
+ # all the paths are target when no filters given
81
+ return true unless @filters
82
+
83
+ @filters.each do |filter|
84
+ return false unless filter.match?(path)
85
+ end
86
+ true
87
+ end
88
+
89
+ private
90
+
91
+ def dot_dir?(path)
92
+ basename = File.basename(path)
93
+ basename == '.' || basename == '..'
94
+ end
95
+
96
+ def assert_directory(dir)
97
+ unless dir.is_a?(String) && File.directory?(dir)
98
+ raise ArgumentError, "#{dir} is not directory"
99
+ end
100
+ end
101
+
102
+ # dir: target directory
103
+ # mode:
104
+ # file - visit all files
105
+ # dir - visit directory only
106
+ # handler: proc to call
107
+
108
+ def visit_with_mode(dir, mode, &handler)
109
+ assert_directory(dir)
110
+
111
+ Dir.entries(dir).each do |entry|
112
+ next if (dot_dir?(entry) && !@visit_dot_dir)
113
+
114
+ abs_path = File.join(dir, entry)
115
+ if File.directory?(abs_path)
116
+ mode == :dir && handler.call(abs_path)
117
+ visit_with_mode(abs_path, mode, &handler)
118
+ else
119
+ if mode == :file && target?(abs_path)
120
+ handler.call(abs_path)
121
+ end
122
+ end
123
+ end
124
+ end
125
+
126
+ end
127
+ end
128
+
@@ -0,0 +1,10 @@
1
+
2
+ # create namespace for filters
3
+
4
+ class File
5
+ class Visitor
6
+ class Filter
7
+ end
8
+ end
9
+ end
10
+
@@ -0,0 +1,27 @@
1
+
2
+ require 'file/visitor/filter'
3
+
4
+ class File::Visitor::Filter::Ext
5
+
6
+ def initialize(extstr)
7
+ if extstr.nil? || !(extstr.is_a?(String) || extstr.is_a?(Symbol))
8
+ raise ArgumentError, "ext must be Sting/Symbol"
9
+ end
10
+ extstr = extstr.to_s
11
+ unless extstr =~ /\A\./
12
+ extstr = ".#{extstr}"
13
+ end
14
+ @ext = extstr
15
+ end
16
+
17
+ def match?(path)
18
+ ext = File.extname(path)
19
+ ext == @ext
20
+ end
21
+
22
+ def to_s
23
+ "%s[%s]" % [self.class.name, @ext.to_s]
24
+ end
25
+
26
+ end
27
+
@@ -0,0 +1,59 @@
1
+
2
+ require 'file/visitor/filter'
3
+ require 'file/visitor/time_utils'
4
+
5
+ class File::Visitor::Filter::Mtime
6
+ include File::Visitor::TimeUtils
7
+
8
+ PASSED_COMPARATOR = {
9
+ :passed => :is_older_than,
10
+ :passed= => :is_older_than=,
11
+ }
12
+
13
+ # Two ways to initialize filter:
14
+ # 1) File::Visitor::Filter::Mtime.new(
15
+ # :younger_than, "2012-01-01")
16
+ # 2) File::Visitor::Filter::Mtime.new(
17
+ # :passed, 30, :days)
18
+
19
+ def initialize(*args)
20
+ comparator = args.shift
21
+ target_time = nil
22
+
23
+ # case 1)
24
+ if PASSED_COMPARATOR.keys.include?(comparator)
25
+ count = args.shift
26
+ unit = args.shift
27
+ target_time = Time.now -
28
+ unitexp2sec(count, unit)
29
+ comparator = PASSED_COMPARATOR[comparator]
30
+ # case 2)
31
+ else
32
+ unless COMPARATOR.include?(comparator)
33
+ raise ArgumentError,
34
+ "comparator must be in " +
35
+ COMPARATOR.join(", ")
36
+ end
37
+ target_time = args.shift
38
+ if target_time.is_a?(String)
39
+ target_time = Time.parse(target_time)
40
+ end
41
+ unless target_time.is_a?(Time)
42
+ raise ArgumentError, "time must be a Time"
43
+ end
44
+ end
45
+
46
+ @comparator = comparator
47
+ @target_time = target_time
48
+ end
49
+
50
+ def match?(path)
51
+ compare_time(
52
+ File.mtime(path),
53
+ @comparator,
54
+ @target_time
55
+ )
56
+ end
57
+
58
+ end
59
+
@@ -0,0 +1,25 @@
1
+
2
+ require 'file/visitor/filter'
3
+
4
+ class File::Visitor::Filter::Name
5
+
6
+ def initialize(exp)
7
+ unless exp.is_a?(String) || exp.is_a?(Regexp)
8
+ raise ArgumentError, "expression must be String or Regexp"
9
+ end
10
+ @exp = exp
11
+ end
12
+
13
+ def match?(path)
14
+ filename = File.basename(path)
15
+ return @exp == filename if @exp.is_a?(String)
16
+ return filename =~ @exp if @exp.is_a?(Regexp)
17
+ raise RuntimeError, "unexpected exp type: #{@exp.class}"
18
+ end
19
+
20
+ def to_s
21
+ "%s[%s:%s]" % [self.class.name, @exp.class.name, @exp.to_s]
22
+ end
23
+
24
+ end
25
+
@@ -0,0 +1,23 @@
1
+
2
+ require 'file/visitor/filter'
3
+
4
+ class File::Visitor::Filter::Proc
5
+
6
+ def initialize(custom_proc)
7
+ unless custom_proc.is_a?(Proc)
8
+ raise ArgumentError, "Proc instance required"
9
+ end
10
+ @proc = custom_proc
11
+ end
12
+
13
+ def match?(path)
14
+ !!@proc.call(path)
15
+ end
16
+
17
+ def to_s
18
+ "%s[%s]" % [self.class.name, @proc.object_id]
19
+ end
20
+
21
+ end
22
+
23
+
@@ -0,0 +1,22 @@
1
+
2
+ require 'file/visitor/filter'
3
+ require 'file/visitor/filter/name'
4
+ require 'file/visitor/filter/ext'
5
+ require 'file/visitor/filter/mtime'
6
+
7
+ class File::Visitor::FilterDispatcher
8
+
9
+ def self.dispatch(filter_name)
10
+ case filter_name
11
+ when :name, :filename
12
+ return File::Visitor::Filter::Name
13
+ when :ext, :extension, :filetype
14
+ return File::Visitor::Filter::Ext
15
+ when :mtime, :modified_time
16
+ return File::Visitor::Filter::Mtime
17
+ end
18
+ raise ArgumentError, "invalid filter name: #{filter_name}"
19
+ end
20
+
21
+ end
22
+