memfs 0.5.0 → 1.0.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.
@@ -47,7 +47,7 @@ module MemFs
47
47
  class << self; alias_method :pwd, :getwd; end
48
48
 
49
49
  def self.glob(patterns, flags = 0)
50
- patterns = [*patterns]
50
+ patterns = [*patterns].map(&:to_s)
51
51
  list = fs.paths.select do |path|
52
52
  patterns.any? do |pattern|
53
53
  File.fnmatch?(pattern, path, flags | GLOB_FLAGS)
@@ -143,11 +143,11 @@ module MemFs
143
143
 
144
144
  private
145
145
 
146
- if defined?(File::FNM_EXTGLOB)
147
- GLOB_FLAGS = File::FNM_EXTGLOB | File::FNM_PATHNAME
148
- else
149
- GLOB_FLAGS = File::FNM_PATHNAME
150
- end
146
+ GLOB_FLAGS = if defined?(File::FNM_EXTGLOB)
147
+ File::FNM_EXTGLOB | File::FNM_PATHNAME
148
+ else
149
+ File::FNM_PATHNAME
150
+ end
151
151
 
152
152
  attr_accessor :entry, :max_seek, :state
153
153
 
@@ -19,7 +19,7 @@ module MemFs
19
19
  end
20
20
 
21
21
  def find(path)
22
- path = path.sub(/\A\/+/, '')
22
+ path = path.sub(%r{\A/+}, '').sub(%r{/+\z}, '')
23
23
  parts = path.split('/', 2)
24
24
 
25
25
  if entry_names.include?(path)
@@ -44,7 +44,10 @@ module MemFs
44
44
  end
45
45
 
46
46
  def paths
47
- [path] + entries.reject { |p| %w[. ..].include?(p) }.values.map(&:paths).flatten
47
+ [path] + entries.reject { |p| %w[. ..].include?(p) }
48
+ .values
49
+ .map(&:paths)
50
+ .flatten
48
51
  end
49
52
 
50
53
  def remove_entry(entry)
@@ -33,11 +33,10 @@ module MemFs
33
33
  end
34
34
 
35
35
  def type
36
- case
37
- when block_device then 'blockSpecial'
38
- when character_device then 'characterSpecial'
39
- else 'file'
40
- end
36
+ return 'blockSpecial' if block_device
37
+ return 'characterSpecial' if character_device
38
+
39
+ 'file'
41
40
  end
42
41
  end
43
42
  end
@@ -3,7 +3,6 @@ require 'delegate'
3
3
  module MemFs
4
4
  module Fake
5
5
  class File < Entry
6
-
7
6
  class Content < SimpleDelegator
8
7
  attr_accessor :pos
9
8
 
@@ -46,7 +45,6 @@ module MemFs
46
45
  text.size
47
46
  end
48
47
  end
49
-
50
48
  end
51
49
  end
52
50
  end
@@ -3,15 +3,12 @@ require 'memfs/filesystem_access'
3
3
  require 'memfs/io'
4
4
 
5
5
  module MemFs
6
- class File
6
+ class File < IO
7
7
  extend FilesystemAccess
8
8
  extend SingleForwardable
9
- extend IO::ClassMethods
10
9
 
11
10
  include Enumerable
12
11
  include FilesystemAccess
13
- include OriginalFile::Constants
14
- include IO::InstanceMethods
15
12
 
16
13
  ALT_SEPARATOR = nil
17
14
 
@@ -22,9 +19,9 @@ module MemFs
22
19
  'w+' => CREAT | TRUNC | RDWR,
23
20
  'a' => CREAT | APPEND | WRONLY,
24
21
  'a+' => CREAT | APPEND | RDWR
25
- }
22
+ }.freeze
26
23
 
27
- SEPARATOR = '/'
24
+ SEPARATOR = '/'.freeze
28
25
  SUCCESS = 0
29
26
 
30
27
  @umask = nil
@@ -58,18 +55,24 @@ module MemFs
58
55
  :writable_real?,
59
56
  :zero?
60
57
  ].each do |query_method|
