memfs 1.0.0 → 2.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.
data/lib/memfs/file.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'forwardable'
2
4
  require 'memfs/filesystem_access'
3
5
  require 'memfs/io'
@@ -10,50 +12,51 @@ module MemFs
10
12
  include Enumerable
11
13
  include FilesystemAccess
12
14
 
15
+ PATH_SEPARATOR = '/'
13
16
  ALT_SEPARATOR = nil
14
17
 
15
18
  MODE_MAP = {
16
- 'r' => RDONLY,
19
+ 'r' => RDONLY,
17
20
  'r+' => RDWR,
18
- 'w' => CREAT | TRUNC | WRONLY,
21
+ 'w' => CREAT | TRUNC | WRONLY,
19
22
  'w+' => CREAT | TRUNC | RDWR,
20
- 'a' => CREAT | APPEND | WRONLY,
23
+ 'a' => CREAT | APPEND | WRONLY,
21
24
  'a+' => CREAT | APPEND | RDWR
22
25
  }.freeze
23
26
 
24
- SEPARATOR = '/'.freeze
27
+ SEPARATOR = '/'
25
28
  SUCCESS = 0
26
29
 
27
30
  @umask = nil
28
31
 
29
32
  def_delegators :original_file_class,
30
- :basename,
31
- :dirname,
32
- :extname,
33
- :fnmatch,
34
- :join,
35
- :path,
36
- :split
37
-
38
- [
39
- :blockdev?,
40
- :chardev?,
41
- :directory?,
42
- :executable?,
43
- :executable_real?,
44
- :file?,
45
- :grpowned?,
46
- :owned?,
47
- :pipe?,
48
- :readable?,
49
- :readable_real?,
50
- :setgid?,
51
- :setuid?,
52
- :socket?,
53
- :sticky?,
54
- :writable?,
55
- :writable_real?,
56
- :zero?
33
+ :basename,
34
+ :dirname,
35
+ :extname,
36
+ :fnmatch,
37
+ :join,
38
+ :path,
39
+ :split
40
+
41
+ %i[
42
+ blockdev?
43
+ chardev?
44
+ directory?
45
+ executable?
46
+ executable_real?
47
+ file?
48
+ grpowned?
49
+ owned?
50
+ pipe?
51
+ readable?
52
+ readable_real?
53
+ setgid?
54
+ setuid?
55
+ socket?
56
+ sticky?
57
+ writable?
58
+ writable_real?
59
+ zero?
57
60
  ].each do |query_method|
58
61
  # def directory?(path)
59
62
  # stat_query(path, :directory?)
@@ -63,15 +66,17 @@ module MemFs
63
66
  end
64
67
  end
65
68
 
66
- [
67
- :world_readable?,
68
- :world_writable?
69
+ class << self; alias empty? zero?; end
70
+
71
+ %i[
72
+ world_readable?
73
+ world_writable?
69
74
  ].each do |query_method|
70
75
  # def directory?(path)
71
76
  # stat_query(path, :directory?, false)
72
77
  # end
73
78
  define_singleton_method(query_method) do |path|
74
- stat_query(path, query_method, false)
79
+ stat_query(path, query_method, force_boolean: false)
75
80
  end
76
81
  end
77
82
 
@@ -83,6 +88,10 @@ module MemFs
83
88
  stat(path).atime
84
89
  end
85
90
 
91
+ def self.birthtime(path)
92
+ stat(path).birthtime
93
+ end
94
+
86
95
  def self.chmod(mode_int, *paths)
87
96
  paths.each do |path|
88
97
  fs.chmod mode_int, path
@@ -103,7 +112,7 @@ module MemFs
103
112
  def self.exists?(path)
104
113
  !!fs.find(path)
105
114
  end
106
- class << self; alias_method :exist?, :exists?; end
115
+ class << self; alias exist? exists?; end
107
116
 
108
117
  def self.expand_path(file_name, dir_string = fs.pwd)
109
118
  original_file_class.expand_path(file_name, dir_string)
@@ -113,10 +122,12 @@ module MemFs
113
122
  fs.find!(path) && lstat(path).ftype
114
123
  end
115
124
 
116
- class << self; alias_method :fnmatch?, :fnmatch; end
125
+ class << self; alias fnmatch? fnmatch; end
117
126
 
118
127
  def self.identical?(path1, path2)
119
- fs.find!(path1).dereferenced === fs.find!(path2).dereferenced
128
+ path1 = path1.path if path1.respond_to?(:path)
129
+ path2 = path2.path if path2.respond_to?(:path)
130
+ fs.find!(path1).dereferenced.equal? fs.find!(path2).dereferenced
120
131
  rescue Errno::ENOENT
121
132
  false
122
133
  end
@@ -183,15 +194,13 @@ module MemFs
183
194
 
184
195
  def self.size?(path)
185
196
  file = fs.find(path)
186
- if file && file.size > 0
187
- file.size
188
- else
189
- false
190
- end
197
+ size = file&.size.to_i
198
+
199
+ size.positive? ? size : false
191
200
  end
192
201
 
193
202
  def self.stat(path)
194
- Stat.new(path, true)
203
+ Stat.new(path, dereference: true)
195
204
  end
196
205
 
197
206
  def self.symlink(old_name, new_name)
@@ -200,7 +209,7 @@ module MemFs
200
209
  end
201
210
 
202
211
  def self.symlink?(path)
203
- lstat_query(path, :symlink?)
212
+ lstat_query?(path, :symlink?)
204
213
  end
205
214
 
206
215
  def self.truncate(path, length)
@@ -217,12 +226,10 @@ module MemFs
217
226
  end
218
227
 
219
228
  def self.unlink(*paths)
220
- paths.each do |path|
221
- fs.unlink(path)
222
- end
229
+ paths.each { |path| fs.unlink(path) }
223
230
  paths.size
224
231
  end
225
- class << self; alias_method :delete, :unlink; end
232
+ class << self; alias delete unlink; end
226
233
 
227
234
  def self.utime(atime, mtime, *file_names)
228
235
  file_names.each do |file_name|
@@ -232,18 +239,20 @@ module MemFs
232
239
  file_names.size
233
240
  end
234
241
 
235
- attr_reader :path
242
+ attr_reader :path # rubocop:disable Lint/DuplicateMethods
236
243
 
244
+ # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
237
245
  def initialize(filename, mode = File::RDONLY, *perm_and_or_opt)
246
+ super()
247
+
238
248
  opt = perm_and_or_opt.last.is_a?(Hash) ? perm_and_or_opt.pop : {}
239
249
  perm_and_or_opt.shift
240
- if perm_and_or_opt.any?
241
- fail ArgumentError, 'wrong number of arguments (4 for 1..3)'
242
- end
250
+
251
+ fail ArgumentError, 'wrong number of arguments (4 for 1..3)' if perm_and_or_opt.any?
243
252
 
244
253
  @path = filename
245
- @external_encoding = opt[:external_encoding] &&
246
- Encoding.find(opt[:external_encoding])
254
+ @external_encoding =
255
+ opt[:external_encoding] && Encoding.find(opt[:external_encoding])
247
256
 
248
257
  self.closed = false
249
258
  self.opening_mode = str_to_mode_int(mode)
@@ -254,16 +263,19 @@ module MemFs
254
263
  # FIXME: this is an ugly way to ensure a symlink has a target
255
264
  entry.dereferenced
256
265
 
257
- if entry.respond_to?(:pos=)
258
- entry.pos = 0
259
- end
266
+ entry.pos = 0 if entry.respond_to?(:pos=)
260
267
  entry.content.clear if truncate_file?
261
268
  end
269
+ # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
262
270
 
263
271
  def atime
264
272
  File.atime(path)
265
273
  end
266
274
 
275
+ def birthtime
276
+ File.birthtime(path)
277
+ end
278
+
267
279
  def chmod(mode_int)
268
280
  fs.chmod(mode_int, path)
269
281
  SUCCESS
@@ -300,11 +312,7 @@ module MemFs
300
312
 
301
313
  def self.dereference_name(path)
302
314
  entry = fs.find(path)
303
- if entry
304
- entry.dereferenced_name
305
- else
306
- basename(path)
307
- end
315
+ entry ? entry.dereferenced_name : basename(path)
308
316
  end
309
317
  private_class_method :dereference_name
310
318
 
@@ -328,16 +336,16 @@ module MemFs
328
336
  end
329
337
  private_class_method :original_file_class
330
338
 
331
- def self.stat_query(path, query, force_boolean = true)
339
+ def self.stat_query(path, query, force_boolean: true)
332
340
  response = fs.find(path) && stat(path).public_send(query)
333
341
  force_boolean ? !!response : response
334
342
  end
335
343
  private_class_method :stat_query
336
344
 
337
- def self.lstat_query(path, query)
345
+ def self.lstat_query?(path, query)
338
346
  response = fs.find(path) && lstat(path).public_send(query)
339
347
  !!response
340
348
  end
341
- private_class_method :lstat_query
349
+ private_class_method :lstat_query?
342
350
  end
343
351
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'singleton'
2
4
  require 'memfs/fake/directory'
3
5
  require 'memfs/fake/file'
@@ -7,9 +9,9 @@ module MemFs
7
9
  class FileSystem
8
10
  include Singleton
9
11
 
10
- attr_accessor :working_directory
11
- attr_accessor :registred_entries
12
- attr_accessor :root
12
+ attr_accessor :registred_entries,
13
+ :root,
14
+ :working_directory
13
15
 
14
16
  def basename(path)
15
17
  File.basename(path)
@@ -27,9 +29,10 @@ module MemFs
27
29
  end
28
30
 
29
31
  def clear!
30
- self.root = Fake::Directory.new('/')
31
- mkdir '/tmp'
32
- chdir '/'
32
+ MemFs.reset_platform_root!
33
+ self.root = Fake::Directory.new(MemFs.platform_root)
34
+ mkdir File.join(MemFs.platform_root, 'tmp')
35
+ chdir MemFs.platform_root
33
36
  end
34
37
 
35
38
  def chmod(mode_int, file_name)
@@ -51,7 +54,9 @@ module MemFs
51
54
  end
52
55
 
53
56
  def find(path)
54
- if path == '/'
57
+ path = MemFs.normalize_path(path)
58
+
59
+ if MemFs.root_path?(path)
55
60
  root
56
61
  elsif dirname(path) == '.'
57
62
  working_directory.find(path)
@@ -80,7 +85,7 @@ module MemFs
80
85
  def getwd
81
86
  working_directory.path
82
87
  end
83
- alias_method :pwd, :getwd
88
+ alias pwd getwd
84
89
 
85
90
  def initialize
86
91
  clear!
@@ -96,7 +101,7 @@ module MemFs
96
101
  find_parent!(new_name).add_entry link
97
102
  end
98
103
 
99
- def mkdir(path, mode = 0777)
104
+ def mkdir(path, mode = 0o777)
100
105
  fail Errno::EEXIST, path if find(path)
101
106
  directory = Fake::Directory.new(path)
102
107
  directory.mode = mode
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module MemFs
2
4
  module FilesystemAccess
3
5
  private
data/lib/memfs/io.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'forwardable'
2
4
  require 'memfs/filesystem_access'
3
5
 
@@ -6,23 +8,17 @@ module MemFs
6
8
  extend SingleForwardable
7
9
  include OriginalFile::Constants
8
10
 
9
- (OriginalIO.constants - OriginalFile::Constants.constants)
10
- .each do |const_name|
11
- self.const_set(const_name, OriginalIO.const_get(const_name))
12
- end
11
+ (OriginalIO.constants - OriginalFile::Constants.constants).each do |const_name|
12
+ const_set(const_name, OriginalIO.const_get(const_name))
13
+ end
13
14
 
14
15
  def_delegators :original_io_class,
15
16
  :copy_stream
16
17
 
17
18
  def self.read(path, *args)
18
19
  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]]
