Skip to content

Commit

Permalink
Fixes line break issue when specs run pass terminal width. Substantia…
Browse files Browse the repository at this point in the history
…l refactoring to simplify the progress dump and DRY up codes. Add animation to Nyan Cat's flight. Add scoreboard to display running passing, pending, and failing examples.
  • Loading branch information
mattsears committed Nov 21, 2011
1 parent 64c9bdf commit c123f44
Show file tree
Hide file tree
Showing 2 changed files with 115 additions and 99 deletions.
167 changes: 95 additions & 72 deletions lib/nyan_cat_formatter.rb
Expand Up @@ -3,38 +3,19 @@

class NyanCatFormatter < RSpec::Core::Formatters::BaseTextFormatter

ESC = "\e["
NND = "#{ESC}0m"
PASS = '='
PASS_ARY= ['-', '_']
FAIL = '*'
ERROR = '!'
PENDING = '·'
HAPPY_CAT = <<-CAT
_,------,
_| /\\_/\\
~|__( ^ .^)
_"" ""
CAT
CONCERNED_CAT = <<-CAT
_,------,
_| /\\_/\\
~|__( o .o)
_"" ""
CAT
ASLEEP_CAT = <<-CAT
_,------,
_| /\\_/\\
~|__( - .-)
_"" ""
CAT

attr_reader :title, :current, :example_results, :color_index
ESC = "\e["
NND = "#{ESC}0m"
PASS = '='
PASS_ARY = ['-', '_']
FAIL = '*'
ERROR = '!'
PENDING = '+'

attr_reader :current, :example_results, :color_index

def start(example_count)
super(example_count)
@current, @color_index = 0,0
@bar_length = 70
@current, @color_index, @passing_count = 0,0,0
@example_results = []
end

Expand Down Expand Up @@ -68,75 +49,115 @@ def dump_summary(duration, example_count, failure_count, pending_count)
end

def dump_failures
# noop
#no op
end

# Increments the example count and displays the current progress
#
# Returns nothing
# @returns nothing
def tick(mark = PASS)
@example_results << mark
@current = (@current > @example_count) ? @example_count : @current + 1
@title = " #{current}/#{example_count}"
dump_progress
end

# Creates a rainbow trail
#
# Returns the sprintf format of the Nyan cat
# @return [String] the sprintf format of the Nyan cat
def nyan_trail
width = percentage * @bar_length / 100
marker = @example_results.map{ |mark| highlight(mark) }.join
nyan_cat_lines = nyan_cat.split("\n").map {|line| sprintf("%s#{line}%s", marker, " " * (@bar_length - width) ) }.join("\n")
end

# Calculates the percentage completed any given point
#
# Returns Fixnum of the percentage
def percentage
@example_count.zero? ? 100 : @current * 100 / @example_count
marks = @example_results.map{ |mark| highlight(mark) }
marks.shift(current_width - terminal_width) if current_width > terminal_width
nyan_cat_lines = nyan_cat.split("\n").each_with_index.map do |line, index|
format("%s#{line}", marks.join)
end.join("\n")
end

# Ascii Nyan Cat. If tests are complete, Nyan Cat goes to sleep. If
# there are failing or pending examples, Nyan Cat is concerned.
# Determine which Ascii Nyan Cat to display. If tests are complete,
# Nyan Cat goes to sleep. If there are failing or pending examples,
# Nyan Cat is concerned.
#
# Returns String Nyan Cat
# @return [String] Nyan Cat
def nyan_cat
if @failure_count > 0 || @pending_count > 0
CONCERNED_CAT
ascii_cat('o')[@color_index%2].join("\n") #'~|_(o.o)'
elsif (@current == @example_count)
ASLEEP_CAT
ascii_cat('-')[@color_index%2].join("\n") # '~|_(-.-)'
else
HAPPY_CAT
ascii_cat('^')[@color_index%2].join("\n") # '~|_(^.^)'
end
end

# Displays the current progress in all Nyan Cat glory
#
# @return nothing
def dump_progress
title_width = @example_count.to_s.length * 2 + 4
max_width = 80
lines = []
nyan_trail.split("\n").each do |nyan_trail_line|
lines << sprintf("%#{title_width}s %s", @title[0,(7)] + ":", nyan_trail_line)
end
line = lines.join("\n")
tail = (@current == @example_count) ? "\n" : sprintf("%c[1A%c[1A%c[1A\r", 0x1B, 0x1B, 0x1B)

if line.length == max_width - 1
output.print line + tail
output.flush
elsif line.length >= max_width
@bar_length = [@bar_length - (line.length - max_width + 1), 0].max
@bar_length == 0 ? output.print( rainbowify(line + tail) ) : dump_progress
else
@bar_length += max_width - line.length + 1
dump_progress
end
padding = @example_count.to_s.length * 2 + 2
line = nyan_trail.split("\n").each_with_index.inject([]) do |result, (trail, index)|
value = "#{scoreboard[index]}/#{@example_count}:"
result << format("%s %s", value, trail)
end.join("\n")
output.print line + eol
end