61
- define_singleton_method(query_method) do |path| # def directory?(path)
62
- stat_query(path, query_method) # stat_query(path, :directory?)
63
- end # end
58
+ # def directory?(path)
59
+ # stat_query(path, :directory?)
60
+ # end
61
+ define_singleton_method(query_method) do |path|
62
+ stat_query(path, query_method)
63
+ end
64
64
  end
65
65
 
66
66
  [
67
67
  :world_readable?,
68
68
  :world_writable?
69
69
  ].each do |query_method|
70
- define_singleton_method(query_method) do |path| # def directory?(path)
71
- stat_query(path, query_method, false) # stat_query(path, :directory?, false)
72
- end # end
70
+ # def directory?(path)
71
+ # stat_query(path, :directory?, false)
72
+ # end
73
+ define_singleton_method(query_method) do |path|
74
+ stat_query(path, query_method, false)
75
+ end
73
76
  end
74
77
 
75
78
  def self.absolute_path(path, dir_string = fs.pwd)
@@ -233,21 +236,27 @@ module MemFs
233
236
 
234
237
  def initialize(filename, mode = File::RDONLY, *perm_and_or_opt)
235
238
  opt = perm_and_or_opt.last.is_a?(Hash) ? perm_and_or_opt.pop : {}
236
- perm = perm_and_or_opt.shift
237
- if perm_and_or_opt.size > 0
239
+ perm_and_or_opt.shift
240
+ if perm_and_or_opt.any?
238
241
  fail ArgumentError, 'wrong number of arguments (4 for 1..3)'
239
242
  end
240
243
 
241
244
  @path = filename
242
- @external_encoding = opt[:external_encoding] && Encoding.find(opt[:external_encoding])
245
+ @external_encoding = opt[:external_encoding] &&
246
+ Encoding.find(opt[:external_encoding])
243
247
 
244
248
  self.closed = false
245
249
  self.opening_mode = str_to_mode_int(mode)
246
250
 
247
251
  fs.touch(filename) if create_file?
248
252
 
249
- self.entry = fs.find(filename)
253
+ self.entry = fs.find!(filename)
254
+ # FIXME: this is an ugly way to ensure a symlink has a target
255
+ entry.dereferenced
250
256
 
257
+ if entry.respond_to?(:pos=)
258
+ entry.pos = 0
259
+ end
251
260
  entry.content.clear if truncate_file?
252
261
  end
253
262
 
@@ -289,8 +298,6 @@ module MemFs
289
298
  File.truncate(path, integer)
290
299
  end
291
300
 
292
- private
293
-
294
301
  def self.dereference_name(path)
295
302
  entry = fs.find(path)
296
303
  if entry
@@ -323,13 +330,13 @@ module MemFs
323
330
 
324
331
  def self.stat_query(path, query, force_boolean = true)
325
332
  response = fs.find(path) && stat(path).public_send(query)
326
- force_boolean ? !!(response) : response
333
+ force_boolean ? !!response : response
327
334
  end
328
335
  private_class_method :stat_query
329
336
 
330
337
  def self.lstat_query(path, query)
331
338
  response = fs.find(path) && lstat(path).public_send(query)
332
- !!(response)
339
+ !!response
333
340
  end
334
341
  private_class_method :lstat_query
335
342
  end
@@ -21,7 +21,7 @@ module MemFs
21
21
  :uid
22
22
 
23
23
  def blockdev?
24
- !!(entry.block_device)
24
+ !!entry.block_device
25
25
  end
26
26
 
27
27
  def chardev?
@@ -15,15 +15,15 @@ module MemFs
15
15
  File.basename(path)
16
16
  end
17
17
 
18
- def chdir(path, &block)
18
+ def chdir(path)
19
19
  destination = find_directory!(path)
20
20
 
21
21
  previous_directory = working_directory
22
22
  self.working_directory = destination
23
23
 
24
- block.call if block
24
+ yield if block_given?
25
25
  ensure
26
- self.working_directory = previous_directory if block
26
+ self.working_directory = previous_directory if block_given?
27
27
  end
28
28
 
29
29
  def clear!
@@ -2,203 +2,239 @@ require 'forwardable'
2
2
  require 'memfs/filesystem_access'