20
+ options = { encoding: nil, mode: File::RDONLY, open_args: nil }.merge(options)
21
+ open_args = options[:open_args] || [options[:mode], { encoding: options[:encoding] }]
26
22
 
27
23
  length, offset = args
28
24
 
@@ -30,11 +26,12 @@ module MemFs
30
26
  file.seek(offset || 0)
31
27
  file.read(length)
32
28
  ensure
33
- file.close if file
29
+ file&.close
34
30
  end
35
31
 
32
+ # rubocop:disable Metrics/MethodLength
36
33
  def self.write(path, string, offset = 0, open_args = nil)
37
- open_args ||= [File::WRONLY, encoding: nil]
34
+ open_args ||= [File::WRONLY, { encoding: nil }]
38
35
 
39
36
  offset = 0 if offset.nil?
40
37
  unless offset.respond_to?(:to_int)
@@ -42,17 +39,17 @@ module MemFs
42
39
  end
43
40
  offset = offset.to_int
44
41
 
45
- if offset > 0
46
- fail NotImplementedError,
47
- 'MemFs::IO.write with offset not yet supported.'
42
+ if offset.positive?
43
+ fail(NotImplementedError, 'MemFs::IO.write with offset not yet supported.')
48
44
  end
49
45
 
50
46
  file = open(path, *open_args)
