I've just completed the WordWrap Kata in the Ruby Kata gem and I'd love some feedback. Thanks!
Here's the Kata:
$kata take word_wrap.rb
WordWrap Kata
Create a "WordWrap" class that is initialized with an integer parameter
- detail: The parameter is the wrap column
- example: WordWrap.new 4
- detail: Non-integer parameters raise an exception
- example: WordWrap.new raises an exception
- example: WordWrap.new "foo" raises an exception
completed (Y|n):
"wrap" method
Create a wrap method that takes a string parameter
- example: WordWrap.new(5).wrap "this is text"
- detail: The method should raise an exception for non-string parameters
- example: WordWrap.new(4).wrap([1,2,3]) raises an exception
completed (Y|n):
It should wrap the empty string to an empty string
- example: WordWrap.new(4).wrap("") returns ""
completed (Y|n):
It should return one short word as-is
- example: WordWrap.new(6).wrap("word") returns "word"
completed (Y|n):
It should wrap text at the last space before the wrap length
- example: WordWrap.new(5).wrap("word word") returns "word\nword"
- example: WordWrap.new(5).wrap("word word word") returns "word\nword\nword"
- example: WordWrap.new(10).wrap("word word word") returns "word word\nword"
completed (Y|n):
Long words without spaces should be broken at the wrap length
- example: WordWrap.new(4).wrap("wordword") returns "word\nword"
completed (Y|n):
Strings with long words and spaces should mix breaking words and at spaces
- example: WordWrap.new(3).wrap("word word word") returns "wor\nd\nwor\nd\nwor\d"
completed (Y|n):
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━┓
┃ Requirement ┃ Time ┃
┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╊━━━━━━━━━━┫
┃ Create a "WordWrap" class that is initialized with an integer parameter ┃ 00:09:37 ┃
┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╊━━━━━━━━━━┫
┃ Create a wrap method that takes a string parameter ┃ 00:06:59 ┃
┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╊━━━━━━━━━━┫
┃ It should wrap the empty string to an empty string ┃ 00:03:55 ┃
┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╊━━━━━━━━━━┫
┃ It should return one short word as-is ┃ 00:01:53 ┃
┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╊━━━━━━━━━━┫
┃ It should wrap text at the last space before the wrap length ┃ 00:34:53 ┃
┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╊━━━━━━━━━━┫
┃ Long words without spaces should be broken at the wrap length ┃ 00:06:16 ┃
┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╊━━━━━━━━━━┫
┃ Strings with long words and spaces should mix breaking words and at spaces ┃ 00:05:04 ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┻━━━━━━━━━━┛
My specs:
require 'spec_helper'
require 'wordwrap'
describe WordWrap do
describe "#initialize" do
let(:wrap_column) { 1 }
it "instantiates" do
expect {
WordWrap.new
}.to raise_exception
end
it "accepts an integer parameter" do
expect { WordWrap.new(wrap_column) }.to_not raise_exception
end
it "rejects non integer parameters" do
expect { WordWrap.new("foo") }.to raise_exception
end
end
describe ".wrap" do
let(:wrap_column) { 5 }
subject(:word_wrap) { WordWrap.new(wrap_column) }
specify { expect { word_wrap.wrap("this is text") }.to_not raise_exception }
specify { expect { word_wrap.wrap([1,2,3]) }.to raise_exception }
it "wraps the empty string to the empty string" do
word_wrap.wrap("").should eq("")
end
it "returns short words (i.e. char count < wrap_column) without modifying them" do
word_wrap.wrap("word").should eq("word")
end
it "wraps text at the last space before the wrap_column" do
WordWrap.new(5).wrap("word word").should eq("word\nword")
end
it { WordWrap.new(5).wrap("word word word").should eq("word\nword\nword") }
it { WordWrap.new(10).wrap("word word word").should eq("word word\nword") }
it { WordWrap.new(4).wrap("wordword").should eq("word\nword") }
it "mixes breaks at words and spaces" do
WordWrap.new(3).wrap("word word word").should eq("wor\nd\nwor\nd\nwor\nd")
end
end
end
And, finally, the WordWrap Class:
class WordWrap
def initialize(wrap_column)
if (!wrap_column.integer?)
raise(ArgumentError, "expected Integer got #{wrap_column.class}")
end
@wrap_column = wrap_column
end
def wrap(words)
if (!words.is_a?(String))
raise(ArgumentError, "expected String and got #{words.class}")
end
wrapped_words = ""
wrap_count = 0
word_array = words.scan(/\w+/)
word_array.each_with_index do |current_word, index|
next_word = word_array[index + 1]
puts "current_word: #{current_word}"
puts "next_word: #{next_word}"
if (current_word.length > @wrap_column)
wrapped_words << current_word[0..@wrap_column - 1] << "\n"
wrapped_words << current_word[@wrap_column..-1]
if !next_word.nil?
wrapped_words << "\n"
end
elsif next_word.nil?
wrapped_words << current_word
else
if (current_word.length + next_word.length + wrap_count) > @wrap_column
wrapped_words << current_word << "\n"
wrap_count = 0
else
wrapped_words << current_word << " "
wrap_count += current_word.length + next_word.length
end
end
end
wrapped_words
end
end
As you can see, wrap()
is a pretty ugly function with lots of branching. Any ideas for streamlining it would be greatly appreciated.
Edit:
Thanks to the suggestions below, I've refactored wrap
into something more readable:
def wrap text
if (text.class != String)
raise Exception "expected String but paramater has class #{text.class}"
end
if text.empty?
return ""
end
letter_array = text.split("")
last_space = false
offset = 0
current_length = 0
letter_array.each_with_index do |letter, index|
if (letter =~ /\s/)
last_space = index
end
if (current_length == @wrap_length && last_space)
letter_array[last_space] = "\n"
current_length = index - last_space
last_space = false
elsif (current_length == @wrap_length)
letter_array.insert(index, "\n")
current_length = 0
else
current_length += 1
end
end
if (letter_array.last == "\n")
letter_array.delete_at(-1)
end
letter_array.inject(:+)
end