4 require 'test/unit/assertions'
6 # --------------------------------------------------------------------
7 # Support code for the Ruby Koans.
8 # --------------------------------------------------------------------
10 class FillMeInError < StandardError
13 def ruby_version?(version)
14 RUBY_VERSION =~ /^#{version}/ ||
15 (version == 'jruby' && defined?(JRUBY_VERSION)) ||
16 (version == 'mri' && ! defined?(JRUBY_VERSION))
19 def in_ruby_version(*versions)
20 yield if versions.any? { |v| ruby_version?(v) }
23 # Standard, generic replacement value.
24 # If value19 is given, it is used inplace of value for Ruby 1.9.
25 def __(value="FILL ME IN", value19=:mu)
26 if RUBY_VERSION < "1.9"
29 (value19 == :mu) ? value : value19
33 # Numeric replacement value.
34 def _n_(value=999999, value19=:mu)
35 if RUBY_VERSION < "1.9"
38 (value19 == :mu) ? value : value19
42 # Error object replacement value.
43 def ___(value=FillMeInError)
47 # Method name replacement.
55 in_ruby_version("1.9") do
56 public :method_missing
61 def side_padding(width)
62 extra = width - self.size
66 left_padding = extra / 2
67 right_padding = (extra+1)/2
68 (" " * left_padding) + self + (" " *right_padding)
76 ENV['SIMPLE_KOAN_OUTPUT'] == 'true'
81 #shamelessly stolen (and modified) from redgreen
83 :clear => 0, :black => 30, :red => 31,
84 :green => 32, :yellow => 33, :blue => 34,
85 :magenta => 35, :cyan => 36,
90 COLORS.each do |color, value|
91 module_eval "def #{color}(string); colorize(string, #{value}); end"
95 def colorize(string, color_value)
99 color(color_value) + string + color(COLORS[:clear])
103 def color(color_value)
109 attr_reader :failure, :failed_test, :pass_count
111 in_ruby_version("1.8") do
112 AssertionError = Test::Unit::AssertionFailedError
115 in_ruby_version("1.9") do
116 if defined?(MiniTest)
117 AssertionError = MiniTest::Assertion
119 AssertionError = Test::Unit::AssertionFailedError
130 PROGRESS_FILE_NAME = '.path_progress'
132 def add_progress(prog)
134 exists = File.exists?(PROGRESS_FILE_NAME)
135 File.open(PROGRESS_FILE_NAME,'a+') do |f|
136 f.print "#{',' if exists}#{prog}"
142 if File.exists?(PROGRESS_FILE_NAME)
143 File.open(PROGRESS_FILE_NAME,'r') do |f|
144 @_contents = f.read.to_s.gsub(/\s/,'').split(',')
156 if @pass_count > progress.last.to_i
157 @observations << Color.green("#{step.koan_file}##{step.name} has expanded your awareness.")
161 @failure = step.failure
162 add_progress(@pass_count)
163 @observations << Color.red("#{step.koan_file}##{step.name} has damaged your karma.")
173 failure.is_a?(AssertionError)
178 @observations.each{|c| puts c }
190 total_tests = EdgeCase::Koan.total_tests
191 scale = bar_width.to_f/total_tests
192 print Color.green("your path thus far [")
193 happy_steps = (pass_count*scale).to_i
194 happy_steps = 1 if happy_steps == 0 && pass_count > 0
195 print Color.green('.'*happy_steps)
198 print Color.cyan('_'*(bar_width-1-happy_steps))
200 print Color.green(']')
201 print " #{pass_count}/#{total_tests}"
206 if EdgeCase.simple_output
213 def boring_end_screen
214 puts "Mountains are again merely mountains"
217 def artistic_end_screen
219 ruby_version = "(in #{'J' if defined?(JRUBY_VERSION)}Ruby #{defined?(JRUBY_VERSION) ? JRUBY_VERSION : RUBY_VERSION})"
220 ruby_version = ruby_version.side_padding(54)
221 completed = <<-ENDTEXT
224 , ,,: :::::::::::::,, :::: : ,
225 , ,,, ,:::::::::::::::::::, ,: ,: ,,
226 :, ::, , , :, ,::::::::::::::::::, ::: ,::::
227 : : ::, ,:::::::: ::, ,::::
228 , ,::::: :,:::::::,::::,
229 ,: , ,:,,: :::::::::::::
230 ::,: ,,:::, ,::::::::::::,
231 ,:::, :,,::: ::::::::::::,
232 ,::: :::::::, Mountains are again merely mountains ,::::::::::::
233 :::,,,:::::: ::::::::::::
234 ,:::::::::::, ::::::::::::,
235 :::::::::::, ,::::::::::::
236 ::::::::::::: ,::::::::::::
237 :::::::::::: Ruby Koans ::::::::::::,
238 ::::::::::::#{ ruby_version },::::::::::::,
239 :::::::::::, , ::::::::::::
240 ,:::::::::::::, brought to you by ,,::::::::::::,
241 :::::::::::::: ,::::::::::::
242 ::::::::::::::, ,:::::::::::::
243 ::::::::::::, EdgeCase Software Artisans , ::::::::::::
244 :,::::::::: :::: :::::::::::::
245 ,::::::::::: ,: ,,:::::::::::::,
246 :::::::::::: ,::::::::::::::,
247 :::::::::::::::::, ::::::::::::::::
248 :::::::::::::::::::, ::::::::::::::::
249 ::::::::::::::::::::::, ,::::,:, , ::::,:::
250 :::::::::::::::::::::::, ::,: ::,::, ,,: ::::
251 ,:::::::::::::::::::: ::,, , ,, ,::::
252 ,:::::::::::::::: ::,, , ,:::,
261 puts "The Master says:"
262 puts Color.cyan(" You have not yet reached enlightenment.")
263 if ((recents = progress.last(5)) && recents.size == 5 && recents.uniq.size == 1)
264 puts Color.cyan(" I sense frustration. Do not be afraid to ask for help.")
265 elsif progress.last(2).size == 2 && progress.last(2).uniq.size == 1
266 puts Color.cyan(" Do not lose hope.")
267 elsif progress.last.to_i > 0
268 puts Color.cyan(" You are progressing. Excellent. #{progress.last} completed.")
272 def guide_through_error
274 puts "The answers you seek..."
275 puts Color.red(indent(failure.message).join)
277 puts "Please meditate on the following code:"
279 puts embolden_first_line_only(indent(find_interesting_lines(failure.backtrace)))
281 puts embolden_first_line_only(indent(failure.backtrace))
286 def embolden_first_line_only(text)
299 text = text.split(/\n/) if text.is_a?(String)
300 text.collect{|t| " #{t}"}
303 def find_interesting_lines(backtrace)
304 backtrace.reject { |line|
305 line =~ /test\/unit\/|edgecase\.rb|minitest/
309 # Hat's tip to Ara T. Howard for the zen statements from his
310 # metakoans Ruby Quiz (http://rubyquiz.com/quiz67.html)
311 def a_zenlike_statement
313 zen_statement = "Mountains are again merely mountains"
315 zen_statement = case (@pass_count % 10)
317 "mountains are merely mountains"
319 "learn the rules so you know how to break them properly"
321 "remember that silence is sometimes the best answer"
323 "sleep is the best meditation"
325 "when you lose, don't lose the lesson"
327 "things are not what they appear to be: nor are they otherwise"
330 puts Color.green(zen_statement)
335 include Test::Unit::Assertions
337 attr_reader :name, :failure, :koan_count, :step_count, :koan_file
339 def initialize(name, koan_file=nil, koan_count=0, step_count=0)
342 @koan_count = koan_count
343 @step_count = step_count
344 @koan_file = koan_file
365 rescue StandardError, EdgeCase::Sensei::AssertionError => ex
370 rescue StandardError, EdgeCase::Sensei::AssertionError => ex
371 failed(ex) if passed?
377 # Class methods for the EdgeCase test suite.
379 def inherited(subclass)
380 subclasses << subclass
383 def method_added(name)
384 testmethods << name if !tests_disabled? && Koan.test_pattern =~ name.to_s
387 def end_of_enlightenment
388 @tests_disabled = true
391 def command_line(args)
395 @test_pattern = Regexp.new($1)
397 @test_pattern = Regexp.new(Regexp.quote($1))
402 fail "Unknown command line argument '#{arg}'"
408 # Lazy initialize list of subclasses
413 # Lazy initialize list of test methods.
419 @tests_disabled ||= false
423 @test_pattern ||= /^test_/
427 self.subclasses.inject(0){|total, k| total + k.testmethods.size }
434 sensei = EdgeCase::Sensei.new
436 sensei.observe(step.meditate)
442 catch(:edgecase_exit) {
444 EdgeCase::Koan.subclasses.each_with_index do |koan,koan_index|
445 koan.testmethods.each do |method_name|
446 step = koan.new(method_name, koan.to_s, koan_index+1, step_count+=1)
456 EdgeCase::Koan.command_line(ARGV)
457 EdgeCase::ThePath.new.walk