3
3
 
4
4
  module MemFs
5
- module IO
6
- module ClassMethods
7
- def read(path, *args)
8
- options = args.last.is_a?(Hash) ? args.pop : {}
9
- options = { mode: File::RDONLY, encoding: nil, open_args: nil }.merge(options)
10
- open_args = options[:open_args] ||
11
- [options[:mode], encoding: options[:encoding]]
12
-
13
- length, offset = args
14
-
15
- file = open(path, *open_args)
16
- file.seek(offset || 0)
17
- file.read(length)
18
- ensure
19
- file.close if file
20
- end
5
+ class IO
6
+ extend SingleForwardable
7
+ include OriginalFile::Constants
8
+
9
+ (OriginalIO.constants - OriginalFile::Constants.constants)
10
+ .each do |const_name|
11
+ self.const_set(const_name, OriginalIO.const_get(const_name))
12
+ end
13
+
14
+ def_delegators :original_io_class,
15
+ :copy_stream
16
+
17
+ def self.read(path, *args)
18
+ options = args.last.is_a?(Hash) ? args.pop : {}
19
+ options = {
20
+ mode: File::RDONLY,
21
+ encoding: nil,
22
+ open_args: nil
23
+ }.merge(options)
24
+ open_args = options[:open_args] ||
25
+ [options[:mode], encoding: options[:encoding]]
26
+
27
+ length, offset = args
28
+
29
+ file = open(path, *open_args)
30
+ file.seek(offset || 0)
31
+ file.read(length)
32
+ ensure
33
+ file.close if file
21
34
  end
22
35
 
23
- module InstanceMethods
24
- attr_writer :autoclose,
25
- :close_on_exec
26
-
27
- def <<(object)
28
- fail IOError, 'not opened for writing' unless writable?
36
+ def self.write(path, string, offset = 0, open_args = nil)
37
+ open_args ||= [File::WRONLY, encoding: nil]
29
38
 
30
- content << object.to_s
39
+ offset = 0 if offset.nil?
40
+ unless offset.respond_to?(:to_int)
41
+ fail TypeError, "no implicit conversion from #{offset.class}"
31
42
  end
43
+ offset = offset.to_int
32
44
 
33
- def advise(advice_type, offset = 0, len = 0)
34
- advice_types = [
35
- :dontneed,
36
- :noreuse,
37
- :normal,
38
- :random,
39
- :sequential,
40
- :willneed
41
- ]
42
- unless advice_types.include?(advice_type)
43
- fail NotImplementedError, "Unsupported advice: #{advice_type.inspect}"
44
- end
45
- nil
45
+ if offset > 0
46
+ fail NotImplementedError,
47
+ 'MemFs::IO.write with offset not yet supported.'
46
48
  end
47
49
 
48
- def autoclose?
49
- @autoclose.nil? ? true : !!@autoclose
50
- end
50
+ file = open(path, *open_args)
51
+ file.seek(offset)
52
+ file.write(string)
53
+ ensure
54
+ file.close if file
55
+ end
51
56
 
52
- def binmode
53
- @binmode = true
54
- @external_encoding = Encoding::ASCII_8BIT
55
- self
56
- end
57
+ def self.original_io_class
58
+ MemFs::OriginalIO
59
+ end
60
+ private_class_method :original_io_class
57
61
 
58
- def binmode?
59
- @binmode.nil? ? false : @binmode
60
- end
62
+ attr_writer :autoclose,
63
+ :close_on_exec
61
64
 
62
- def close
63
- self.closed = true
64
- end
65
+ def <<(object)
66
+ fail IOError, 'not opened for writing' unless writable?
65
67
 
66
- def closed?
67
- closed
68
- end
68
+ content << object.to_s
69
+ end
69
70
 
70
- def close_on_exec?
71
- @close_on_exec.nil? ? true : !!@close_on_exec
72
- end
71
+ def advise(advice_type, _offset = 0, _len = 0)
72
+ advice_types = [
73
+ :dontneed,
74
+ :noreuse,
75
+ :normal,
76
+ :random,
77
+ :sequential,
78
+ :willneed
79
+ ]
80
+ unless advice_types.include?(advice_type)
81
+ fail NotImplementedError, "Unsupported advice: #{advice_type.inspect}"
82
+ end
83
+ nil
84
+ end
73
85
 