51
47
  file.seek(offset)
52
48
  file.write(string)
53
49
  ensure
54
- file.close if file
50
+ file&.close
55
51
  end
52
+ # rubocop:enable Metrics/MethodLength
56
53
 
57
54
  def self.original_io_class
58
55
  MemFs::OriginalIO
@@ -60,7 +57,7 @@ module MemFs
60
57
  private_class_method :original_io_class
61
58
 
62
59
  attr_writer :autoclose,
63
- :close_on_exec
60
+ :close_on_exec
64
61
 
65
62
  def <<(object)
66
63
  fail IOError, 'not opened for writing' unless writable?
@@ -69,18 +66,11 @@ module MemFs
69
66
  end
70
67
 
71
68
  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
69
+ advice_types = %i[dontneed noreuse normal random sequential willneed]
70
+
71
+ return if advice_types.include?(advice_type)
72
+
73
+ fail NotImplementedError, "Unsupported advice: #{advice_type.inspect}"
84
74
  end
85
75
 
86
76
  def autoclose?
@@ -112,7 +102,7 @@ module MemFs
112
102
  def eof?
113
103
  pos >= content.size
114
104
  end
115
- alias_method :eof, :eof?
105
+ alias eof eof?
116
106
 
117
107
  def external_encoding
118
108
  if writable?