# Determines how we end the trail line. If complete, return a newline etc.
#
# @return [String]
def eol
return "\n" if @current == @example_count
length = (nyan_cat.split("\n").length - 1)
length > 0 ? format("\e[1A" * length + "\r") : "\r"
end

# Calculates the current flight length
#
# @return [Fixnum]
def current_width
padding = @example_count.to_s.length * 2 + 6
cat_length = nyan_cat.split("\n").group_by(&:size).max.first
padding + @current + cat_length
end

# A Unix trick using stty to get the console columns
# does not work in JRuby :-(
#
# @return [Fixnum]
def terminal_width
@terminal_width ||= `stty size`.split.map { |x| x.to_i }.reverse.first - 1
end

# Creates a data store of pass, failed, and pending example results
# We have to pad the results here because sprintf can't properly pad color
#
# @return [Array]
def scoreboard
padding = @example_count.to_s.length + 9
[ @current.to_s.rjust( @example_count.to_s.length),
green(@current - @pending_examples.size - @failed_examples.size).rjust(padding),
yellow(@pending_examples.size).rjust(padding),
red(@failed_examples.size).rjust(padding) ]
end

# Ascii version of Nyan cat. Two cats in the array allow Nyan to animate running.
#
# @param o [String] Nyan's eye
# @return [Array] Nyan cats
def ascii_cat(o = '^')
[[ "_,------, ",
"_| /\\_/\\ ",
"~|_( #{o} .#{o}) ",
" \"\" \"\" "
],
[ "_,------, ",
"_| /\\_/\\",
"^|__( #{o} .#{o}) ",
" \"\" \"\" "
]]
end

# Colorizes the string with raindow colors of the rainbow
#
# @params string [String]
# @return [String]
def rainbowify(string)
c = colors[@color_index % colors.size]
@color_index += 1
Expand All @@ -145,6 +166,7 @@ def rainbowify(string)

# Calculates the colors of the rainbow
#
# @return [Array]
def colors
@colors ||= (0...(6 * 7)).map do |n|
pi_3 = Math::PI / 3
Expand All @@ -159,12 +181,13 @@ def colors
# Determines how to color the example. If pass, it is rainbowified, otherwise
# we assign red if failed or yellow if an error occurred.
#
# @return [String]
def highlight(mark = PASS)
case mark
when PASS; rainbowify PASS_ARY[@color_index%2]
when FAIL; red mark
when ERROR; yellow mark
else mark
when PASS; rainbowify PASS_ARY[@color_index%2]
when FAIL; red mark
when ERROR; yellow mark
else mark
end
end

Expand Down
47 changes: 20 additions & 27 deletions spec/nyan_cat_formatter_spec.rb
Expand Up @@ -8,7 +8,7 @@
@formatter = NyanCatFormatter.new(@output)
@formatter.start(2)
@example = RSpec::Core::ExampleGroup.describe.example
sleep(0.2) # Just to slow it down a little :-)
sleep(0.1) # Just to slow it down a little :-)
end

describe 'passed, pending and failed' do
Expand All @@ -26,12 +26,15 @@

it 'should relax Nyan Cat' do
@formatter.example_passed(@example)
@formatter.nyan_cat.should == <<-CAT
_,------,
_| /\\_/\\
~|__( ^ .^)
_"" ""
CAT
@formatter.nyan_cat.should == [ "_,------, ",
"_| /\\_/\\ ",
"~|_( ^ .^) ",
" \"\" \"\" "
].join("\n")
end

it 'should update the scoreboard' do
@formatter.scoreboard.size.should == 4
end

end
Expand All @@ -50,12 +53,11 @@

it 'should alert Nyan Cat' do
@formatter.example_pending(@example)
@formatter.nyan_cat.should == <<-CAT
_,------,
_| /\\_/\\
~|__( o .o)
_"" ""
CAT
@formatter.nyan_cat.should == [ "_,------, ",
"_| /\\_/\\ ",
"~|_( o .o) ",
" \"\" \"\" "
].join("\n")
end

end
Expand All @@ -74,12 +76,11 @@

it 'should alert Nyan Cat' do
@formatter.example_failed(@example)
@formatter.nyan_cat.should == <<-CAT
_,------,
_| /\\_/\\
~|__( o .o)
_"" ""
CAT
@formatter.nyan_cat.should == [ "_,------, ",
"_| /\\_/\\ ",
"~|_( o .o) ",
" \"\" \"\" "
].join("\n")
end

end
Expand All @@ -93,14 +94,6 @@
@formatter.tick
end

it 'should change title' do
@formatter.title.should == ' 1/2'
end

it 'should calculate the percentage done' do
@formatter.percentage.should == 50
end

it 'should increment the current' do
@formatter.current.should == 1
end
Expand Down

0 comments on commit c123f44

Please sign in to comment.