diff --git a/lib/dry/cli.rb b/lib/dry/cli.rb index 5f938605..127b78b4 100644 --- a/lib/dry/cli.rb +++ b/lib/dry/cli.rb @@ -97,6 +97,10 @@ def call(arguments: ARGV, out: $stdout, err: $stderr) # @api private def perform_command(arguments) command, args = parse(kommand, arguments, []) + + command.instance_variable_set(:@err, err) unless command.instance_variable_defined?(:@err) + command.instance_variable_set(:@out, out) unless command.instance_variable_defined?(:@out) + command.call(**args) end @@ -113,6 +117,9 @@ def perform_registry(arguments) command, args = parse(result.command, result.arguments, result.names) + command.instance_variable_set(:@err, err) unless command.instance_variable_defined?(:@err) + command.instance_variable_set(:@out, out) unless command.instance_variable_defined?(:@out) + result.before_callbacks.run(command, args) command.call(**args) result.after_callbacks.run(command, args) diff --git a/lib/dry/cli/command.rb b/lib/dry/cli/command.rb index 3e6a6010..07afe771 100644 --- a/lib/dry/cli/command.rb +++ b/lib/dry/cli/command.rb @@ -379,6 +379,39 @@ def self.superclass_options optional_arguments subcommands ] => "self.class" + + protected + + # The error output used to print error messaging + # + # @example + # class MyCommand + # def call + # out.puts "Hello World!" + # exit(0) + # rescue StandardError => e + # err.puts "Uh oh: #{e.message}" + # exit(1) + # end + # end + # + # @since unreleased + # @return [IO] + attr_reader :err + + # The standard output object used to print messaging + # + # @example + # class MyCommand + # def call + # out.puts "Hello World!" + # exit(0) + # end + # end + # + # @since unreleased + # @return [IO] + attr_reader :out end end end diff --git a/spec/unit/dry/cli/cli_spec.rb b/spec/unit/dry/cli/cli_spec.rb index d51d3fcc..b48a73ce 100644 --- a/spec/unit/dry/cli/cli_spec.rb +++ b/spec/unit/dry/cli/cli_spec.rb @@ -183,5 +183,45 @@ ) end end + + it "exposes @out and @err to command without overriding pre-existing ivars" do + # @out and @err are exposed by default + command_class = Class.new(Dry::CLI::Command) do + def call(**) + @out.puts "out" + @err.puts "err" + end + end + cli = Dry.CLI(command_class.new) + + default_out = StringIO.new + default_err = StringIO.new + cli.call(arguments: [], out: default_out, err: default_err) + + expect(default_out.string).to eq("out\n") + expect(default_err.string).to eq("err\n") + + # @out and @err do not override pre-existing ivars + custom_command_class = Class.new(command_class) do + define_method(:initialize) do |out, err| + super() + @out = out + @err = err + end + end + + custom_out = StringIO.new + custom_err = StringIO.new + cli = Dry.CLI(custom_command_class.new(custom_out, custom_err)) + + default_out = StringIO.new + default_err = StringIO.new + cli.call(arguments: [], out: default_out, err: default_err) + + expect(custom_out.string).to eq("out\n") + expect(custom_err.string).to eq("err\n") + expect(default_out.string).to eq("") + expect(default_err.string).to eq("") + end end end