dry-cli 0.6.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.
@@ -1,444 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'pathname'
4
- require 'fileutils'
5
- require 'backports/2.4.0/string/match' if RUBY_VERSION < '2.4'
6
-
7
- module Dry
8
- class CLI
9
- module Utils
10
- # Files utilities
11
- #
12
- # @since 0.3.1
13
- module Files # rubocop:disable Metrics/ModuleLength
14
- # Creates an empty file for the given path.
15
- # All the intermediate directories are created.
16
- # If the path already exists, it doesn't change the contents
17
- #
18
- # @param path [String,Pathname] the path to file
19
- #
20
- # @since 0.3.1
21
- def self.touch(path)
22
- mkdir_p(path)
23
- FileUtils.touch(path)
24
- end
25
-
26
- # Creates a new file or rewrites the contents
27
- # of an existing file for the given path and content
28
- # All the intermediate directories are created.
29
- #
30
- # @param path [String,Pathname] the path to file
31
- # @param content [String, Array<String>] the content to write
32
- #
33
- # @since 0.3.1
34
- def self.write(path, *content)
35
- mkdir_p(path)
36
- open(path, ::File::CREAT | ::File::WRONLY | ::File::TRUNC, *content) # rubocop:disable LineLength, Security/Open - this isn't a call to `::Kernel.open`, but to `self.open`
37
- end
38
-
39
- # Copies source into destination.
40
- # All the intermediate directories are created.
41
- # If the destination already exists, it overrides the contents.
42
- #
43
- # @param source [String,Pathname] the path to the source file
44
- # @param destination [String,Pathname] the path to the destination file
45
- #
46
- # @since 0.3.1
47
- def self.cp(source, destination)
48
- mkdir_p(destination)
49
- FileUtils.cp(source, destination)
50
- end
51
-
52
- # Creates a directory for the given path.
53
- # It assumes that all the tokens in `path` are meant to be a directory.
54
- # All the intermediate directories are created.
55
- #
56
- # @param path [String,Pathname] the path to directory
57
- #
58
- # @since 0.3.1
59
- #
60
- # @see .mkdir_p
61
- #
62
- # @example
63
- # require "dry/cli/utils/files"
64
- #
65
- # Dry::CLI::Utils::Files.mkdir("path/to/directory")
66
- # # => creates the `path/to/directory` directory
67
- #
68
- # # WRONG this isn't probably what you want, check `.mkdir_p`
69
- # Dry::CLI::Utils::Files.mkdir("path/to/file.rb")
70
- # # => creates the `path/to/file.rb` directory
71
- def self.mkdir(path)
72
- FileUtils.mkdir_p(path)
73
- end
74
-
75
- # Creates a directory for the given path.
76
- # It assumes that all the tokens, but the last, in `path` are meant to be
77
- # a directory, whereas the last is meant to be a file.
78
- # All the intermediate directories are created.
79
- #
80
- # @param path [String,Pathname] the path to directory
81
- #
82
- # @since 0.3.1
83
- #
84
- # @see .mkdir
85
- #
86
- # @example
87
- # require "dry/cli/utils/files"
88
- #
89
- # Dry::CLI::Utils::Files.mkdir_p("path/to/file.rb")
90
- # # => creates the `path/to` directory, but NOT `file.rb`
91
- #
92
- # # WRONG it doesn't create the last directory, check `.mkdir`
93
- # Dry::CLI::Utils::Files.mkdir_p("path/to/directory")
94
- # # => creates the `path/to` directory
95
- def self.mkdir_p(path)
96
- Pathname.new(path).dirname.mkpath
97
- end
98
-
99
- # Deletes given path (file).
100
- #
101
- # @param path [String,Pathname] the path to file
102
- #
103
- # @raise [Errno::ENOENT] if the path doesn't exist
104
- #
105
- # @since 0.3.1
106
- def self.delete(path)
107
- FileUtils.rm(path)
108
- end
109
-
110
- # Deletes given path (directory).
111
- #
112
- # @param path [String,Pathname] the path to file
113
- #
114
- # @raise [Errno::ENOENT] if the path doesn't exist
115
- #
116
- # @since 0.3.1
117
- def self.delete_directory(path)
118
- FileUtils.remove_entry_secure(path)
119
- end
120
-
121
- # Adds a new line at the top of the file
122
- #
123
- # @param path [String,Pathname] the path to file
124
- # @param line [String] the line to add
125
- #
126
- # @raise [Errno::ENOENT] if the path doesn't exist
127
- #
128
- # @see .append
129
- #
130
- # @since 0.3.1
131
- def self.unshift(path, line)
132
- content = ::File.readlines(path)
133
- content.unshift("#{line}\n")
134
-
135
- write(path, content)
136
- end
137
-
138
- # Adds a new line at the bottom of the file
139
- #
140
- # @param path [String,Pathname] the path to file
141
- # @param contents [String] the contents to add
142
- #
143
- # @raise [Errno::ENOENT] if the path doesn't exist
144
- #
145
- # @see .unshift
146
- #
147
- # @since 0.3.1
148
- def self.append(path, contents)
149
- mkdir_p(path)
150
-
151
- content = ::File.readlines(path)
152
- content << "\n" unless content.last.end_with?("\n")
153
- content << "#{contents}\n"
154
-
155
- write(path, content)
156
- end
157
-
158
- # Replace first line in `path` that contains `target` with `replacement`.
159
- #
160
- # @param path [String,Pathname] the path to file
161
- # @param target [String,Regexp] the target to replace
162
- # @param replacement [String] the replacement
163
- #
164
- # @raise [Errno::ENOENT] if the path doesn't exist
165
- # @raise [ArgumentError] if `target` cannot be found in `path`
166
- #
167
- # @see .replace_last_line
168
- #
169
- # @since 0.3.1
170
- def self.replace_first_line(path, target, replacement)
171
- content = ::File.readlines(path)
172
- content[index(content, path, target)] = "#{replacement}\n"
173
-
174
- write(path, content)
175
- end
176
-
177
- # Replace last line in `path` that contains `target` with `replacement`.
178
- #
179
- # @param path [String,Pathname] the path to file
180
- # @param target [String,Regexp] the target to replace
181
- # @param replacement [String] the replacement
182
- #
183
- # @raise [Errno::ENOENT] if the path doesn't exist
184
- # @raise [ArgumentError] if `target` cannot be found in `path`
185
- #
186
- # @see .replace_first_line
187
- #
188
- # @since 0.3.1
189
- def self.replace_last_line(path, target, replacement)
190
- content = ::File.readlines(path)
191
- content[-index(content.reverse, path, target) - 1] = "#{replacement}\n"
192
-
193
- write(path, content)
194
- end
195
-
196
- # Inject `contents` in `path` before `target`.
197
- #
198
- # @param path [String,Pathname] the path to file
199
- # @param target [String,Regexp] the target to replace
200
- # @param contents [String] the contents to inject
201
- #
202
- # @raise [Errno::ENOENT] if the path doesn't exist
203
- # @raise [ArgumentError] if `target` cannot be found in `path`
204
- #
205
- # @see .inject_line_after
206
- # @see .inject_line_before_last
207
- # @see .inject_line_after_last
208
- #
209
- # @since 0.3.1
210
- def self.inject_line_before(path, target, contents)
211
- _inject_line_before(path, target, contents, method(:index))
212
- end
213
-
214
- # Inject `contents` in `path` after last `target`.
215
- #
216
- # @param path [String,Pathname] the path to file
217
- # @param target [String,Regexp] the target to replace
218
- # @param contents [String] the contents to inject
219
- #
220
- # @raise [Errno::ENOENT] if the path doesn't exist
221
- # @raise [ArgumentError] if `target` cannot be found in `path`
222
- #
223
- # @see .inject_line_before
224
- # @see .inject_line_after
225
- # @see .inject_line_after_last
226
- #
227
- # @since 1.3.0
228
- def self.inject_line_before_last(path, target, contents)
229
- _inject_line_before(path, target, contents, method(:rindex))
230
- end
231
-
232
- # Inject `contents` in `path` after `target`.
233
- #
234
- # @param path [String,Pathname] the path to file
235
- # @param target [String,Regexp] the target to replace
236
- # @param contents [String] the contents to inject
237
- #
238
- # @raise [Errno::ENOENT] if the path doesn't exist
239
- # @raise [ArgumentError] if `target` cannot be found in `path`
240
- #
241
- # @see .inject_line_before
242
- # @see .inject_line_before_last
243
- # @see .inject_line_after_last
244
- #
245
- # @since 0.3.1
246
- def self.inject_line_after(path, target, contents)
247
- _inject_line_after(path, target, contents, method(:index))
248
- end
249
-
250
- # Inject `contents` in `path` after last `target`.
251
- #
252
- # @param path [String,Pathname] the path to file
253
- # @param target [String,Regexp] the target to replace
254
- # @param contents [String] the contents to inject
255
- #
256
- # @raise [Errno::ENOENT] if the path doesn't exist
257
- # @raise [ArgumentError] if `target` cannot be found in `path`
258
- #
259
- # @see .inject_line_before
260
- # @see .inject_line_after
261
- # @see .inject_line_before_last
262
- # @see .inject_line_after_last
263
- #
264
- # @since 1.3.0
265
- def self.inject_line_after_last(path, target, contents)
266
- _inject_line_after(path, target, contents, method(:rindex))
267
- end
268
-
269
- # Removes line from `path`, matching `target`.
270
- #
271
- # @param path [String,Pathname] the path to file
272
- # @param target [String,Regexp] the target to remove
273
- #
274
- # @raise [Errno::ENOENT] if the path doesn't exist
275
- # @raise [ArgumentError] if `target` cannot be found in `path`
276
- #
277
- # @since 0.3.1
278
- def self.remove_line(path, target)
279
- content = ::File.readlines(path)
280
- i = index(content, path, target)
281
-
282
- content.delete_at(i)
283
- write(path, content)
284
- end
285
-
286
- # Removes `target` block from `path`
287
- #
288
- # @param path [String,Pathname] the path to file
289
- # @param target [String] the target block to remove
290
- #
291
- # @raise [Errno::ENOENT] if the path doesn't exist
292
- # @raise [ArgumentError] if `target` cannot be found in `path`
293
- #
294
- # @since 0.3.1
295
- #
296
- # @example
297
- # require "dry/cli/utils/files"
298
- #
299
- # puts File.read("app.rb")
300
- #
301
- # # class App
302
- # # configure do
303
- # # root __dir__
304
- # # end
305
- # # end
306
- #
307
- # Dry::CLI::Utils::Files.remove_block("app.rb", "configure")
308
- #
309
- # puts File.read("app.rb")
310
- #
311
- # # class App
312
- # # end
313
- def self.remove_block(path, target)
314
- content = ::File.readlines(path)
315
- starting = index(content, path, target)
316
- line = content[starting]
317
- size = line[/\A[[:space:]]*/].bytesize
318
- closing = (' ' * size) + (target.match?(/{/) ? '}' : 'end')
319
- ending = starting + index(content[starting..-1], path, closing)
320
-
321
- content.slice!(starting..ending)
322
- write(path, content)
323
-
324
- remove_block(path, target) if match?(content, target)
325
- end
326
-
327
- # Checks if `path` exist
328
- #
329
- # @param path [String,Pathname] the path to file
330
- #
331
- # @return [TrueClass,FalseClass] the result of the check
332
- #
333
- # @since 0.3.1
334
- #
335
- # @example
336
- # require "dry/cli/utils/files"
337
- #
338
- # Dry::CLI::Utils::Files.exist?(__FILE__) # => true
339
- # Dry::CLI::Utils::Files.exist?(__dir__) # => true
340
- #
341
- # Dry::CLI::Utils::Files.exist?("missing_file") # => false
342
- def self.exist?(path)
343
- File.exist?(path)
344
- end
345
-
346
- # Checks if `path` is a directory
347
- #
348
- # @param path [String,Pathname] the path to directory
349
- #
350
- # @return [TrueClass,FalseClass] the result of the check
351
- #
352
- # @since 0.3.1
353
- #
354
- # @example
355
- # require "dry/cli/utils/files"
356
- #
357
- # Dry::CLI::Utils::Files.directory?(__dir__) # => true
358
- # Dry::CLI::Utils::Files.directory?(__FILE__) # => false
359
- #
360
- # Dry::CLI::Utils::Files.directory?("missing_directory") # => false
361
- def self.directory?(path)
362
- File.directory?(path)
363
- end
364
-
365
- # private
366
-
367
- # @since 0.3.1
368
- # @api private
369
- def self.match?(content, target)
370
- !line_number(content, target).nil?
371
- end
372
-
373
- private_class_method :match?
374
-
375
- # @since 0.3.1
376
- # @api private
377
- def self.open(path, mode, *content)
378
- ::File.open(path, mode) do |file|
379
- file.write(Array(content).flatten.join)
380
- end
381
- end
382
-
383
- private_class_method :open
384
-
385
- # @since 0.3.1
386
- # @api private
387
- def self.index(content, path, target)
388
- line_number(content, target) ||
389
- raise(ArgumentError, "Cannot find `#{target}' inside `#{path}'.")
390
- end
391
-
392
- private_class_method :index
393
-
394
- # @since 1.3.0
395
- # @api private
396
- def self.rindex(content, path, target)
397
- line_number(content, target, finder: content.method(:rindex)) ||
398
- raise(ArgumentError, "Cannot find `#{target}' inside `#{path}'.")
399
- end
400
-
401
- private_class_method :rindex
402
-
403
- # @since 1.3.0
404
- # @api private
405
- def self._inject_line_before(path, target, contents, finder)
406
- content = ::File.readlines(path)
407
- i = finder.call(content, path, target)
408
-
409
- content.insert(i, "#{contents}\n")
410
- write(path, content)
411
- end
412
-
413
- private_class_method :_inject_line_before
414
-
415
- # @since 1.3.0
416
- # @api private
417
- def self._inject_line_after(path, target, contents, finder)
418
- content = ::File.readlines(path)
419
- i = finder.call(content, path, target)
420
-
421
- content.insert(i + 1, "#{contents}\n")
422
- write(path, content)
423
- end
424
-
425
- private_class_method :_inject_line_after
426
-
427
- # @since 0.3.1
428
- # @api private
429
- def self.line_number(content, target, finder: content.method(:index))
430
- finder.call do |l|
431
- case target
432
- when ::String
433
- l.include?(target)
434
- when Regexp
435
- l =~ target
436
- end
437
- end
438
- end
439
-
440
- private_class_method :line_number
441
- end
442
- end
443
- end
444
- end