Skip to content

Commit ef66718

Browse files
committed
Add stderr, stdin, and stdout support for Dry::CLI
Introduce `stderr`, `stdin`, and `stdout` streams to `Dry::CLI` and its components to better handle I/O. This allows commands to utilize specific input/output streams, improving flexibility and testability. Refactor existing methods to use the new stream parameters consistently.
1 parent a197817 commit ef66718

File tree

3 files changed

+72
-13
lines changed

3 files changed

+72
-13
lines changed

lib/dry/cli.rb

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -57,12 +57,13 @@ def initialize(command_or_registry = nil, &block)
5757
# Invoke the CLI
5858
#
5959
# @param arguments [Array<string>] the command line arguments (defaults to `ARGV`)
60-
# @param out [IO] the standard output (defaults to `$stdout`)
61-
# @param err [IO] the error output (defaults to `$stderr`)
60+
# @param stderr [IO] the error output (defaults to `$stderr`)
61+
# @param stdin [IO] the standard input (defaults to `$stdin`)
62+
# @param stdout [IO] the standard output (defaults to `$stdout`)
6263
#
6364
# @since 0.1.0
64-
def call(arguments: ARGV, out: $stdout, err: $stderr)
65-
@out, @err = out, err
65+
def call(arguments: ARGV, stderr: $stderr, stdin: $stdin, stdout: $stdout)
66+
@stderr, @stdin, @stdout = stderr, stdin, stdout
6667
kommand ? perform_command(arguments) : perform_registry(arguments)
6768
rescue SignalException => e
6869
signal_exception(e)
@@ -82,11 +83,15 @@ def call(arguments: ARGV, out: $stdout, err: $stderr)
8283

8384
# @since 0.6.0
8485
# @api private
85-
attr_reader :out
86+
attr_reader :stderr
87+
88+
# @since unreleased
89+
# @api private
90+
attr_reader :stdin
8691

8792
# @since 0.6.0
8893
# @api private
89-
attr_reader :err
94+
attr_reader :stdout
9095

9196
# Invoke the CLI if singular command passed
9297
#
@@ -145,29 +150,29 @@ def parse(command, arguments, names)
145150
# @since 0.6.0
146151
# @api private
147152
def build_command(command)
148-
command.is_a?(Class) ? command.new : command
153+
command.is_a?(Class) ? command.new(stderr:, stdin:, stdout:) : command
149154
end
150155

151156
# @since 0.6.0
152157
# @api private
153158
def help(command, prog_name)
154-
out.puts Banner.call(command, prog_name)
159+
stdout.puts Banner.call(command, prog_name)
155160
exit(0) # Successful exit
156161
end
157162

158163
# @since 0.6.0
159164
# @api private
160165
def error(result)
161-
err.puts(result.error)
166+
stderr.puts(result.error)
162167
exit(1)
163168
end
164169

165170
# @since 1.1.1
166171
def spell_checker(result, arguments)
167172
spell_checker = SpellChecker.call(result, arguments)
168-
err.puts spell_checker if spell_checker
173+
stderr.puts spell_checker if spell_checker
169174
puts
170-
err.puts Usage.call(result)
175+
stderr.puts Usage.call(result)
171176
exit(1)
172177
end
173178

lib/dry/cli/command.rb

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,14 @@ def self.superclass_options
366366
superclass_variable_dup(:@options)
367367
end
368368

369+
# @since unreleased
370+
# @api private
371+
def initialize(stderr: $stderr, stdin: $stdin, stdout: $stdout)
372+
@stderr = stderr
373+
@stdin = stdin
374+
@stdout = stdout
375+
end
376+
369377
extend Forwardable
370378

371379
delegate %i[
@@ -379,6 +387,52 @@ def self.superclass_options
379387
optional_arguments
380388
subcommands
381389
] => "self.class"
390+
391+
protected
392+
393+
# The error output used to print error messaging
394+
#
395+
# @example
396+
# class MyCommand
397+
# def call
398+
# stdout.puts "Hello World!"
399+
# exit(0)
400+
# rescue StandardError => e
401+
# stderr.puts "Uh oh: #{e.message}"
402+
# exit(1)
403+
# end
404+
# end
405+
#
406+
# @since unreleased
407+
# @return [IO]
408+
attr_reader :stderr
409+
410+
# The standard input stream used for reading input
411+
#
412+
# @example
413+
# class MyCommand
414+
# def call
415+
# name = stdin.gets.chomp
416+
# stdout.puts "Hello #{name}!"
417+
# end
418+
# end
419+
#
420+
# @since unreleased
421+
# @return [IO]
422+
attr_reader :stdin
423+
424+
# The standard output stream used for normal output
425+
#
426+
# @example
427+
# class MyCommand
428+
# def call
429+
# stdout.puts "Hello World!"
430+
# end
431+
# end
432+
#
433+
# @since unreleased
434+
# @return [IO]
435+
attr_reader :stdout
382436
end
383437
end
384438
end

lib/dry/cli/inline.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,13 +60,13 @@ module Inline
6060
# end
6161
#
6262
# @since 0.6.0
63-
def run(arguments: ARGV, out: $stdout)
63+
def run(arguments: ARGV, stdout: $stdout)
6464
command = AnonymousCommand
6565
command.define_method(:call) do |**args|
6666
yield(**args)
6767
end
6868

69-
Dry.CLI(command).call(arguments: arguments, out: out)
69+
Dry.CLI(command).call(arguments: arguments, stdout:)
7070
end
7171
end
7272
end

0 commit comments

Comments
 (0)