@@ -122,28 +112,32 @@ module MemFs
122
112
  end
123
113
  end
124
114
 
125
- def each(sep = $/)
126
- return to_enum(__callee__) unless block_given?
115
+ def each(sep = $/, &block)
116
+ return to_enum(__callee__, sep) unless block_given?
127
117
  fail IOError, 'not opened for reading' unless readable?
128
- content.each_line(sep) { |line| yield(line) }
118
+ content.each_line(sep, &block)
129
119
  self
130
120
  end
131
121
 
132
- def each_byte
122
+ def each_byte(&block)
133
123
  return to_enum(__callee__) unless block_given?
134
124
  fail IOError, 'not opened for reading' unless readable?
135
- content.each_byte { |byte| yield(byte) }
125
+ content.each_byte(&block)
136
126
  self
137
127
  end
138
- alias_method :bytes, :each_byte
128
+ alias bytes each_byte
139
129
 
140
- def each_char
130
+ def each_char(&block)
141
131
  return to_enum(__callee__) unless block_given?
142
132
  fail IOError, 'not opened for reading' unless readable?
143
- content.each_char { |char| yield(char) }
133
+ content.each_char(&block)
144
134
  self
145
135
  end
146
- alias_method :chars, :each_char
136
+ alias chars each_char
137
+
138
+ def fileno
139
+ entry.fileno
140
+ end
147
141
 
