madpilot-fakefs 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +20 -0
- data/README.markdown +68 -0
- data/Rakefile +40 -0
- data/lib/fakefs/base.rb +37 -0
- data/lib/fakefs/dir.rb +114 -0
- data/lib/fakefs/fake/dir.rb +37 -0
- data/lib/fakefs/fake/file.rb +29 -0
- data/lib/fakefs/fake/symlink.rb +26 -0
- data/lib/fakefs/file.rb +126 -0
- data/lib/fakefs/file_system.rb +118 -0
- data/lib/fakefs/fileutils.rb +108 -0
- data/lib/fakefs/safe.rb +11 -0
- data/lib/fakefs/version.rb +9 -0
- data/lib/fakefs.rb +3 -0
- data/test/fakefs_test.rb +804 -0
- data/test/safe_test.rb +20 -0
- data/test/verify.rb +27 -0
- metadata +72 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Chris Wanstrath
|
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.
|
data/README.markdown
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
FakeFS
|
2
|
+
======
|
3
|
+
|
4
|
+
Mocha is great. But when your library is all about manipulating the
|
5
|
+
filesystem, you really want to test the behavior and not the implementation.
|
6
|
+
|
7
|
+
If you're mocking and stubbing every call to FileUtils or File, you're
|
8
|
+
tightly coupling your tests with the implementation.
|
9
|
+
|
10
|
+
def test_creates_directory
|
11
|
+
FileUtils.expects(:mkdir).with("directory").once
|
12
|
+
Library.add "directory"
|
13
|
+
end
|
14
|
+
|
15
|
+
The above test will break if we decide to use `mkdir_p` in our code. Refactoring
|
16
|
+
code shouldn't necessitate refactoring tests.
|
17
|
+
|
18
|
+
With FakeFS:
|
19
|
+
|
20
|
+
def test_creates_directory
|
21
|
+
Library.add "directory"
|
22
|
+
assert File.directory?("directory")
|
23
|
+
end
|
24
|
+
|
25
|
+
Woot.
|
26
|
+
|
27
|
+
|
28
|
+
Usage
|
29
|
+
-----
|
30
|
+
|
31
|
+
require 'fakefs'
|
32
|
+
|
33
|
+
# That's it.
|
34
|
+
|
35
|
+
|
36
|
+
Don't Fake the FS Immediately
|
37
|
+
-----------------------------
|
38
|
+
|
39
|
+
require 'fakefs/safe'
|
40
|
+
|
41
|
+
FakeFS.activate!
|
42
|
+
# your code
|
43
|
+
FakeFS.deactivate!
|
44
|
+
|
45
|
+
# or
|
46
|
+
FakeFS do
|
47
|
+
# your code
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
How is this different than MockFS?
|
52
|
+
----------------------------------
|
53
|
+
|
54
|
+
FakeFS provides a test suite and works with symlinks. It's also strictly a
|
55
|
+
test-time dependency: your actual library does not need to use or know about
|
56
|
+
FakeFS.
|
57
|
+
|
58
|
+
|
59
|
+
Speed?
|
60
|
+
------
|
61
|
+
http://gist.github.com/156091
|
62
|
+
|
63
|
+
|
64
|
+
Authors
|
65
|
+
-------
|
66
|
+
|
67
|
+
* Chris Wanstrath [chris@ozmm.org]
|
68
|
+
* Pat Nakajima [http://github.com/nakajima]
|
data/Rakefile
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
task :default do
|
2
|
+
Dir['test/*_test.rb'].each { |file| require file }
|
3
|
+
end
|
4
|
+
|
5
|
+
begin
|
6
|
+
require 'jeweler'
|
7
|
+
|
8
|
+
# We're not putting VERSION or VERSION.yml in the root,
|
9
|
+
# so we have to help Jeweler find our version.
|
10
|
+
$LOAD_PATH.unshift File.dirname(__FILE__) + '/lib'
|
11
|
+
require 'fakefs/version'
|
12
|
+
|
13
|
+
FakeFS::Version.instance_eval do
|
14
|
+
def refresh
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class Jeweler
|
19
|
+
def version_helper
|
20
|
+
FakeFS::Version
|
21
|
+
end
|
22
|
+
|
23
|
+
def version_exists?
|
24
|
+
true
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
Jeweler::Tasks.new do |gemspec|
|
29
|
+
gemspec.name = "fakefs"
|
30
|
+
gemspec.summary = "A fake filesystem. Use it in your tests."
|
31
|
+
gemspec.email = "chris@ozmm.org"
|
32
|
+
gemspec.homepage = "http://github.com/defunkt/fakefs"
|
33
|
+
gemspec.description = "A fake filesystem. Use it in your tests."
|
34
|
+
gemspec.authors = ["Chris Wanstrath"]
|
35
|
+
gemspec.has_rdoc = false
|
36
|
+
end
|
37
|
+
rescue LoadError
|
38
|
+
puts "Jeweler not available."
|
39
|
+
puts "Install it with: gem install technicalpickles-jeweler"
|
40
|
+
end
|
data/lib/fakefs/base.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
RealFile = File
|
2
|
+
RealFileUtils = FileUtils
|
3
|
+
RealDir = Dir
|
4
|
+
RealFileUtils::Dir = RealDir
|
5
|
+
RealFileUtils::File = RealFile
|
6
|
+
|
7
|
+
module FakeFS
|
8
|
+
def self.activate!
|
9
|
+
Object.class_eval do
|
10
|
+
remove_const(:Dir)
|
11
|
+
remove_const(:File)
|
12
|
+
remove_const(:FileUtils)
|
13
|
+
const_set(:Dir, FakeFS::Dir)
|
14
|
+
const_set(:File, FakeFS::File)
|
15
|
+
const_set(:FileUtils, FakeFS::FileUtils)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.deactivate!
|
20
|
+
Object.class_eval do
|
21
|
+
remove_const(:Dir)
|
22
|
+
remove_const(:File)
|
23
|
+
remove_const(:FileUtils)
|
24
|
+
const_set(:Dir, RealDir)
|
25
|
+
const_set(:File, RealFile)
|
26
|
+
const_set(:FileUtils, RealFileUtils)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def FakeFS
|
32
|
+
return ::FakeFS unless block_given?
|
33
|
+
::FakeFS.activate!
|
34
|
+
yield
|
35
|
+
::FakeFS.deactivate!
|
36
|
+
end
|
37
|
+
|
data/lib/fakefs/dir.rb
ADDED
@@ -0,0 +1,114 @@
|
|
1
|
+
module FakeFS
|
2
|
+
class Dir
|
3
|
+
include Enumerable
|
4
|
+
|
5
|
+
def initialize(string)
|
6
|
+
raise Errno::ENOENT, string unless FileSystem.find(string)
|
7
|
+
@path = string
|
8
|
+
@open = true
|
9
|
+
@pointer = 0
|
10
|
+
@contents = [ '.', '..', ] + FileSystem.find(@path).values
|
11
|
+
end
|
12
|
+
|
13
|
+
def close
|
14
|
+
@open = false
|
15
|
+
@pointer = nil
|
16
|
+
@contents = nil
|
17
|
+
nil
|
18
|
+
end
|
19
|
+
|
20
|
+
def each(&block)
|
21
|
+
while f = read
|
22
|
+
yield f
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def path
|
27
|
+
@path
|
28
|
+
end
|
29
|
+
|
30
|
+
def pos
|
31
|
+
@pointer
|
32
|
+
end
|
33
|
+
|
34
|
+
def pos=(integer)
|
35
|
+
@pointer = integer
|
36
|
+
end
|
37
|
+
|
38
|
+
def read
|
39
|
+
raise IOError, "closed directory" if @pointer == nil
|
40
|
+
n = @contents[@pointer]
|
41
|
+
@pointer += 1
|
42
|
+
n.to_s if n
|
43
|
+
end
|
44
|
+
|
45
|
+
def rewind
|
46
|
+
@pointer = 0
|
47
|
+
end
|
48
|
+
|
49
|
+
def seek(integer)
|
50
|
+
raise IOError, "closed directory" if @pointer == nil
|
51
|
+
@pointer = integer
|
52
|
+
@contents[integer]
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.[](pattern)
|
56
|
+
glob(pattern)
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.chdir(dir, &blk)
|
60
|
+
FileSystem.chdir(dir, &blk)
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.chroot(string)
|
64
|
+
# Not implemented yet
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.delete(string)
|
68
|
+
raise SystemCallError, "No such file or directory - #{string}" unless FileSystem.find(string).values.empty?
|
69
|
+
FileSystem.delete(string)
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.entries(dirname)
|
73
|
+
raise SystemCallError, dirname unless FileSystem.find(dirname)
|
74
|
+
Dir.new(dirname).map { |file| file }
|
75
|
+
end
|
76
|
+
|
77
|
+
def self.foreach(dirname, &block)
|
78
|
+
Dir.open(dirname) { |file| yield file }
|
79
|
+
end
|
80
|
+
|
81
|
+
def self.glob(pattern)
|
82
|
+
[FileSystem.find(pattern) || []].flatten.map{|e| e.to_s}.sort
|
83
|
+
end
|
84
|
+
|
85
|
+
def self.mkdir(string, integer = 0)
|
86
|
+
parent = string.split('/')
|
87
|
+
parent.pop
|
88
|
+
raise Errno::ENOENT, "No such file or directory - #{string}" unless parent.join == "" || FileSystem.find(parent.join('/'))
|
89
|
+
FileUtils.mkdir_p(string)
|
90
|
+
end
|
91
|
+
|
92
|
+
def self.open(string, &block)
|
93
|
+
if block_given?
|
94
|
+
Dir.new(string).each { |file| yield(file) }
|
95
|
+
else
|
96
|
+
Dir.new(string)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def tmpdir
|
101
|
+
'/tmp'
|
102
|
+
end
|
103
|
+
|
104
|
+
def self.pwd
|
105
|
+
FileSystem.current_dir.to_s
|
106
|
+
end
|
107
|
+
|
108
|
+
class << self
|
109
|
+
alias_method :getwd, :pwd
|
110
|
+
alias_method :rmdir, :delete
|
111
|
+
alias_method :unline, :delete
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module FakeFS
|
2
|
+
class FakeDir < Hash
|
3
|
+
attr_accessor :name, :parent
|
4
|
+
|
5
|
+
def initialize(name = nil, parent = nil)
|
6
|
+
@name = name
|
7
|
+
@parent = parent
|
8
|
+
end
|
9
|
+
|
10
|
+
def entry
|
11
|
+
self
|
12
|
+
end
|
13
|
+
|
14
|
+
def inspect
|
15
|
+
"(FakeDir name:#{name.inspect} parent:#{parent.to_s.inspect} size:#{size})"
|
16
|
+
end
|
17
|
+
|
18
|
+
def clone(parent = nil)
|
19
|
+
clone = Marshal.load(Marshal.dump(self))
|
20
|
+
clone.each do |key, value|
|
21
|
+
value.parent = clone
|
22
|
+
end
|
23
|
+
clone.parent = parent if parent
|
24
|
+
clone
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_s
|
28
|
+
if parent && parent.to_s != '.'
|
29
|
+
File.join(parent.to_s, name)
|
30
|
+
elsif parent && parent.to_s == '.'
|
31
|
+
"#{File::PATH_SEPARATOR}#{name}"
|
32
|
+
else
|
33
|
+
name
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module FakeFS
|
2
|
+
class FakeFile
|
3
|
+
attr_accessor :name, :parent, :content
|
4
|
+
|
5
|
+
def initialize(name = nil, parent = nil)
|
6
|
+
@name = name
|
7
|
+
@parent = parent
|
8
|
+
@content = ''
|
9
|
+
end
|
10
|
+
|
11
|
+
def clone(parent = nil)
|
12
|
+
clone = super()
|
13
|
+
clone.parent = parent if parent
|
14
|
+
clone
|
15
|
+
end
|
16
|
+
|
17
|
+
def entry
|
18
|
+
self
|
19
|
+
end
|
20
|
+
|
21
|
+
def inspect
|
22
|
+
"(FakeFile name:#{name.inspect} parent:#{parent.to_s.inspect} size:#{content.size})"
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_s
|
26
|
+
File.join(parent.to_s, name)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module FakeFS
|
2
|
+
class FakeSymlink
|
3
|
+
attr_accessor :name, :target
|
4
|
+
alias_method :to_s, :name
|
5
|
+
|
6
|
+
def initialize(target)
|
7
|
+
@target = target
|
8
|
+
end
|
9
|
+
|
10
|
+
def inspect
|
11
|
+
"symlink(#{target.split('/').last})"
|
12
|
+
end
|
13
|
+
|
14
|
+
def entry
|
15
|
+
FileSystem.find(target)
|
16
|
+
end
|
17
|
+
|
18
|
+
def method_missing(*args, &block)
|
19
|
+
entry.send(*args, &block)
|
20
|
+
end
|
21
|
+
|
22
|
+
def respond_to?(method)
|
23
|
+
entry.respond_to?(method)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/lib/fakefs/file.rb
ADDED
@@ -0,0 +1,126 @@
|
|
1
|
+
module FakeFS
|
2
|
+
class File
|
3
|
+
PATH_SEPARATOR = '/'
|
4
|
+
|
5
|
+
def self.join(*parts)
|
6
|
+
parts * PATH_SEPARATOR
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.exist?(path)
|
10
|
+
!!FileSystem.find(path)
|
11
|
+
end
|
12
|
+
|
13
|
+
class << self
|
14
|
+
alias_method :exists?, :exist?
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.const_missing(name)
|
18
|
+
RealFile.const_get(name)
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.directory?(path)
|
22
|
+
if path.respond_to? :entry
|
23
|
+
path.entry.is_a? FakeDir
|
24
|
+
else
|
25
|
+
result = FileSystem.find(path)
|
26
|
+
result ? result.entry.is_a?(FakeDir) : false
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.symlink?(path)
|
31
|
+
if path.respond_to? :entry
|
32
|
+
path.is_a? FakeSymlink
|
33
|
+
else
|
34
|
+
FileSystem.find(path).is_a? FakeSymlink
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.file?(path)
|
39
|
+
if path.respond_to? :entry
|
40
|
+
path.entry.is_a? FakeFile
|
41
|
+
else
|
42
|
+
result = FileSystem.find(path)
|
43
|
+
result ? result.entry.is_a?(FakeFile) : false
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.expand_path(*args)
|
48
|
+
RealFile.expand_path(*args)
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.basename(*args)
|
52
|
+
RealFile.basename(*args)
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.dirname(path)
|
56
|
+
RealFile.dirname(path)
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.readlink(path)
|
60
|
+
symlink = FileSystem.find(path)
|
61
|
+
FileSystem.find(symlink.target).to_s
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.open(path, mode='r')
|
65
|
+
if block_given?
|
66
|
+
yield new(path, mode)
|
67
|
+
else
|
68
|
+
new(path, mode)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.read(path)
|
73
|
+
file = new(path)
|
74
|
+
if file.exists?
|
75
|
+
file.read
|
76
|
+
else
|
77
|
+
raise Errno::ENOENT
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def self.readlines(path)
|
82
|
+
read(path).split("\n")
|
83
|
+
end
|
84
|
+
|
85
|
+
attr_reader :path
|
86
|
+
def initialize(path, mode = nil)
|
87
|
+
@path = path
|
88
|
+
@mode = mode
|
89
|
+
@file = FileSystem.find(path)
|
90
|
+
@open = true
|
91
|
+
end
|
92
|
+
|
93
|
+
def close
|
94
|
+
@open = false
|
95
|
+
end
|
96
|
+
|
97
|
+
def read
|
98
|
+
raise IOError.new('closed stream') unless @open
|
99
|
+
@file.content
|
100
|
+
end
|
101
|
+
|
102
|
+
def exists?
|
103
|
+
@file
|
104
|
+
end
|
105
|
+
|
106
|
+
def puts(*content)
|
107
|
+
content.flatten.each do |obj|
|
108
|
+
write(obj.to_s + "\n")
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def write(content)
|
113
|
+
raise IOError.new('closed stream') unless @open
|
114
|
+
|
115
|
+
if !File.exists?(@path)
|
116
|
+
@file = FileSystem.add(path, FakeFile.new)
|
117
|
+
end
|
118
|
+
|
119
|
+
@file.content += content
|
120
|
+
end
|
121
|
+
alias_method :print, :write
|
122
|
+
alias_method :<<, :write
|
123
|
+
|
124
|
+
def flush; self; end
|
125
|
+
end
|
126
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
module FakeFS
|
2
|
+
module FileSystem
|
3
|
+
extend self
|
4
|
+
|
5
|
+
def dir_levels
|
6
|
+
@dir_levels ||= []
|
7
|
+
end
|
8
|
+
|
9
|
+
def fs
|
10
|
+
@fs ||= FakeDir.new('.')
|
11
|
+
end
|
12
|
+
|
13
|
+
def clear
|
14
|
+
@dir_levels = nil
|
15
|
+
@fs = nil
|
16
|
+
end
|
17
|
+
|
18
|
+
def files
|
19
|
+
fs.values
|
20
|
+
end
|
21
|
+
|
22
|
+
def find(path)
|
23
|
+
parts = path_parts(normalize_path(path))
|
24
|
+
return fs if parts.empty? # '/'
|
25
|
+
|
26
|
+
entries = find_recurser(fs, parts).flatten
|
27
|
+
|
28
|
+
case entries.length
|
29
|
+
when 0 then nil
|
30
|
+
when 1 then entries.first
|
31
|
+
else entries
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def add(path, object=FakeDir.new)
|
36
|
+
parts = path_parts(normalize_path(path))
|
37
|
+
|
38
|
+
d = parts[0...-1].inject(fs) do |dir, part|
|
39
|
+
dir[part] ||= FakeDir.new(part, dir)
|
40
|
+
end
|
41
|
+
|
42
|
+
object.name = parts.last
|
43
|
+
object.parent = d
|
44
|
+
d[parts.last] ||= object
|
45
|
+
end
|
46
|
+
|
47
|
+
# copies directories and files from the real filesystem
|
48
|
+
# into our fake one
|
49
|
+
def clone(path)
|
50
|
+
path = File.expand_path(path)
|
51
|
+
pattern = File.join(path, '**', '*')
|
52
|
+
files = RealFile.file?(path) ? [path] : [path] + RealDir.glob(pattern, RealFile::FNM_DOTMATCH)
|
53
|
+
|
54
|
+
files.each do |f|
|
55
|
+
if RealFile.file?(f)
|
56
|
+
FileUtils.mkdir_p(File.dirname(f))
|
57
|
+
File.open(f, 'w') do |g|
|
58
|
+
g.print RealFile.open(f){|h| h.read }
|
59
|
+
end
|
60
|
+
elsif RealFile.directory?(f)
|
61
|
+
FileUtils.mkdir_p(f)
|
62
|
+
elsif RealFile.symlink?(f)
|
63
|
+
FileUtils.ln_s()
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def delete(path)
|
69
|
+
if dir = FileSystem.find(path)
|
70
|
+
dir.parent.delete(dir.name)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def chdir(dir, &blk)
|
75
|
+
new_dir = find(dir)
|
76
|
+
dir_levels.push dir if blk
|
77
|
+
|
78
|
+
raise Errno::ENOENT, dir unless new_dir
|
79
|
+
|
80
|
+
dir_levels.push dir if !blk
|
81
|
+
blk.call if blk
|
82
|
+
ensure
|
83
|
+
dir_levels.pop if blk
|
84
|
+
end
|
85
|
+
|
86
|
+
def path_parts(path)
|
87
|
+
path.split(File::PATH_SEPARATOR).reject { |part| part.empty? }
|
88
|
+
end
|
89
|
+
|
90
|
+
def normalize_path(path)
|
91
|
+
if Pathname.new(path).absolute?
|
92
|
+
File.expand_path(path)
|
93
|
+
else
|
94
|
+
parts = dir_levels + [path]
|
95
|
+
File.expand_path(File.join(*parts))
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def current_dir
|
100
|
+
find(normalize_path('.'))
|
101
|
+
end
|
102
|
+
|
103
|
+
private
|
104
|
+
|
105
|
+
def find_recurser(dir, parts)
|
106
|
+
return [] unless dir.respond_to? :[]
|
107
|
+
|
108
|
+
pattern , *parts = parts
|
109
|
+
matches = dir.reject {|k,v| /\A#{pattern.gsub('?','.').gsub('*', '.*')}\Z/ !~ k }.values
|
110
|
+
|
111
|
+
if parts.empty? # we're done recursing
|
112
|
+
matches
|
113
|
+
else
|
114
|
+
matches.map{|entry| find_recurser(entry, parts) }
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|