dry-files 0.1.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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +37 -0
- data/LICENSE +20 -0
- data/README.md +29 -0
- data/dry-files.gemspec +34 -0
- data/lib/dry-files.rb +3 -0
- data/lib/dry/files.rb +860 -0
- data/lib/dry/files/adapter.rb +21 -0
- data/lib/dry/files/error.rb +120 -0
- data/lib/dry/files/file_system.rb +377 -0
- data/lib/dry/files/memory_file_system.rb +429 -0
- data/lib/dry/files/memory_file_system/node.rb +246 -0
- data/lib/dry/files/path.rb +112 -0
- data/lib/dry/files/version.rb +7 -0
- metadata +102 -0
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dry
|
4
|
+
class Files
|
5
|
+
# @since 0.1.0
|
6
|
+
# @api private
|
7
|
+
class Adapter
|
8
|
+
# @since 0.1.0
|
9
|
+
# @api private
|
10
|
+
def self.call(memory:)
|
11
|
+
if memory
|
12
|
+
require_relative "./memory_file_system"
|
13
|
+
MemoryFileSystem.new
|
14
|
+
else
|
15
|
+
require_relative "./file_system"
|
16
|
+
FileSystem.new
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dry
|
4
|
+
class Files
|
5
|
+
# Dry::Files base error
|
6
|
+
#
|
7
|
+
# @since 0.1.0
|
8
|
+
# @api public
|
9
|
+
class Error < StandardError
|
10
|
+
end
|
11
|
+
|
12
|
+
# Wraps low level I/O errors
|
13
|
+
#
|
14
|
+
# @see https://ruby-doc.org/core/Errno.html
|
15
|
+
#
|
16
|
+
# @since 0.1.0
|
17
|
+
# @api public
|
18
|
+
class IOError < Error
|
19
|
+
# Instantiate a new `Dry::Files::IOError`
|
20
|
+
#
|
21
|
+
# @param cause [Exception] the low level exception
|
22
|
+
#
|
23
|
+
# @return [Dry::Files::IOError] the new error
|
24
|
+
#
|
25
|
+
# @since 0.1.0
|
26
|
+
# @api private
|
27
|
+
def initialize(cause)
|
28
|
+
super(cause.message)
|
29
|
+
@_cause = cause
|
30
|
+
end
|
31
|
+
|
32
|
+
# The original exception
|
33
|
+
#
|
34
|
+
# @see https://ruby-doc.org/core/Exception.html#method-i-cause
|
35
|
+
#
|
36
|
+
# @since 0.1.0
|
37
|
+
# @api public
|
38
|
+
def cause
|
39
|
+
@_cause
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# File manipulations error.
|
44
|
+
# Raised when the given `target` cannot be found in `path`.
|
45
|
+
#
|
46
|
+
# @since 0.1.0
|
47
|
+
# @api public
|
48
|
+
class MissingTargetError < Error
|
49
|
+
# @param target [String,Regexp] the missing target
|
50
|
+
# @param path [String,Pathname] the file
|
51
|
+
#
|
52
|
+
# @return [Dry::Files::MissingTargetError] the new error
|
53
|
+
#
|
54
|
+
# @since 0.1.0
|
55
|
+
# @api private
|
56
|
+
def initialize(target, path)
|
57
|
+
super("cannot find `#{target}' in `#{path}'")
|
58
|
+
|
59
|
+
@_target = target
|
60
|
+
@_path = path
|
61
|
+
end
|
62
|
+
|
63
|
+
# The missing target
|
64
|
+
#
|
65
|
+
# @return [String, Regexp] the missing target
|
66
|
+
#
|
67
|
+
# @since 0.1.0
|
68
|
+
# @api public
|
69
|
+
def target
|
70
|
+
@_target
|
71
|
+
end
|
72
|
+
|
73
|
+
# The missing target
|
74
|
+
#
|
75
|
+
# @return [String,Pathname] the file
|
76
|
+
#
|
77
|
+
# @since 0.1.0
|
78
|
+
# @api public
|
79
|
+
def path
|
80
|
+
@_path
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# Unknown memory node
|
85
|
+
#
|
86
|
+
# Raised by the memory adapter (used for testing purposes)
|
87
|
+
#
|
88
|
+
# @since 0.1.0
|
89
|
+
# @api public
|
90
|
+
class UnknownMemoryNodeError < Error
|
91
|
+
# Instantiate a new error
|
92
|
+
#
|
93
|
+
# @param node [String] node name
|
94
|
+
#
|
95
|
+
# @since 0.1.0
|
96
|
+
# @api public
|
97
|
+
def initialize(node)
|
98
|
+
super("unknown memory node `#{node}'")
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# Not a memory file
|
103
|
+
#
|
104
|
+
# Raised by the memory adapter (used for testing purposes)
|
105
|
+
#
|
106
|
+
# @since 0.1.0
|
107
|
+
# @api public
|
108
|
+
class NotMemoryFileError < Error
|
109
|
+
# Instantiate a new error
|
110
|
+
#
|
111
|
+
# @param path [String] path name
|
112
|
+
#
|
113
|
+
# @since 0.1.0
|
114
|
+
# @api public
|
115
|
+
def initialize(path)
|
116
|
+
super("not a memory file `#{path}'")
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
@@ -0,0 +1,377 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "fileutils"
|
4
|
+
|
5
|
+
module Dry
|
6
|
+
class Files
|
7
|
+
# File System abstraction to support `Dry::Files`
|
8
|
+
#
|
9
|
+
# @since 0.1.0
|
10
|
+
# @api private
|
11
|
+
class FileSystem
|
12
|
+
# @since 0.1.0
|
13
|
+
# @api private
|
14
|
+
attr_reader :file
|
15
|
+
|
16
|
+
# @since 0.1.0
|
17
|
+
# @api private
|
18
|
+
attr_reader :file_utils
|
19
|
+
|
20
|
+
# Creates a new instance
|
21
|
+
#
|
22
|
+
# @param file [Class]
|
23
|
+
# @param file_utils [Class]
|
24
|
+
#
|
25
|
+
# @return [Dry::Files::FileSystem]
|
26
|
+
#
|
27
|
+
# @since 0.1.0
|
28
|
+
def initialize(file: File, file_utils: FileUtils)
|
29
|
+
@file = file
|
30
|
+
@file_utils = file_utils
|
31
|
+
end
|
32
|
+
|
33
|
+
# Opens (or creates) a new file for both read/write operations.
|
34
|
+
#
|
35
|
+
# If the file doesn't exist, it creates a new one.
|
36
|
+
#
|
37
|
+
# @see https://ruby-doc.org/core/File.html#method-c-open
|
38
|
+
#
|
39
|
+
# @param path [String] the target file
|
40
|
+
# @param mode [String,Integer] Ruby file open mode
|
41
|
+
# @param args [Array<Object>] ::File.open args
|
42
|
+
# @param blk [Proc] the block to yield
|
43
|
+
#
|
44
|
+
# @yieldparam [::File] the opened file
|
45
|
+
#
|
46
|
+
# @raise [Dry::Files::IOError] in case of I/O error
|
47
|
+
#
|
48
|
+
# @since 0.1.0
|
49
|
+
def open(path, mode = OPEN_MODE, *args, &blk)
|
50
|
+
touch(path)
|
51
|
+
|
52
|
+
with_error_handling do
|
53
|
+
file.open(path, mode, *args, &blk)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# Opens the file, optionally seeks to the given offset, then returns
|
58
|
+
# length bytes (defaulting to the rest of the file).
|
59
|
+
#
|
60
|
+
# Read ensures the file is closed before returning.
|
61
|
+
#
|
62
|
+
# @see https://ruby-doc.org/core/IO.html#method-c-read
|
63
|
+
#
|
64
|
+
# @param path [String, Array<String>] the target path
|
65
|
+
#
|
66
|
+
# @raise [Dry::Files::IOError] in case of I/O error
|
67
|
+
#
|
68
|
+
# @since 0.1.0
|
69
|
+
# @api private
|
70
|
+
def read(path, *args, **kwargs)
|
71
|
+
with_error_handling do
|
72
|
+
file.read(path, *args, **kwargs)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# Reads the entire file specified by name as individual lines,
|
77
|
+
# and returns those lines in an array
|
78
|
+
#
|
79
|
+
# @see https://ruby-doc.org/core/IO.html#method-c-readlines
|
80
|
+
#
|
81
|
+
# @param path [String] the file to read
|
82
|
+
#
|
83
|
+
# @raise [Dry::Files::IOError] in case of I/O error
|
84
|
+
#
|
85
|
+
# @since 0.1.0
|
86
|
+
# @api private
|
87
|
+
def readlines(path, *args)
|
88
|
+
with_error_handling do
|
89
|
+
file.readlines(path, *args)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# Updates modification time (mtime) and access time (atime) of file(s)
|
94
|
+
# in list.
|
95
|
+
#
|
96
|
+
# Files are created if they don’t exist.
|
97
|
+
#
|
98
|
+
# @see https://ruby-doc.org/stdlib/libdoc/fileutils/rdoc/FileUtils.html#method-c-touch
|
99
|
+
#
|
100
|
+
# @param path [String, Array<String>] the target path
|
101
|
+
#
|
102
|
+
# @raise [Dry::Files::IOError] in case of I/O error
|
103
|
+
#
|
104
|
+
# @since 0.1.0
|
105
|
+
# @api private
|
106
|
+
def touch(path, **kwargs)
|
107
|
+
raise IOError, Errno::EISDIR.new(path.to_s) if directory?(path)
|
108
|
+
|
109
|
+
with_error_handling do
|
110
|
+
mkdir_p(path)
|
111
|
+
file_utils.touch(path, **kwargs)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# Creates a new file or rewrites the contents
|
116
|
+
# of an existing file for the given path and content
|
117
|
+
# All the intermediate directories are created.
|
118
|
+
#
|
119
|
+
# @param path [String,Pathname] the path to file
|
120
|
+
# @param content [String, Array<String>] the content to write
|
121
|
+
#
|
122
|
+
# @raise [Dry::Files::IOError] in case of I/O error
|
123
|
+
#
|
124
|
+
# @since 0.1.0
|
125
|
+
# @api private
|
126
|
+
def write(path, *content)
|
127
|
+
mkdir_p(path)
|
128
|
+
|
129
|
+
self.open(path, WRITE_MODE) do |f|
|
130
|
+
f.write(Array(content).flatten.join)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
# Returns a new string formed by joining the strings using Operating
|
135
|
+
# System path separator
|
136
|
+
#
|
137
|
+
# @see https://ruby-doc.org/core/File.html#method-c-join
|
138
|
+
#
|
139
|
+
# @param path [Array<String,Pathname>] path tokens
|
140
|
+
#
|
141
|
+
# @return [String] the joined path
|
142
|
+
#
|
143
|
+
# @since 0.1.0
|
144
|
+
# @api private
|
145
|
+
def join(*path)
|
146
|
+
file.join(*path)
|
147
|
+
end
|
148
|
+
|
149
|
+
# Converts a path to an absolute path.
|
150
|
+
#
|
151
|
+
# @see https://ruby-doc.org/core/File.html#method-c-expand_path
|
152
|
+
#
|
153
|
+
# @param path [String,Pathname] the path to the file
|
154
|
+
# @param dir [String,Pathname] the base directory
|
155
|
+
#
|
156
|
+
# @since 0.1.0
|
157
|
+
# @api private
|
158
|
+
def expand_path(path, dir)
|
159
|
+
file.expand_path(path, dir)
|
160
|
+
end
|
161
|
+
|
162
|
+
# Returns the name of the current working directory.
|
163
|
+
#
|
164
|
+
# @see https://ruby-doc.org/stdlib/libdoc/fileutils/rdoc/FileUtils.html#method-c-pwd
|
165
|
+
#
|
166
|
+
# @return [String] the current working directory.
|
167
|
+
#
|
168
|
+
# @since 0.1.0
|
169
|
+
# @api private
|
170
|
+
def pwd
|
171
|
+
file_utils.pwd
|
172
|
+
end
|
173
|
+
|
174
|
+
# Temporary changes the current working directory of the process to the
|
175
|
+
# given path and yield the given block.
|
176
|
+
#
|
177
|
+
# The argument `path` is intended to be a **directory**.
|
178
|
+
#
|
179
|
+
# @see https://ruby-doc.org/stdlib-3.0.0/libdoc/fileutils/rdoc/FileUtils.html#method-i-cd
|
180
|
+
#
|
181
|
+
# @param path [String,Pathname] the target directory
|
182
|
+
# @param blk [Proc] the code to execute with the target directory
|
183
|
+
#
|
184
|
+
# @raise [Dry::Files::IOError] in case of I/O error
|
185
|
+
#
|
186
|
+
# @since 0.1.0
|
187
|
+
# @api private
|
188
|
+
def chdir(path, &blk)
|
189
|
+
with_error_handling do
|
190
|
+
file_utils.chdir(path, &blk)
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
# Creates a directory and all its parent directories.
|
195
|
+
#
|
196
|
+
# The argument `path` is intended to be a **directory** that you want to
|
197
|
+
# explicitly create.
|
198
|
+
#
|
199
|
+
# @see #mkdir_p
|
200
|
+
# @see https://ruby-doc.org/stdlib/libdoc/fileutils/rdoc/FileUtils.html#method-c-mkdir_p
|
201
|
+
#
|
202
|
+
# @param path [String] the directory to create
|
203
|
+
#
|
204
|
+
# @raise [Dry::Files::IOError] in case of I/O error
|
205
|
+
#
|
206
|
+
# @example
|
207
|
+
# require "dry/cli/utils/files/file_system"
|
208
|
+
#
|
209
|
+
# fs = Dry::Files::FileSystem.new
|
210
|
+
# fs.mkdir("/usr/var/project")
|
211
|
+
# # creates all the directory structure (/usr/var/project)
|
212
|
+
#
|
213
|
+
# @since 0.1.0
|
214
|
+
# @api private
|
215
|
+
def mkdir(path, **kwargs)
|
216
|
+
with_error_handling do
|
217
|
+
file_utils.mkdir_p(path, **kwargs)
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
# Creates a directory and all its parent directories.
|
222
|
+
#
|
223
|
+
# The argument `path` is intended to be a **file**, where its
|
224
|
+
# directory ancestors will be implicitly created.
|
225
|
+
#
|
226
|
+
# @see #mkdir
|
227
|
+
# @see https://ruby-doc.org/stdlib/libdoc/fileutils/rdoc/FileUtils.html#method-c-mkdir
|
228
|
+
#
|
229
|
+
# @param path [String] the file that will be in the directories that
|
230
|
+
# this method creates
|
231
|
+
#
|
232
|
+
# @raise [Dry::Files::IOError] in case of I/O error
|
233
|
+
#
|
234
|
+
# @example
|
235
|
+
# require "dry/cli/utils/files/file_system"
|
236
|
+
#
|
237
|
+
# fs = Dry::Files::FileSystem.new
|
238
|
+
# fs.mkdir("/usr/var/project/file.rb")
|
239
|
+
# # creates all the directory structure (/usr/var/project)
|
240
|
+
# # where file.rb will eventually live
|
241
|
+
#
|
242
|
+
# @since 0.1.0
|
243
|
+
# @api private
|
244
|
+
def mkdir_p(path, **kwargs)
|
245
|
+
mkdir(
|
246
|
+
file.dirname(path), **kwargs
|
247
|
+
)
|
248
|
+
end
|
249
|
+
|
250
|
+
# Copies file content from `source` to `destination`
|
251
|
+
# All the intermediate `destination` directories are created.
|
252
|
+
#
|
253
|
+
# @see https://ruby-doc.org/stdlib/libdoc/fileutils/rdoc/FileUtils.html#method-c-cp
|
254
|
+
#
|
255
|
+
# @param source [String] the file(s) or directory to copy
|
256
|
+
# @param destination [String] the directory destination
|
257
|
+
#
|
258
|
+
# @raise [Dry::Files::IOError] in case of I/O error
|
259
|
+
#
|
260
|
+
# @since 0.1.0
|
261
|
+
# @api private
|
262
|
+
def cp(source, destination, **kwargs)
|
263
|
+
mkdir_p(destination)
|
264
|
+
|
265
|
+
with_error_handling do
|
266
|
+
file_utils.cp(source, destination, **kwargs)
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
# Removes (deletes) a file
|
271
|
+
#
|
272
|
+
# @see https://ruby-doc.org/stdlib/libdoc/fileutils/rdoc/FileUtils.html#method-c-rm
|
273
|
+
#
|
274
|
+
# @see #rm_rf
|
275
|
+
#
|
276
|
+
# @param path [String] the file to remove
|
277
|
+
#
|
278
|
+
# @raise [Dry::Files::IOError] in case of I/O error
|
279
|
+
#
|
280
|
+
# @since 0.1.0
|
281
|
+
# @api private
|
282
|
+
def rm(path, **kwargs)
|
283
|
+
with_error_handling do
|
284
|
+
file_utils.rm(path, **kwargs)
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
288
|
+
# Removes (deletes) a directory
|
289
|
+
#
|
290
|
+
# @see https://ruby-doc.org/stdlib/libdoc/fileutils/rdoc/FileUtils.html#method-c-remove_entry_secure
|
291
|
+
#
|
292
|
+
# @param path [String] the directory to remove
|
293
|
+
#
|
294
|
+
# @raise [Dry::Files::IOError] in case of I/O error
|
295
|
+
#
|
296
|
+
# @since 0.1.0
|
297
|
+
# @api private
|
298
|
+
def rm_rf(path, *args)
|
299
|
+
with_error_handling do
|
300
|
+
file_utils.remove_entry_secure(path, *args)
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
304
|
+
# Check if the given path exist.
|
305
|
+
#
|
306
|
+
# @see https://ruby-doc.org/core/File.html#method-c-exist-3F
|
307
|
+
#
|
308
|
+
# @param path [String,Pathname] the file to check
|
309
|
+
#
|
310
|
+
# @return [TrueClass,FalseClass] the result of the check
|
311
|
+
#
|
312
|
+
# @since 0.1.0
|
313
|
+
# @api private
|
314
|
+
def exist?(path)
|
315
|
+
file.exist?(path)
|
316
|
+
end
|
317
|
+
|
318
|
+
# Check if the given path is a directory.
|
319
|
+
#
|
320
|
+
# @see https://ruby-doc.org/core/File.html#method-c-directory-3F
|
321
|
+
#
|
322
|
+
# @param path [String,Pathname] the directory to check
|
323
|
+
#
|
324
|
+
# @return [TrueClass,FalseClass] the result of the check
|
325
|
+
#
|
326
|
+
# @since 0.1.0
|
327
|
+
# @api private
|
328
|
+
def directory?(path)
|
329
|
+
file.directory?(path)
|
330
|
+
end
|
331
|
+
|
332
|
+
# Check if the given path is an executable.
|
333
|
+
#
|
334
|
+
# @see https://ruby-doc.org/core/File.html#method-c-executable-3F
|
335
|
+
#
|
336
|
+
# @param path [String,Pathname] the path to check
|
337
|
+
#
|
338
|
+
# @return [TrueClass,FalseClass] the result of the check
|
339
|
+
#
|
340
|
+
# @since 0.1.0
|
341
|
+
# @api private
|
342
|
+
def executable?(path)
|
343
|
+
file.executable?(path)
|
344
|
+
end
|
345
|
+
|
346
|
+
private
|
347
|
+
|
348
|
+
# @since 0.1.0
|
349
|
+
# @api private
|
350
|
+
OPEN_MODE = ::File::RDWR
|
351
|
+
private_constant :OPEN_MODE
|
352
|
+
|
353
|
+
# @since 0.1.0
|
354
|
+
# @api private
|
355
|
+
WRITE_MODE = (::File::CREAT | ::File::WRONLY | ::File::TRUNC).freeze
|
356
|
+
private_constant :WRITE_MODE
|
357
|
+
|
358
|
+
# Catch `SystemCallError` and re-raise a `Dry::Files::IOError`.
|
359
|
+
#
|
360
|
+
# `SystemCallError` is parent for all the `Errno::*` Ruby exceptions.
|
361
|
+
# These class of exceptions are raised in case of I/O error.
|
362
|
+
#
|
363
|
+
# @see https://ruby-doc.org/core/SystemCallError.html
|
364
|
+
# @see https://ruby-doc.org/core/Errno.html
|
365
|
+
#
|
366
|
+
# @raise [Dry::Files::IOError] in case of I/O error
|
367
|
+
#
|
368
|
+
# @since 0.1.0
|
369
|
+
# @api private
|
370
|
+
def with_error_handling
|
371
|
+
yield
|
372
|
+
rescue SystemCallError => e
|
373
|
+
raise IOError, e
|
374
|
+
end
|
375
|
+
end
|
376
|
+
end
|
377
|
+
end
|