74
- def eof?
75
- pos >= content.size
76
- end
77
- alias_method :eof, :eof?
78
-
79
- def external_encoding
80
- if writable?
81
- @external_encoding
82
- else
83
- @external_encoding ||= Encoding.default_external
84
- end
85
- end
86
+ def autoclose?
87
+ defined?(@autoclose) ? !!@autoclose : true
88
+ end
86
89
 
87
- def each(sep = $/, &block)
88
- return to_enum(__callee__) unless block_given?
89
- fail IOError, 'not opened for reading' unless readable?
90
- content.each_line(sep) { |line| block.call(line) }
91
- self
92
- end
90
+ def binmode
91
+ @binmode = true
92
+ @external_encoding = Encoding::ASCII_8BIT
93
+ self
94
+ end
93
95
 
94
- def each_byte(&block)
95
- return to_enum(__callee__) unless block_given?
96
- fail IOError, 'not opened for reading' unless readable?
97
- content.each_byte { |byte| block.call(byte) }
98
- self
99
- end
100
- alias_method :bytes, :each_byte
96
+ def binmode?
97
+ defined?(@binmode) ? @binmode : false
98
+ end
101
99
 
102
- def each_char(&block)
103
- return to_enum(__callee__) unless block_given?
104
- fail IOError, 'not opened for reading' unless readable?
105
- content.each_char { |char| block.call(char) }
106
- self
107
- end
108
- alias_method :chars, :each_char
100
+ def close
101
+ self.closed = true
102
+ end
109
103
 
110
- def pos
111
- entry.pos
112
- end
104
+ def closed?
105
+ closed
106
+ end
113
107
 
114
- def print(*objs)
115
- $stdout.puts $_.inspect
116
- objs << $_ if objs.empty?
117
- self << objs.join($,) << $\.to_s
118
- nil
119
- end
108
+ def close_on_exec?
109
+ defined?(@close_on_exec) ? !!@close_on_exec : true
110
+ end
111
+
112
+ def eof?
113
+ pos >= content.size
114
+ end
115
+ alias_method :eof, :eof?
120
116
 
121
- def printf(format_string, *objs)
122
- print format_string % objs
117
+ def external_encoding
118
+ if writable?
119
+ @external_encoding
120
+ else
121
+ @external_encoding ||= Encoding.default_external
123
122
  end
123
+ end
124
124
 
125
- def puts(text)
126
- fail IOError, 'not opened for writing' unless writable?
125
+ def each(sep = $/)
126
+ return to_enum(__callee__) unless block_given?
127
+ fail IOError, 'not opened for reading' unless readable?
128
+ content.each_line(sep) { |line| yield(line) }
129
+ self
130
+ end
127
131
 
128
- content.puts text
129
- end
132
+ def each_byte
133
+ return to_enum(__callee__) unless block_given?
134
+ fail IOError, 'not opened for reading' unless readable?
135
+ content.each_byte { |byte| yield(byte) }
136
+ self
137
+ end
138
+ alias_method :bytes, :each_byte
130
139
 
131
- def read(length = nil, buffer = '')
132
- unless entry
133
- fail(Errno::ENOENT, path)
134
- end
135
- default = length ? nil : ''
136
- content.read(length, buffer) || default
137
- end
140
+ def each_char
141
+ return to_enum(__callee__) unless block_given?
142
+ fail IOError, 'not opened for reading' unless readable?
143
+ content.each_char { |char| yield(char) }
144
+ self
145
+ end
146
+ alias_method :chars, :each_char
138
147
 
139
- def seek(amount, whence = ::IO::SEEK_SET)
140
- new_pos = case whence
141
- when ::IO::SEEK_CUR then entry.pos + amount
142
- when ::IO::SEEK_END then content.to_s.length + amount
143
- when ::IO::SEEK_SET then amount
144
- end
148
+ def pos
149
+ entry.pos
150
+ end
145
151
 
