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