4 require 'test/unit/assertions'
10 # --------------------------------------------------------------------
11 # Support code for the Ruby Koans.
12 # --------------------------------------------------------------------
14 class FillMeInError < StandardError
17 def ruby_version?(version)
18 RUBY_VERSION =~ /^#{version}/ ||
19 (version == 'jruby' && defined?(JRUBY_VERSION)) ||
20 (version == 'mri' && ! defined?(JRUBY_VERSION))
23 def in_ruby_version(*versions)
24 yield if versions.any? { |v| ruby_version?(v) }
27 # Standard, generic replacement value.
28 # If value19 is given, it is used in place of value for Ruby 1.9.
29 def __(value="FILL ME IN", value19=:mu)
30 if RUBY_VERSION < "1.9"
33 (value19 == :mu) ? value : value19
37 # Numeric replacement value.
38 def _n_(value=999999, value19=:mu)
39 if RUBY_VERSION < "1.9"
42 (value19 == :mu) ? value : value19
46 # Error object replacement value.
47 def ___(value=FillMeInError)
51 # Method name replacement.
59 in_ruby_version("1.9") do
60 public :method_missing
65 def side_padding(width)
66 extra = width - self.size
70 left_padding = extra / 2
71 right_padding = (extra+1)/2
72 (" " * left_padding) + self + (" " *right_padding)
80 ENV['SIMPLE_KOAN_OUTPUT'] == 'true'
85 #shamelessly stolen (and modified) from redgreen
87 :clear => 0, :black => 30, :red => 31,
88 :green => 32, :yellow => 33, :blue => 34,
89 :magenta => 35, :cyan => 36,
94 COLORS.each do |color, value|
95 module_eval "def #{color}(string); colorize(string, #{value}); end"
99 def colorize(string, color_value)
101 color(color_value) + string + color(COLORS[:clear])
107 def color(color_value)
112 return false if ENV['NO_COLOR']
113 if ENV['ANSI_COLOR'].nil?
118 ENV['ANSI_COLOR'] =~ /^(t|y)/i
126 def using_win32console
127 defined? Win32::Console
132 attr_reader :failure, :failed_test, :pass_count
134 in_ruby_version("1.8") do
135 AssertionError = Test::Unit::AssertionFailedError
138 in_ruby_version("1.9") do
139 if defined?(MiniTest)
140 AssertionError = MiniTest::Assertion
142 AssertionError = Test::Unit::AssertionFailedError
153 PROGRESS_FILE_NAME = '.path_progress'
155 def add_progress(prog)
157 exists = File.exists?(PROGRESS_FILE_NAME)
158 File.open(PROGRESS_FILE_NAME,'a+') do |f|
159 f.print "#{',' if exists}#{prog}"
165 if File.exists?(PROGRESS_FILE_NAME)
166 File.open(PROGRESS_FILE_NAME,'r') do |f|
167 @_contents = f.read.to_s.gsub(/\s/,'').split(',')
179 if @pass_count > progress.last.to_i
180 @observations << Color.green("#{step.koan_file}##{step.name} has expanded your awareness.")
184 @failure = step.failure
185 add_progress(@pass_count)
186 @observations << Color.red("#{step.koan_file}##{step.name} has damaged your karma.")
196 failure.is_a?(AssertionError)
201 @observations.each{|c| puts c }
213 total_tests = EdgeCase::Koan.total_tests
214 scale = bar_width.to_f/total_tests
215 print Color.green("your path thus far [")
216 happy_steps = (pass_count*scale).to_i
217 happy_steps = 1 if happy_steps == 0 && pass_count > 0
218 print Color.green('.'*happy_steps)
221 print Color.cyan('_'*(bar_width-1-happy_steps))
223 print Color.green(']')
224 print " #{pass_count}/#{total_tests}"
229 if EdgeCase.simple_output
236 def boring_end_screen
237 puts "Mountains are again merely mountains"
240 def artistic_end_screen
242 ruby_version = "(in #{'J' if defined?(JRUBY_VERSION)}Ruby #{defined?(JRUBY_VERSION) ? JRUBY_VERSION : RUBY_VERSION})"
243 ruby_version = ruby_version.side_padding(54)
244 completed = <<-ENDTEXT
247 , ,,: :::::::::::::,, :::: : ,
248 , ,,, ,:::::::::::::::::::, ,: ,: ,,
249 :, ::, , , :, ,::::::::::::::::::, ::: ,::::
250 : : ::, ,:::::::: ::, ,::::
251 , ,::::: :,:::::::,::::,
252 ,: , ,:,,: :::::::::::::
253 ::,: ,,:::, ,::::::::::::,
254 ,:::, :,,::: ::::::::::::,
255 ,::: :::::::, Mountains are again merely mountains ,::::::::::::
256 :::,,,:::::: ::::::::::::
257 ,:::::::::::, ::::::::::::,
258 :::::::::::, ,::::::::::::
259 ::::::::::::: ,::::::::::::
260 :::::::::::: Ruby Koans ::::::::::::,
261 ::::::::::::#{ ruby_version },::::::::::::,
262 :::::::::::, , ::::::::::::
263 ,:::::::::::::, brought to you by ,,::::::::::::,
264 :::::::::::::: ,::::::::::::
265 ::::::::::::::, ,:::::::::::::
266 ::::::::::::, EdgeCase Software Artisans , ::::::::::::
267 :,::::::::: :::: :::::::::::::
268 ,::::::::::: ,: ,,:::::::::::::,
269 :::::::::::: ,::::::::::::::,
270 :::::::::::::::::, ::::::::::::::::
271 :::::::::::::::::::, ::::::::::::::::
272 ::::::::::::::::::::::, ,::::,:, , ::::,:::
273 :::::::::::::::::::::::, ::,: ::,::, ,,: ::::
274 ,:::::::::::::::::::: ::,, , ,, ,::::
275 ,:::::::::::::::: ::,, , ,:::,
284 puts "The Master says:"
285 puts Color.cyan(" You have not yet reached enlightenment.")
286 if ((recents = progress.last(5)) && recents.size == 5 && recents.uniq.size == 1)
287 puts Color.cyan(" I sense frustration. Do not be afraid to ask for help.")
288 elsif progress.last(2).size == 2 && progress.last(2).uniq.size == 1
289 puts Color.cyan(" Do not lose hope.")
290 elsif progress.last.to_i > 0
291 puts Color.cyan(" You are progressing. Excellent. #{progress.last} completed.")
295 def guide_through_error
297 puts "The answers you seek..."
298 puts Color.red(indent(failure.message).join)
300 puts "Please meditate on the following code:"
302 puts embolden_first_line_only(indent(find_interesting_lines(failure.backtrace)))
304 puts embolden_first_line_only(indent(failure.backtrace))
309 def embolden_first_line_only(text)
322 text = text.split(/\n/) if text.is_a?(String)
323 text.collect{|t| " #{t}"}
326 def find_interesting_lines(backtrace)
327 backtrace.reject { |line|
328 line =~ /test\/unit\/|edgecase\.rb|minitest/
332 # Hat's tip to Ara T. Howard for the zen statements from his
333 # metakoans Ruby Quiz (http://rubyquiz.com/quiz67.html)
334 def a_zenlike_statement
336 zen_statement = "Mountains are again merely mountains"
338 zen_statement = case (@pass_count % 10)
340 "mountains are merely mountains"
342 "learn the rules so you know how to break them properly"
344 "remember that silence is sometimes the best answer"
346 "sleep is the best meditation"
348 "when you lose, don't lose the lesson"
350 "things are not what they appear to be: nor are they otherwise"
353 puts Color.green(zen_statement)
358 include Test::Unit::Assertions
360 attr_reader :name, :failure, :koan_count, :step_count, :koan_file
362 def initialize(name, koan_file=nil, koan_count=0, step_count=0)
365 @koan_count = koan_count
366 @step_count = step_count
367 @koan_file = koan_file
388 rescue StandardError, EdgeCase::Sensei::AssertionError => ex
393 rescue StandardError, EdgeCase::Sensei::AssertionError => ex
394 failed(ex) if passed?
400 # Class methods for the EdgeCase test suite.
402 def inherited(subclass)
403 subclasses << subclass
406 def method_added(name)
407 testmethods << name if !tests_disabled? && Koan.test_pattern =~ name.to_s
410 def end_of_enlightenment
411 @tests_disabled = true
414 def command_line(args)
418 @test_pattern = Regexp.new($1)
420 @test_pattern = Regexp.new(Regexp.quote($1))
425 fail "Unknown command line argument '#{arg}'"
431 # Lazy initialize list of subclasses
436 # Lazy initialize list of test methods.
442 @tests_disabled ||= false
446 @test_pattern ||= /^test_/
450 self.subclasses.inject(0){|total, k| total + k.testmethods.size }
457 sensei = EdgeCase::Sensei.new
459 sensei.observe(step.meditate)
465 catch(:edgecase_exit) {
467 EdgeCase::Koan.subclasses.each_with_index do |koan,koan_index|
468 koan.testmethods.each do |method_name|
469 step = koan.new(method_name, koan.to_s, koan_index+1, step_count+=1)
479 EdgeCase::Koan.command_line(ARGV)
480 EdgeCase::ThePath.new.walk