148
142
  def pos
149
143
  entry.pos
@@ -165,22 +159,22 @@ module MemFs
165
159
  content.puts text
166
160
  end
167
161
 
168
- def read(length = nil, buffer = '')
169
- unless entry
170
- fail(Errno::ENOENT, path)
171
- end
162
+ def read(length = nil, buffer = +'')
163
+ fail(Errno::ENOENT, path) unless entry
164
+
172
165
  default = length ? nil : ''
173
166
  content.read(length, buffer) || default
174
167
  end
175
168
 
176
169
  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
170
+ new_pos =
171
+ case whence
172
+ when ::IO::SEEK_CUR then entry.pos + amount
173
+ when ::IO::SEEK_END then content.to_s.length + amount
174
+ when ::IO::SEEK_SET then amount
175
+ end
182
176
 
183
- fail Errno::EINVAL, path if new_pos.nil? || new_pos < 0
177
+ fail Errno::EINVAL, path if new_pos.nil? || new_pos.negative?
184
178
 
185
179
  entry.pos = new_pos
186
180
  0
@@ -191,7 +185,7 @@ module MemFs
191
185
  end
192
186
 
193
187
  def write(string)
194
- fail IOError, 'not opened for writing' unless writable?
188
+ fail(IOError, 'not opened for writing') unless writable?
195
189
 
196
190
  content.write(string.to_s)
197
191
  end
@@ -199,8 +193,8 @@ module MemFs
199
193
  private
200
194
 
201
195
  attr_accessor :closed,
202
- :entry,
203
- :opening_mode
196
+ :entry,
197
+ :opening_mode
204
198
 
205
199
  attr_reader :path
206
200
 
@@ -220,7 +214,7 @@ module MemFs
220
214
  def str_to_mode_int(mode)
221
215
  return mode unless mode.is_a?(String)
222
216
 
223
- unless mode =~ /\A([rwa]\+?)([bt])?(:bom)?(\|.+)?\z/
217
+ unless mode =~ /\A([rwa]\+?)([bt])?(:(bom|UTF-8|utf-8))?(\|.+)?\z/
224
218
  fail ArgumentError, "invalid access mode #{mode}"
225
219
  end
226
220
 
data/lib/memfs/version.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module MemFs
2
- VERSION = '1.0.0'.freeze
4
+ VERSION = '2.0.0'
3
5
  end
data/lib/memfs.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'memfs/version'
2
4
  require 'fileutils'
3
5
 
@@ -25,6 +27,86 @@ module MemFs
25
27
  # Keeps track of the original Ruby IO class.
26
28
  OriginalIO = ::IO
27
29
 
