memfs 0.0.1
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.
- checksums.yaml +7 -0
- data/.DS_Store +0 -0
- data/.gitignore +17 -0
- data/.rspec +2 -0
- data/.travis.yml +3 -0
- data/Gemfile +4 -0
- data/Guardfile +8 -0
- data/LICENSE.txt +22 -0
- data/README.md +128 -0
- data/Rakefile +6 -0
- data/lib/fileutils.rb +1738 -0
- data/lib/memfs/dir.rb +33 -0
- data/lib/memfs/fake/directory.rb +51 -0
- data/lib/memfs/fake/entry.rb +73 -0
- data/lib/memfs/fake/file/content.rb +47 -0
- data/lib/memfs/fake/file.rb +36 -0
- data/lib/memfs/fake/symlink.rb +32 -0
- data/lib/memfs/file/stat.rb +48 -0
- data/lib/memfs/file.rb +284 -0
- data/lib/memfs/file_system.rb +157 -0
- data/lib/memfs/filesystem_access.rb +7 -0
- data/lib/memfs/version.rb +3 -0
- data/lib/memfs.rb +100 -0
- data/memfs.gemspec +29 -0
- data/spec/fileutils_spec.rb +961 -0
- data/spec/memfs/dir_spec.rb +104 -0
- data/spec/memfs/fake/directory_spec.rb +115 -0
- data/spec/memfs/fake/entry_spec.rb +140 -0
- data/spec/memfs/fake/file/content_spec.rb +154 -0
- data/spec/memfs/fake/file_spec.rb +37 -0
- data/spec/memfs/fake/symlink_spec.rb +43 -0
- data/spec/memfs/file/stat_spec.rb +209 -0
- data/spec/memfs/file_spec.rb +928 -0
- data/spec/memfs/file_system_spec.rb +393 -0
- data/spec/memfs_spec.rb +54 -0
- data/spec/spec_helper.rb +20 -0
- metadata +203 -0
data/lib/memfs/dir.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'memfs/filesystem_access'
|
2
|
+
|
3
|
+
module MemFs
|
4
|
+
class Dir
|
5
|
+
extend FilesystemAccess
|
6
|
+
|
7
|
+
def self.chdir(path, &block)
|
8
|
+
fs.chdir path, &block
|
9
|
+
return 0
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.entries(dirname, opts = {})
|
13
|
+
fs.entries(dirname)
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.exists?(path)
|
17
|
+
fs.directory?(path)
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.getwd
|
21
|
+
fs.getwd
|
22
|
+
end
|
23
|
+
class << self; alias :pwd :getwd; end
|
24
|
+
|
25
|
+
def self.mkdir(path)
|
26
|
+
fs.mkdir path
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.rmdir(path)
|
30
|
+
fs.rmdir path
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'memfs/fake/entry'
|
2
|
+
|
3
|
+
module MemFs
|
4
|
+
module Fake
|
5
|
+
class Directory < Entry
|
6
|
+
attr_accessor :entries
|
7
|
+
|
8
|
+
def add_entry(entry)
|
9
|
+
entries[entry.name] = entry
|
10
|
+
entry.parent = self
|
11
|
+
end
|
12
|
+
|
13
|
+
def empty?
|
14
|
+
(entries.keys - %w[. ..]).empty?
|
15
|
+
end
|
16
|
+
|
17
|
+
def entry_names
|
18
|
+
entries.keys
|
19
|
+
end
|
20
|
+
|
21
|
+
def find(path)
|
22
|
+
path = path.sub(/\A\/+/, '')
|
23
|
+
parts = path.split('/', 2)
|
24
|
+
|
25
|
+
if entry_names.include?(path)
|
26
|
+
entries[path]
|
27
|
+
elsif entry_names.include?(parts.first)
|
28
|
+
entries[parts.first].find(parts.last)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def initialize(*args)
|
33
|
+
super
|
34
|
+
self.entries = { '.' => self, '..' => nil }
|
35
|
+
end
|
36
|
+
|
37
|
+
def parent=(parent)
|
38
|
+
super
|
39
|
+
entries['..'] = parent
|
40
|
+
end
|
41
|
+
|
42
|
+
def path
|
43
|
+
name == '/' ? '/' : super
|
44
|
+
end
|
45
|
+
|
46
|
+
def remove_entry(entry)
|
47
|
+
entries.delete(entry.name)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module MemFs
|
2
|
+
module Fake
|
3
|
+
class Entry
|
4
|
+
UREAD = 00100
|
5
|
+
UWRITE = 00200
|
6
|
+
UEXEC = 00400
|
7
|
+
GREAD = 00010
|
8
|
+
GWRITE = 00020
|
9
|
+
GEXEC = 00040
|
10
|
+
OREAD = 00001
|
11
|
+
OWRITE = 00002
|
12
|
+
OEXEC = 00004
|
13
|
+
RSTICK = 01000
|
14
|
+
USTICK = 05000
|
15
|
+
|
16
|
+
attr_accessor :atime,
|
17
|
+
:gid,
|
18
|
+
:mtime,
|
19
|
+
:name,
|
20
|
+
:parent,
|
21
|
+
:uid
|
22
|
+
attr_reader :mode
|
23
|
+
|
24
|
+
def blksize
|
25
|
+
4096
|
26
|
+
end
|
27
|
+
|
28
|
+
def delete
|
29
|
+
parent.remove_entry self
|
30
|
+
end
|
31
|
+
|
32
|
+
def dereferenced
|
33
|
+
self
|
34
|
+
end
|
35
|
+
|
36
|
+
def dev
|
37
|
+
@dev ||= rand(1000)
|
38
|
+
end
|
39
|
+
|
40
|
+
def find(path)
|
41
|
+
raise Errno::ENOTDIR, self.path
|
42
|
+
end
|
43
|
+
|
44
|
+
def initialize(path = nil)
|
45
|
+
current_user = Etc.getpwuid
|
46
|
+
time = Time.now
|
47
|
+
self.atime = time
|
48
|
+
self.gid = current_user.gid
|
49
|
+
self.mode = 0666 - MemFs::File.umask
|
50
|
+
self.mtime = time
|
51
|
+
self.name = MemFs::File.basename(path || '')
|
52
|
+
self.uid = current_user.uid
|
53
|
+
end
|
54
|
+
|
55
|
+
def ino
|
56
|
+
@ino ||= rand(1000)
|
57
|
+
end
|
58
|
+
|
59
|
+
def mode=(mode_int)
|
60
|
+
@mode = 0100000 | mode_int
|
61
|
+
end
|
62
|
+
|
63
|
+
def path
|
64
|
+
parts = [parent && parent.path, name].compact
|
65
|
+
MemFs::File.join(parts)
|
66
|
+
end
|
67
|
+
|
68
|
+
def touch
|
69
|
+
self.atime = self.mtime = Time.now
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require "delegate"
|
2
|
+
|
3
|
+
module MemFs
|
4
|
+
module Fake
|
5
|
+
class File < Entry
|
6
|
+
|
7
|
+
class Content < SimpleDelegator
|
8
|
+
attr_accessor :pos
|
9
|
+
|
10
|
+
def close
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(obj = '')
|
14
|
+
@string = obj.to_s.dup
|
15
|
+
@pos = 0
|
16
|
+
|
17
|
+
__setobj__ @string
|
18
|
+
end
|
19
|
+
|
20
|
+
def puts(*strings)
|
21
|
+
strings.each do |str|
|
22
|
+
@string << str
|
23
|
+
@string << $/ unless str.end_with?($/)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def read(length = nil, buffer = '')
|
28
|
+
length ||= @string.length - @pos
|
29
|
+
buffer.replace @string[@pos, length]
|
30
|
+
@pos += buffer.bytesize
|
31
|
+
buffer.empty? ? nil : buffer
|
32
|
+
end
|
33
|
+
|
34
|
+
def to_s
|
35
|
+
@string
|
36
|
+
end
|
37
|
+
|
38
|
+
def write(string)
|
39
|
+
text = string.to_s
|
40
|
+
@string << text
|
41
|
+
text.size
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'memfs/fake/entry'
|
2
|
+
require 'memfs/fake/file/content'
|
3
|
+
|
4
|
+
module MemFs
|
5
|
+
module Fake
|
6
|
+
class File < Entry
|
7
|
+
attr_accessor :content
|
8
|
+
|
9
|
+
def close
|
10
|
+
@closed = true
|
11
|
+
end
|
12
|
+
|
13
|
+
def closed?
|
14
|
+
@closed
|
15
|
+
end
|
16
|
+
|
17
|
+
def initialize(*args)
|
18
|
+
super
|
19
|
+
@content = Content.new
|
20
|
+
@closed = false
|
21
|
+
end
|
22
|
+
|
23
|
+
def pos
|
24
|
+
content.pos
|
25
|
+
end
|
26
|
+
|
27
|
+
def pos=(value)
|
28
|
+
content.pos = value
|
29
|
+
end
|
30
|
+
|
31
|
+
def size
|
32
|
+
content.size
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'memfs/filesystem_access'
|
2
|
+
|
3
|
+
module MemFs
|
4
|
+
module Fake
|
5
|
+
class Symlink < Entry
|
6
|
+
include MemFs::FilesystemAccess
|
7
|
+
|
8
|
+
attr_reader :target
|
9
|
+
|
10
|
+
def dereferenced
|
11
|
+
@dereferenced ||= fs.find!(target).dereferenced
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize(path, target)
|
15
|
+
super(path)
|
16
|
+
@target = target
|
17
|
+
end
|
18
|
+
|
19
|
+
def method_missing(meth, *args, &block)
|
20
|
+
if dereferenced.respond_to?(meth)
|
21
|
+
dereferenced.public_send(meth, *args, &block)
|
22
|
+
else
|
23
|
+
super
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def respond_to_missing?(meth, include_private)
|
28
|
+
dereferenced.respond_to?(meth, include_private) || super
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
require 'memfs/filesystem_access'
|
3
|
+
|
4
|
+
module MemFs
|
5
|
+
class File
|
6
|
+
class Stat
|
7
|
+
extend Forwardable
|
8
|
+
include FilesystemAccess
|
9
|
+
|
10
|
+
attr_reader :entry
|
11
|
+
|
12
|
+
def_delegators :entry,
|
13
|
+
:atime,
|
14
|
+
:blksize,
|
15
|
+
:dev,
|
16
|
+
:gid,
|
17
|
+
:ino,
|
18
|
+
:mode,
|
19
|
+
:mtime,
|
20
|
+
:uid
|
21
|
+
|
22
|
+
def directory?
|
23
|
+
File.directory? entry.path
|
24
|
+
end
|
25
|
+
|
26
|
+
def file?
|
27
|
+
File.file? entry.path
|
28
|
+
end
|
29
|
+
|
30
|
+
def initialize(path, dereference = false)
|
31
|
+
entry = fs.find!(path)
|
32
|
+
@entry = dereference ? entry.dereferenced : entry
|
33
|
+
end
|
34
|
+
|
35
|
+
def sticky?
|
36
|
+
!!(entry.mode & Fake::Entry::USTICK).nonzero?
|
37
|
+
end
|
38
|
+
|
39
|
+
def symlink?
|
40
|
+
File.symlink? entry.path
|
41
|
+
end
|
42
|
+
|
43
|
+
def world_writable?
|
44
|
+
entry.mode if (entry.mode & Fake::Entry::OWRITE).nonzero?
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
data/lib/memfs/file.rb
ADDED
@@ -0,0 +1,284 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
require 'memfs/filesystem_access'
|
3
|
+
|
4
|
+
module MemFs
|
5
|
+
class File
|
6
|
+
extend FilesystemAccess
|
7
|
+
extend SingleForwardable
|
8
|
+
include FilesystemAccess
|
9
|
+
|
10
|
+
OriginalFile.constants.grep(/^[A-Z_]+$/).each do |const|
|
11
|
+
const_set const, OriginalFile.const_get(const)
|
12
|
+
end
|
13
|
+
|
14
|
+
MODE_MAP = {
|
15
|
+
'r' => RDONLY,
|
16
|
+
'r+' => RDWR,
|
17
|
+
'w' => CREAT|TRUNC|WRONLY,
|
18
|
+
'w+' => CREAT|TRUNC|RDWR,
|
19
|
+
'a' => CREAT|APPEND|WRONLY,
|
20
|
+
'a+' => CREAT|APPEND|RDWR
|
21
|
+
}
|
22
|
+
|
23
|
+
SUCCESS = 0
|
24
|
+
|
25
|
+
def_delegators :original_file_class,
|
26
|
+
:basename,
|
27
|
+
:dirname,
|
28
|
+
:join,
|
29
|
+
:path
|
30
|
+
|
31
|
+
def self.atime(path)
|
32
|
+
stat(path).atime
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.chmod(mode_int, *paths)
|
36
|
+
paths.each do |path|
|
37
|
+
fs.chmod mode_int, path
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.chown(uid, gid, *paths)
|
42
|
+
paths.each do |path|
|
43
|
+
fs.chown(uid, gid, path)
|
44
|
+
end
|
45
|
+
paths.size
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.directory?(path)
|
49
|
+
fs.directory? path
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.exists?(path)
|
53
|
+
not fs.find(path).nil?
|
54
|
+
end
|
55
|
+
class << self; alias :exist? :exists?; end
|
56
|
+
|
57
|
+
def self.expand_path(file_name, dir_string = fs.pwd)
|
58
|
+
original_file_class.expand_path(file_name, dir_string)
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.file?(path)
|
62
|
+
fs.find(path).is_a?(Fake::File)
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.identical?(path1, path2)
|
66
|
+
fs.find!(path1).dereferenced === fs.find!(path2).dereferenced
|
67
|
+
rescue Errno::ENOENT
|
68
|
+
false
|
69
|
+
end
|
70
|
+
|
71
|
+
def self.lchmod(mode_int, *file_names)
|
72
|
+
file_names.each do |file_name|
|
73
|
+
fs.chmod mode_int, file_name
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def self.lchown(uid, gid, *paths)
|
78
|
+
chown uid, gid, *paths
|
79
|
+
end
|
80
|
+
|
81
|
+
def self.link(old_name, new_name)
|
82
|
+
fs.link old_name, new_name
|
83
|
+
SUCCESS
|
84
|
+
end
|
85
|
+
|
86
|
+
def self.lstat(path)
|
87
|
+
Stat.new(path)
|
88
|
+
end
|
89
|
+
|
90
|
+
def self.mtime(path)
|
91
|
+
stat(path).mtime
|
92
|
+
end
|
93
|
+
|
94
|
+
def self.open(filename, mode = RDONLY, *perm_and_opt)
|
95
|
+
file = self.new(filename, mode, *perm_and_opt)
|
96
|
+
|
97
|
+
if block_given?
|
98
|
+
yield file
|
99
|
+
else
|
100
|
+
file
|
101
|
+
end
|
102
|
+
ensure
|
103
|
+
file.close if file && block_given?
|
104
|
+
end
|
105
|
+
|
106
|
+
def self.read(path, length = nil, offset = 0, mode: RDONLY, encoding: nil, open_args: nil)
|
107
|
+
open_args ||= [mode, encoding: encoding]
|
108
|
+
|
109
|
+
file = open(path, *open_args)
|
110
|
+
file.seek(offset)
|
111
|
+
file.read(length)
|
112
|
+
ensure
|
113
|
+
file.close if file
|
114
|
+
end
|
115
|
+
|
116
|
+
def self.readlink(path)
|
117
|
+
fs.find!(path).target
|
118
|
+
end
|
119
|
+
|
120
|
+
def self.rename(old_name, new_name)
|
121
|
+
fs.rename(old_name, new_name)
|
122
|
+
SUCCESS
|
123
|
+
end
|
124
|
+
|
125
|
+
def self.reset!
|
126
|
+
@umask = original_file_class.umask
|
127
|
+
end
|
128
|
+
|
129
|
+
def self.size(path)
|
130
|
+
fs.find!(path).size
|
131
|
+
end
|
132
|
+
|
133
|
+
def self.stat(path)
|
134
|
+
Stat.new(path, true)
|
135
|
+
end
|
136
|
+
|
137
|
+
def self.symlink(old_name, new_name)
|
138
|
+
fs.symlink old_name, new_name
|
139
|
+
SUCCESS
|
140
|
+
end
|
141
|
+
|
142
|
+
def self.symlink?(path)
|
143
|
+
fs.symlink? path
|
144
|
+
end
|
145
|
+
|
146
|
+
def self.umask(integer = nil)
|
147
|
+
old_value = @umask || original_file_class.umask
|
148
|
+
|
149
|
+
@umask = integer if integer
|
150
|
+
|
151
|
+
old_value
|
152
|
+
end
|
153
|
+
|
154
|
+
def self.unlink(*paths)
|
155
|
+
paths.each do |path|
|
156
|
+
fs.unlink(path)
|
157
|
+
end
|
158
|
+
paths.size
|
159
|
+
end
|
160
|
+
class << self; alias :delete :unlink; end
|
161
|
+
|
162
|
+
def self.utime(atime, mtime, *file_names)
|
163
|
+
file_names.each do |file_name|
|
164
|
+
fs.find!(file_name).atime = atime
|
165
|
+
fs.find!(file_name).mtime = mtime
|
166
|
+
end
|
167
|
+
file_names.size
|
168
|
+
end
|
169
|
+
|
170
|
+
attr_accessor :closed,
|
171
|
+
:entry,
|
172
|
+
:opening_mode
|
173
|
+
attr_reader :path
|
174
|
+
|
175
|
+
def initialize(filename, mode = RDONLY, perm = nil, opt = nil)
|
176
|
+
unless opt.nil? || opt.is_a?(Hash)
|
177
|
+
raise ArgumentError, "wrong number of arguments (4 for 1..3)"
|
178
|
+
end
|
179
|
+
|
180
|
+
@path = filename
|
181
|
+
|
182
|
+
self.opening_mode = str_to_mode_int(mode)
|
183
|
+
|
184
|
+
fs.touch(filename) if create_file?
|
185
|
+
|
186
|
+
self.entry = fs.find(filename)
|
187
|
+
end
|
188
|
+
|
189
|
+
def chmod(mode_int)
|
190
|
+
fs.chmod(mode_int, path)
|
191
|
+
SUCCESS
|
192
|
+
end
|
193
|
+
|
194
|
+
def chown(uid, gid = nil)
|
195
|
+
fs.chown(uid, gid, path)
|
196
|
+
SUCCESS
|
197
|
+
end
|
198
|
+
|
199
|
+
def close
|
200
|
+
self.closed = true
|
201
|
+
end
|
202
|
+
|
203
|
+
def closed?
|
204
|
+
closed
|
205
|
+
end
|
206
|
+
|
207
|
+
def content
|
208
|
+
entry.content
|
209
|
+
end
|
210
|
+
|
211
|
+
def lstat
|
212
|
+
File.lstat(path)
|
213
|
+
end
|
214
|
+
|
215
|
+
def pos
|
216
|
+
entry.pos
|
217
|
+
end
|
218
|
+
|
219
|
+
def puts(text)
|
220
|
+
raise IOError, 'not opened for writing' unless writable?
|
221
|
+
|
222
|
+
content.puts text
|
223
|
+
end
|
224
|
+
|
225
|
+
def read(length = nil, buffer = '')
|
226
|
+
default = length ? nil : ''
|
227
|
+
content.read(length, buffer) || default
|
228
|
+
end
|
229
|
+
|
230
|
+
def seek(amount, whence = IO::SEEK_SET)
|
231
|
+
new_pos = case whence
|
232
|
+
when IO::SEEK_CUR then entry.pos + amount
|
233
|
+
when IO::SEEK_END then content.to_s.length + amount
|
234
|
+
when IO::SEEK_SET then amount
|
235
|
+
end
|
236
|
+
|
237
|
+
if new_pos.nil? || new_pos < 0
|
238
|
+
raise Errno::EINVAL, path
|
239
|
+
end
|
240
|
+
|
241
|
+
entry.pos = new_pos and 0
|
242
|
+
end
|
243
|
+
|
244
|
+
def size
|
245
|
+
entry.size
|
246
|
+
end
|
247
|
+
|
248
|
+
def stat
|
249
|
+
File.stat(path)
|
250
|
+
end
|
251
|
+
|
252
|
+
def write(string)
|
253
|
+
raise IOError, 'not opened for writing' unless writable?
|
254
|
+
|
255
|
+
content.write(string.to_s)
|
256
|
+
end
|
257
|
+
|
258
|
+
private
|
259
|
+
|
260
|
+
def self.original_file_class
|
261
|
+
MemFs::OriginalFile
|
262
|
+
end
|
263
|
+
|
264
|
+
def str_to_mode_int(mode)
|
265
|
+
return mode unless mode.is_a?(String)
|
266
|
+
|
267
|
+
unless mode =~ /\A([rwa]\+?)([bt])?\z/
|
268
|
+
raise ArgumentError, "invalid access mode #{mode}"
|
269
|
+
end
|
270
|
+
|
271
|
+
mode_str = $~[1]
|
272
|
+
MODE_MAP[mode_str]
|
273
|
+
end
|
274
|
+
|
275
|
+
def create_file?
|
276
|
+
(opening_mode & File::CREAT).nonzero?
|
277
|
+
end
|
278
|
+
|
279
|
+
def writable?
|
280
|
+
(opening_mode & File::WRONLY).nonzero? ||
|
281
|
+
(opening_mode & File::RDWR).nonzero?
|
282
|
+
end
|
283
|
+
end
|
284
|
+
end
|