146
- fail Errno::EINVAL, path if new_pos.nil? || new_pos < 0
152
+ def print(*objs)
153
+ objs << $_ if objs.empty?
154
+ self << objs.join($,) << $\.to_s
155
+ nil
156
+ end
147
157
 
148
- entry.pos = new_pos
149
- 0
150
- end
158
+ def printf(format_string, *objs)
159
+ print format_string % objs
160
+ end
151
161
 
152
- def stat
153
- File.stat(path)
154
- end
162
+ def puts(text)
163
+ fail IOError, 'not opened for writing' unless writable?
155
164
 
156
- def write(string)
157
- fail IOError, 'not opened for writing' unless writable?
165
+ content.puts text
166
+ end
158
167
 
159
- content.write(string.to_s)
168
+ def read(length = nil, buffer = '')
169
+ unless entry
170
+ fail(Errno::ENOENT, path)
160
171
  end
172
+ default = length ? nil : ''
173
+ content.read(length, buffer) || default
174
+ end
161
175
 
162
- private
176
+ def seek(amount, whence = ::IO::SEEK_SET)
177
+ new_pos = case whence
178
+ when ::IO::SEEK_CUR then entry.pos + amount
179
+ when ::IO::SEEK_END then content.to_s.length + amount
180
+ when ::IO::SEEK_SET then amount
181
+ end
163
182
 
164
- attr_accessor :closed,
165
- :entry,
166
- :opening_mode
183
+ fail Errno::EINVAL, path if new_pos.nil? || new_pos < 0
167
184
 
168
- attr_reader :path
185
+ entry.pos = new_pos
186
+ 0
187
+ end
169
188
 
170
- def content
171
- entry.content
172
- end
189
+ def stat
190
+ File.stat(path)
191
+ end
173
192
 
174
- def create_file?
175
- (opening_mode & File::CREAT).nonzero?
176
- end
193
+ def write(string)
194
+ fail IOError, 'not opened for writing' unless writable?
177
195
 
178
- def readable?
179
- (opening_mode & File::RDWR).nonzero? ||
180
- (opening_mode | File::RDONLY).zero?
181
- end
196
+ content.write(string.to_s)
197
+ end
182
198
 
183
- def str_to_mode_int(mode)
184
- return mode unless mode.is_a?(String)
199
+ private
185
200
 
186
- unless mode =~ /\A([rwa]\+?)([bt])?(:bom)?(\|.+)?\z/
187
- fail ArgumentError, "invalid access mode #{mode}"
188
- end
201
+ attr_accessor :closed,
202
+ :entry,
203
+ :opening_mode
189
204
 
190
- mode_str = $~[1]
191
- File::MODE_MAP[mode_str]
192
- end
205
+ attr_reader :path
206
+
207
+ def content
208
+ entry.content
209
+ end
193
210
 
194
- def truncate_file?
195
- (opening_mode & File::TRUNC).nonzero?
211
+ def create_file?
212
+ (opening_mode & File::CREAT).nonzero?
213
+ end
214
+
215
+ def readable?
216
+ (opening_mode & File::RDWR).nonzero? ||
217
+ (opening_mode | File::RDONLY).zero?
218
+ end
219
+
220
+ def str_to_mode_int(mode)
221
+ return mode unless mode.is_a?(String)
222
+
223
+ unless mode =~ /\A([rwa]\+?)([bt])?(:bom)?(\|.+)?\z/
224
+ fail ArgumentError, "invalid access mode #{mode}"
196
225
  end
197
226
 
198
- def writable?
199
- (opening_mode & File::WRONLY).nonzero? ||
227
+ mode_str = $~[1]
228
+ File::MODE_MAP[mode_str]
229
+ end
230
+
231
+ def truncate_file?
232
+ (opening_mode & File::TRUNC).nonzero?
233
+ end
234
+
235
+ def writable?
236
+ (opening_mode & File::WRONLY).nonzero? ||
200
237
  (opening_mode & File::RDWR).nonzero?
201
- end
202
238
  end
203
239
  end
204
240
  end