30
+ def self.ruby_version_gte?(version) # :nodoc:
31
+ Gem::Version.new(RUBY_VERSION) >= Gem::Version.new(version)
32
+ end
33
+
34
+ def self.windows?
35
+ /mswin|bccwin|mingw/ =~ RUBY_PLATFORM
36
+ end
37
+
38
+ # Returns the platform-specific root path (e.g., '/' on Unix, 'D:/' on Windows)
39
+ def self.platform_root
40
+ @platform_root || default_platform_root
41
+ end
42
+
43
+ # Allows setting a custom platform root (mainly for testing)
44
+ def self.platform_root=(value)
45
+ @platform_root = value
46
+ end
47
+
48
+ # Resets platform_root to the default value
49
+ def self.reset_platform_root!
50
+ @platform_root = nil
51
+ end
52
+
53
+ # Returns the default platform root based on the current OS
54
+ def self.default_platform_root
55
+ if windows?
56
+ # Normalize drive letter to uppercase
57
+ OriginalFile.expand_path('/').sub(/\A([a-z]):/) { "#{::Regexp.last_match(1).upcase}:" }
58
+ else
59
+ '/'
60
+ end
61
+ end
62
+
63
+ # Check if a path is the root path (handles both '/' and 'D:/')
64
+ def self.root_path?(path)
65
+ return false if path.nil?
66
+
67
+ normalized = normalize_path(path)
68
+ normalized == platform_root || normalized == '/'
69
+ end
70
+
71
+ # Normalize path for consistent handling
72
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
73
+ # rubocop:disable Metrics/PerceivedComplexity
74
+ def self.normalize_path(path)
75
+ return path unless path.is_a?(String)
76
+
77
+ # Reject UNC paths
78
+ fail ArgumentError, "UNC paths are not supported: #{path}" if path.start_with?('\\\\', '//')
79
+
80
+ # Convert backslashes to forward slashes
81
+ path = path.tr('\\', '/')
82
+
83
+ return path unless windows?
84
+
85
+ # Normalize drive letter to uppercase
86
+ path = path.sub(/\A([a-z]):/) { "#{::Regexp.last_match(1).upcase}:" }
87
+
88
+ # Handle drive-relative paths like 'D:foo' or 'D:.' (no slash after colon)
89
+ # and bare drive letters like 'D:' (current directory on drive D)
90
+ # Convert to absolute paths since our fake fs doesn't support per-drive working directories
91
+ if path.match?(/\A[A-Z]:\z/) # Bare drive like 'D:'
92
+ path = "#{path}/"
93
+ elsif path.match?(%r{\A[A-Z]:[^/]}) # Drive-relative like 'D:foo' or 'D:.'
94
+ path = path.sub(/\A([A-Z]):/, '\1:/')
95
+ end
96
+
97
+ # Convert bare '/' to platform root on Windows
98
+ if path == '/'
99
+ platform_root
100
+ elsif path.start_with?('/') && !path.match?(%r{\A[A-Z]:/})
101
+ # Convert '/foo' to 'D:/foo' on Windows
102
+ "#{platform_root}#{path[1..]}"
103
+ else
104
+ path
105
+ end
106
+ end
107
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
108
+ # rubocop:enable Metrics/PerceivedComplexity
109
+
28
110
  require 'memfs/file_system'
29
111
  require 'memfs/dir'
30
112
  require 'memfs/file'
@@ -133,9 +215,7 @@ module MemFs
133
215
  #
134
216
  # @return nothing.
135
217
  def touch(*paths)
136
- if ::File != MemFs::File
137
- fail 'Always call MemFs.touch inside a MemFs active context.'
138
- end
218
+ fail 'Always call MemFs.touch inside a MemFs active context.' if ::File != MemFs::File
139
219
 
140
220
  paths.each do |path|
141
221
  FileUtils.mkdir_p File.dirname(path)
data/memfs.gemspec CHANGED
@@ -12,23 +12,6 @@ Gem::Specification.new do |gem|
12
12
  'for tests. Strongly inspired by FakeFS.'
13
13
  gem.summary = "memfs-#{MemFs::VERSION}"
14
14
  gem.homepage = 'http://github.com/simonc/memfs'
15
-
16
15
  gem.license = 'MIT'
17
-
18
16
  gem.files = `git ls-files`.split($/)
19
- gem.executables = gem.files.grep(/^bin\//).map { |f| File.basename(f) }
20
- gem.test_files = gem.files.grep(/^(test|spec|features)\//)
21
- gem.require_paths = ['lib']
22
-
23
- gem.add_development_dependency 'coveralls', '~> 0.6'
24
- gem.add_development_dependency 'rake', '~> 12.0'
25
- gem.add_development_dependency 'rspec', '~> 3.0'
26
- gem.add_development_dependency 'guard', '~> 2.6'
27
- gem.add_development_dependency 'guard-rspec', '~> 4.3'
28
- gem.add_development_dependency 'rb-inotify', '~> 0.8'
29
- gem.add_development_dependency 'rb-fsevent', '~> 0.9'
30
- gem.add_development_dependency 'rb-fchange', '~> 0.0'
31
-
32
- listen_version = RUBY_VERSION >= '2.2.3' ? '~> 3.1' : '~> 3.0.7'
33
- gem.add_development_dependency 'listen', listen_version
34
17
  end