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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +75 -37
- data/LICENSE +1 -1
- data/README.md +5 -4
- data/dry-cli.gemspec +15 -14
- data/lib/dry/cli/banner.rb +31 -21
- data/lib/dry/cli/command.rb +56 -25
- data/lib/dry/cli/command_registry.rb +59 -34
- data/lib/dry/cli/inflector.rb +1 -1
- data/lib/dry/cli/inline.rb +4 -4
- data/lib/dry/cli/option.rb +2 -2
- data/lib/dry/cli/parser.rb +16 -18
- data/lib/dry/cli/program_name.rb +1 -1
- data/lib/dry/cli/registry.rb +11 -6
- data/lib/dry/cli/usage.rb +11 -8
- data/lib/dry/cli/version.rb +1 -1
- data/lib/dry/cli.rb +53 -34
- metadata +19 -20
- data/lib/dry/cli/utils/files.rb +0 -444
data/lib/dry/cli/utils/files.rb
DELETED
@@ -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
|