Skip to content

Commit

Permalink
Compatibility with the new SSHKit::Command API
Browse files Browse the repository at this point in the history
An upcoming version of SSHKit changes its API for accessing stdout and stderr
data. This commit introduces a facade to normalize the API differences in the
various SSHKit versions, so both the new and old APIs can be supported.

Also, since SSHKit now `clear`s stdout/stderr data instead of assigning a new
blank value, our old technique of a simple `Command#dup` does not protect us
from mutations to the output strings. Use `Marshal` to do a deep copy instead.
  • Loading branch information
mattbrictson committed May 2, 2015
1 parent e5236dc commit ec3122b
Show file tree
Hide file tree
Showing 3 changed files with 51 additions and 13 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
## Next release

* Your contribution here!
* Changes to ensure compatibility with the upcoming version of SSHKit.
* Explicitly specify UTF-8 encoding for source files, for Ruby 1.9.3 compatibility.

## 0.3.0 (2015-03-28)
Expand Down
42 changes: 42 additions & 0 deletions lib/airbrussh/command_output.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
module Airbrussh
# A facade that provides access to stdout and stderr command output of
# sshkit commands. This is needed to normalize the API differences in
# various sshkit versions.
class CommandOutput
def self.for(command)
if command.respond_to?(:clear_stdout_lines)
Modern.new(command)
else
Legacy.new(command)
end
end

attr_reader :command

def initialize(command)
@command = command
end
end

class Legacy < CommandOutput
# The stderr/stdout methods provided by the command object have the current
# "chunk" as received over the wire. Since there may be more chunks
# appended and we don't want to print duplicates, clear the current data.
def each_line(stream, &block)
output = command.public_send(stream)
return if output.empty?
output.lines.to_a.each(&block)
command.public_send("#{stream}=", "")
end
end

class Modern < CommandOutput
# Newer versions of sshkit take care of clearing the output with the
# clear_stdout_lines/clear_stderr_lines methods.
def each_line(stream, &block)
lines = command.public_send("clear_#{stream}_lines")
return if lines.join.empty?
lines.each(&block)
end
end
end
21 changes: 8 additions & 13 deletions lib/airbrussh/formatter.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# encoding: UTF-8
require "airbrussh/command_output"
require "airbrussh/console"
require "colorize"
require "ostruct"
Expand Down Expand Up @@ -79,8 +80,8 @@ def write_log_file_delimiter

def write(obj)
# SSHKit's :pretty formatter mutates the stdout and stderr data in the
# command obj. So we need to dup it to ensure our copy is unscathed.
@log_file_formatter << obj.dup
# command obj. So we need to clone it to ensure our copy is unscathed.
@log_file_formatter << deep_copy(obj)

case obj
when SSHKit::Command then write_command(obj)
Expand Down Expand Up @@ -136,20 +137,10 @@ def write_command_output(command, number)
# Use a bit of meta-programming here, since stderr and stdout logic
# are identical except for different method names.
%w(stderr stdout).each do |stream|

next unless config.public_send("command_output_#{stream}?")
output = command.public_send(stream)
next if output.empty?

output.lines.each do |line|
CommandOutput.for(command).each_line(stream) do |line|
print_line " #{number} #{line.chomp}"
end

# The stderr/stdout data provided by the command object is the current
# "chunk" as received over the wire. Since there may be more chunks
# appended and we don't want to print duplicates, clear the current
# data.
command.public_send("#{stream}=", "")
end
end

Expand Down Expand Up @@ -234,5 +225,9 @@ def clock
def config
Airbrussh.configuration
end

def deep_copy(obj)
Marshal.load(Marshal.dump(obj))
end
end
end

0 comments on commit ec3122b

Please sign in to comment.