Skip to content

Commit

Permalink
Merge pull request #246 from robd/no-colors-for-non-tty-outputs
Browse files Browse the repository at this point in the history
No colors for non tty outputs
  • Loading branch information
leehambley committed May 9, 2015
2 parents bf9c6b2 + b471f86 commit e91d8c3
Show file tree
Hide file tree
Showing 17 changed files with 289 additions and 150 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ appear at the top.

* Add your entries below here, remember to credit yourself however you want
to be credited!
* Now only color the output if it is associated with a tty, or the `SSHKIT_COLOR` environment variable is set. (See [README](README.md#output-colors)) @robd
* Removed broken support for assigning an `IO` to the `output` config option (See [#243](https://github.com/capistrano/sshkit/issues/243)). @robd
* Use `SSHKit.config.output = SSHKit::Formatter::SimpleText.new($stdin)` instead
* Added support for :interaction_handler option on commands. @robd
Expand Down
27 changes: 22 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -359,18 +359,35 @@ However, if you prefer non colored text you can use the `:simpletext` formatter.
there is also a `:dot` formatter which will simply output red or green dots based on the success or failure of commands.
There is also a `:blackhole` formatter which does not output anything.

By default, formatters log to `$stdout`, but they can be constructed with any `IO`:
By default, formatters log to `$stdout`, but they can be constructed with any object which implements `<<`
for example any `IO` subclass, `String`, `Logger` etc:

```ruby
# Output to a StringIO:
out = StringIO.new
SSHKit.config.output = SSHKit::Formatter::Pretty.new(out)
# Do something with out.string
# Output to a String:
output = String.new
SSHKit.config.output = SSHKit::Formatter::Pretty.new(output)
# Do something with output

# Or output to a file:
SSHKit.config.output = SSHKit::Formatter::SimpleText.new(File.open('log/deploy.log', 'wb'))
```

#### Output Colors

By default, SSHKit will color the output using ANSI color escape sequences
if the output you are using is associated with a terminal device (tty).
This means that you should see colors if you are writing output to the terminal (the default),
but you shouldn't see ANSI color escape sequences if you are writing to a file.

Colors are supported for the `Pretty` and `Dot` formatters, but for historical reasons
the `SimpleText` formatter never shows colors.

If you want to force SSHKit to show colors, you can set the `SSHKIT_COLOR` environment variable:

```ruby
ENV['SSHKIT_COLOR'] = 'TRUE'
```

#### Custom formatters

Want custom output formatting? Here's what you have to do:
Expand Down
3 changes: 2 additions & 1 deletion lib/sshkit/backends/printer.rb
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
module SSHKit
module Backend

# Printer is used to implement --dry-run in Capistrano
class Printer < Abstract

def execute_command(cmd)
output << cmd
end

alias :upload! :execute
alias :download! :execute
alias :test :execute
end
end
end
29 changes: 10 additions & 19 deletions lib/sshkit/color.rb
Original file line number Diff line number Diff line change
@@ -1,27 +1,18 @@
require 'colorize'

module Color
String.colors.each do |color|
instance_eval <<-RUBY, __FILE__, __LINE__
def #{color}(string = '')
string = yield if block_given?
colorize? ? string.colorize(:color => :#{color}) : string
end
RUBY
end
module SSHKit
class Color
def initialize(output, env=ENV)
@output, @env = output, env
end

String.modes.each do |mode|
instance_eval <<-RUBY, __FILE__, __LINE__
def #{mode}(string = '')
string = yield if block_given?
colorize? ? string.colorize(:mode => :#{mode}) : string
end
RUBY
end
def colorize(obj, color, mode=nil)
string = obj.to_s
colorize? ? string.colorize(color: color, mode: mode) : string
end

class << self
def colorize?
ENV['SSHKIT_COLOR'] || $stdout.tty?
@env['SSHKIT_COLOR'] || (@output.respond_to?(:tty?) && @output.tty?)
end
end
end
6 changes: 4 additions & 2 deletions lib/sshkit/formatters/abstract.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@ class Abstract
extend Forwardable
attr_reader :original_output
def_delegators :@original_output, :read, :rewind
def_delegators :@color, :colorize

def initialize(oio)
@original_output = oio
def initialize(output)
@original_output = output
@color = SSHKit::Color.new(output)
end

def log(messages)
Expand Down
8 changes: 1 addition & 7 deletions lib/sshkit/formatters/dot.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,11 @@ class Dot < Abstract
def write(obj)
return unless obj.is_a? SSHKit::Command
if obj.finished?
original_output << (obj.failure? ? c.red('.') : c.green('.'))
original_output << colorize('.', obj.failure? ? :red : :green)
end
end
alias :<< :write

private

def c
@c ||= Color
end

end

end
Expand Down
26 changes: 11 additions & 15 deletions lib/sshkit/formatters/pretty.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ module Formatter
class Pretty < Abstract

def write(obj)
return if obj.verbosity < SSHKit.config.output_verbosity
return if obj.respond_to?(:verbosity) && obj.verbosity < SSHKit.config.output_verbosity
case obj
when SSHKit::Command then write_command(obj)
when SSHKit::LogMessage then write_log_message(obj)
else
original_output << c.black(c.on_yellow("Output formatter doesn't know how to handle #{obj.class}\n"))
raise "Output formatter only supports formatting SSHKit::Command and SSHKit::LogMessage, called with #{obj.class}: #{obj.inspect}"
end
end
alias :<< :write
Expand All @@ -19,47 +19,43 @@ def write(obj)

def write_command(command)
unless command.started?
host_prefix = command.host.user ? "as #{c.blue(command.host.user)}@" : 'on '
write_command_message("Running #{c.yellow(c.bold(String(command)))} #{host_prefix}#{c.blue(command.host.to_s)}", command)
host_prefix = command.host.user ? "as #{colorize(command.host.user, :blue)}@" : 'on '
write_command_message("Running #{colorize(command, :yellow, :bold)} #{host_prefix}#{colorize(command.host, :blue)}", command)
if SSHKit.config.output_verbosity == Logger::DEBUG
write_command_message("Command: #{c.blue(command.to_command)}", command, Logger::DEBUG)
write_command_message("Command: #{colorize(command.to_command, :blue)}", command, Logger::DEBUG)
end
end

if SSHKit.config.output_verbosity == Logger::DEBUG
command.clear_stdout_lines.each do |line|
write_command_message(c.green(format_std_stream_line(line)), command, Logger::DEBUG)
write_command_message(colorize(format_std_stream_line(line), :green), command, Logger::DEBUG)
end

command.clear_stderr_lines.each do |line|
write_command_message(c.red(format_std_stream_line(line)), command, Logger::DEBUG)
write_command_message(colorize(format_std_stream_line(line), :red), command, Logger::DEBUG)
end
end

if command.finished?
successful_or_failed = c.bold { command.failure? ? c.red('failed') : c.green('successful') }
successful_or_failed = command.failure? ? colorize('failed', :red, :bold) : colorize('successful', :green, :bold)
write_command_message("Finished in #{sprintf('%5.3f seconds', command.runtime)} with exit status #{command.exit_status} (#{successful_or_failed}).", command)
end
end

def write_command_message(message, command, verbosity_override=nil)
original_output << "%6s [%s] %s\n" % [level(verbosity_override || command.verbosity), c.green(command.uuid), message]
original_output << "%6s [%s] %s\n" % [level(verbosity_override || command.verbosity), colorize(command.uuid, :green), message]
end

def write_log_message(log_message)
original_output << "%6s %s\n" % [level(log_message.verbosity), log_message.to_s]
end

def c
@c ||= Color
end

def level(verbosity)
c.send(level_formatting(verbosity), level_names(verbosity))
colorize(level_names(verbosity), level_formatting(verbosity))
end

def level_formatting(level_num)
%w{ black blue yellow red red }[level_num]
[:black, :blue, :yellow, :red, :red][level_num]
end

def level_names(level_num)
Expand Down
4 changes: 2 additions & 2 deletions lib/sshkit/formatters/simple_text.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ module Formatter
class SimpleText < Abstract

def write(obj)
return if obj.verbosity < SSHKit.config.output_verbosity
return if obj.respond_to?(:verbosity) && obj.verbosity < SSHKit.config.output_verbosity
case obj
when SSHKit::Command then write_command(obj)
when SSHKit::LogMessage then write_log_message(obj)
else
original_output << "Output formatter doesn't know how to handle #{obj.class}\n"
raise "Output formatter only supports formatting SSHKit::Command and SSHKit::LogMessage, called with #{obj.class}: #{obj.inspect}"
end
end
alias :<< :write
Expand Down
1 change: 1 addition & 0 deletions test/functional/backends/test_local.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ module Backend
class TestLocal < MiniTest::Unit::TestCase

def setup
super
SSHKit.config.output = SSHKit::Formatter::BlackHole.new($stdout)
end

Expand Down
6 changes: 3 additions & 3 deletions test/functional/backends/test_netssh.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ class TestNetssh < FunctionalTest

def setup
super
@out = StringIO.new
@output = String.new
SSHKit.config.output_verbosity = :debug
SSHKit.config.output = SSHKit::Formatter::SimpleText.new(@out)
SSHKit.config.output = SSHKit::Formatter::SimpleText.new(@output)
end

def a_host
Expand All @@ -32,7 +32,7 @@ def test_simple_netssh
end
end.run

command_lines = @out.string.lines.select { |line| line.start_with?('Command:') }
command_lines = @output.lines.select { |line| line.start_with?('Command:') }
assert_equal <<-EOEXPECTED.unindent, command_lines.join
Command: /usr/bin/env date
Command: /usr/bin/env ls -l
Expand Down
1 change: 1 addition & 0 deletions test/unit/backends/test_connection_pool.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ module Backend
class TestConnectionPool < UnitTest

def setup
super
pool.flush_connections
end

Expand Down
73 changes: 73 additions & 0 deletions test/unit/backends/test_printer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
require 'helper'

module SSHKit
module Backend
class TestPrinter < UnitTest

def setup
super
SSHKit.config.output = SSHKit::Formatter::Pretty.new(output)
SSHKit.config.output_verbosity = Logger::DEBUG
Command.any_instance.stubs(:uuid).returns('aaaaaa')
end

def output
@output ||= String.new
end

def printer
@printer ||= Printer.new(Host.new('example.com'))
end

def test_execute
printer.execute 'uname -a'
assert_output_lines(
' INFO [aaaaaa] Running /usr/bin/env uname -a on example.com',
' DEBUG [aaaaaa] Command: uname -a'
)
end

def test_test_method
printer.test '[ -d /some/file ]'

assert_output_lines(
' DEBUG [aaaaaa] Running /usr/bin/env [ -d /some/file ] on example.com',
' DEBUG [aaaaaa] Command: [ -d /some/file ]'
)
end

def test_capture
result = printer.capture 'ls -l'

assert_equal '', result

assert_output_lines(
' DEBUG [aaaaaa] Running /usr/bin/env ls -l on example.com',
' DEBUG [aaaaaa] Command: ls -l'
)
end

def test_upload
printer.upload! '/some/file', '/remote'
assert_output_lines(
' INFO [aaaaaa] Running /usr/bin/env /some/file /remote on example.com',
' DEBUG [aaaaaa] Command: /usr/bin/env /some/file /remote'
)
end

def test_download
printer.download! 'remote/file', '/local/path'
assert_output_lines(
' INFO [aaaaaa] Running /usr/bin/env remote/file /local/path on example.com',
' DEBUG [aaaaaa] Command: /usr/bin/env remote/file /local/path'
)
end

private

def assert_output_lines(*expected_lines)
assert_equal(expected_lines, output.split("\n"))
end
end
end
end
Loading

0 comments on commit e91d8c3

Please sign in to comment.