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 in_ruby_version("1.8") do
28 class KeyError < StandardError
32 # Standard, generic replacement value.
33 # If value19 is given, it is used in place of value for Ruby 1.9.
34 def __(value="FILL ME IN", value19=:mu)
35 if RUBY_VERSION < "1.9"
38 (value19 == :mu) ? value : value19
42 # Numeric replacement value.
43 def _n_(value=999999, value19=:mu)
44 if RUBY_VERSION < "1.9"
47 (value19 == :mu) ? value : value19
51 # Error object replacement value.
52 def ___(value=FillMeInError, value19=:mu)
53 if RUBY_VERSION < "1.9"
56 (value19 == :mu) ? value : value19
60 # Method name replacement.
68 in_ruby_version("1.9") do
69 public :method_missing
74 def side_padding(width)
75 extra = width - self.size
79 left_padding = extra / 2
80 right_padding = (extra+1)/2
81 (" " * left_padding) + self + (" " *right_padding)
89 ENV['SIMPLE_KOAN_OUTPUT'] == 'true'
94 #shamelessly stolen (and modified) from redgreen
96 :clear => 0, :black => 30, :red => 31,
97 :green => 32, :yellow => 33, :blue => 34,
98 :magenta => 35, :cyan => 36,
103 COLORS.each do |color, value|
104 module_eval "def #{color}(string); colorize(string, #{value}); end"
105 module_function color
108 def colorize(string, color_value)
110 color(color_value) + string + color(COLORS[:clear])
116 def color(color_value)
121 return false if ENV['NO_COLOR']
122 if ENV['ANSI_COLOR'].nil?
129 ENV['ANSI_COLOR'] =~ /^(t|y)/i
137 def using_win32console
138 defined? Win32::Console
143 attr_reader :failure, :failed_test, :pass_count
145 in_ruby_version("1.8") do
146 AssertionError = Test::Unit::AssertionFailedError
149 in_ruby_version("1.9") do
150 if defined?(MiniTest)
151 AssertionError = MiniTest::Assertion
153 AssertionError = Test::Unit::AssertionFailedError
164 PROGRESS_FILE_NAME = '.path_progress'
166 def add_progress(prog)
168 exists = File.exists?(PROGRESS_FILE_NAME)
169 File.open(PROGRESS_FILE_NAME,'a+') do |f|
170 f.print "#{',' if exists}#{prog}"
176 if File.exists?(PROGRESS_FILE_NAME)
177 File.open(PROGRESS_FILE_NAME,'r') do |f|
178 @_contents = f.read.to_s.gsub(/\s/,'').split(',')
190 if @pass_count > progress.last.to_i
191 @observations << Color.green("#{step.koan_file}##{step.name} has expanded your awareness.")
195 @failure = step.failure
196 add_progress(@pass_count)
197 @observations << Color.red("#{step.koan_file}##{step.name} has damaged your karma.")
207 failure.is_a?(AssertionError)
212 @observations.each{|c| puts c }
224 total_tests = EdgeCase::Koan.total_tests
225 scale = bar_width.to_f/total_tests
226 print Color.green("your path thus far [")
227 happy_steps = (pass_count*scale).to_i
228 happy_steps = 1 if happy_steps == 0 && pass_count > 0
229 print Color.green('.'*happy_steps)
232 print Color.cyan('_'*(bar_width-1-happy_steps))
234 print Color.green(']')
235 print " #{pass_count}/#{total_tests}"
240 if EdgeCase.simple_output
247 def boring_end_screen
248 puts "Mountains are again merely mountains"
251 def artistic_end_screen
253 ruby_version = "(in #{'J' if defined?(JRUBY_VERSION)}Ruby #{defined?(JRUBY_VERSION) ? JRUBY_VERSION : RUBY_VERSION})"
254 ruby_version = ruby_version.side_padding(54)
255 completed = <<-ENDTEXT
258 , ,,: :::::::::::::,, :::: : ,
259 , ,,, ,:::::::::::::::::::, ,: ,: ,,
260 :, ::, , , :, ,::::::::::::::::::, ::: ,::::
261 : : ::, ,:::::::: ::, ,::::
262 , ,::::: :,:::::::,::::,
263 ,: , ,:,,: :::::::::::::
264 ::,: ,,:::, ,::::::::::::,
265 ,:::, :,,::: ::::::::::::,
266 ,::: :::::::, Mountains are again merely mountains ,::::::::::::
267 :::,,,:::::: ::::::::::::
268 ,:::::::::::, ::::::::::::,
269 :::::::::::, ,::::::::::::
270 ::::::::::::: ,::::::::::::
271 :::::::::::: Ruby Koans ::::::::::::,
272 ::::::::::::#{ ruby_version },::::::::::::,
273 :::::::::::, , ::::::::::::
274 ,:::::::::::::, brought to you by ,,::::::::::::,
275 :::::::::::::: ,::::::::::::
276 ::::::::::::::, ,:::::::::::::
277 ::::::::::::, EdgeCase Software Artisans , ::::::::::::
278 :,::::::::: :::: :::::::::::::
279 ,::::::::::: ,: ,,:::::::::::::,
280 :::::::::::: ,::::::::::::::,
281 :::::::::::::::::, ::::::::::::::::
282 :::::::::::::::::::, ::::::::::::::::
283 ::::::::::::::::::::::, ,::::,:, , ::::,:::
284 :::::::::::::::::::::::, ::,: ::,::, ,,: ::::
285 ,:::::::::::::::::::: ::,, , ,, ,::::
286 ,:::::::::::::::: ::,, , ,:::,
295 puts "The Master says:"
296 puts Color.cyan(" You have not yet reached enlightenment.")
297 if ((recents = progress.last(5)) && recents.size == 5 && recents.uniq.size == 1)
298 puts Color.cyan(" I sense frustration. Do not be afraid to ask for help.")
299 elsif progress.last(2).size == 2 && progress.last(2).uniq.size == 1
300 puts Color.cyan(" Do not lose hope.")
301 elsif progress.last.to_i > 0
302 puts Color.cyan(" You are progressing. Excellent. #{progress.last} completed.")
306 def guide_through_error
308 puts "The answers you seek..."
309 puts Color.red(indent(failure.message).join)
311 puts "Please meditate on the following code:"
313 puts embolden_first_line_only(indent(find_interesting_lines(failure.backtrace)))
315 puts embolden_first_line_only(indent(failure.backtrace))
320 def embolden_first_line_only(text)
333 text = text.split(/\n/) if text.is_a?(String)
334 text.collect{|t| " #{t}"}
337 def find_interesting_lines(backtrace)
338 backtrace.reject { |line|
339 line =~ /test\/unit\/|edgecase\.rb|minitest/
343 # Hat's tip to Ara T. Howard for the zen statements from his
344 # metakoans Ruby Quiz (http://rubyquiz.com/quiz67.html)
345 def a_zenlike_statement
347 zen_statement = "Mountains are again merely mountains"
349 zen_statement = case (@pass_count % 10)
351 "mountains are merely mountains"
353 "learn the rules so you know how to break them properly"
355 "remember that silence is sometimes the best answer"
357 "sleep is the best meditation"
359 "when you lose, don't lose the lesson"
361 "things are not what they appear to be: nor are they otherwise"
364 puts Color.green(zen_statement)
369 include Test::Unit::Assertions
371 attr_reader :name, :failure, :koan_count, :step_count, :koan_file
373 def initialize(name, koan_file=nil, koan_count=0, step_count=0)
376 @koan_count = koan_count
377 @step_count = step_count
378 @koan_file = koan_file
399 rescue StandardError, EdgeCase::Sensei::AssertionError => ex
404 rescue StandardError, EdgeCase::Sensei::AssertionError => ex
405 failed(ex) if passed?
411 # Class methods for the EdgeCase test suite.
413 def inherited(subclass)
414 subclasses << subclass
417 def method_added(name)
418 testmethods << name if !tests_disabled? && Koan.test_pattern =~ name.to_s
421 def end_of_enlightenment
422 @tests_disabled = true
425 def command_line(args)
429 @test_pattern = Regexp.new($1)
431 @test_pattern = Regexp.new(Regexp.quote($1))
436 fail "Unknown command line argument '#{arg}'"
442 # Lazy initialize list of subclasses
447 # Lazy initialize list of test methods.
453 @tests_disabled ||= false
457 @test_pattern ||= /^test_/
461 self.subclasses.inject(0){|total, k| total + k.testmethods.size }
468 sensei = EdgeCase::Sensei.new
470 sensei.observe(step.meditate)
476 catch(:edgecase_exit) {
478 EdgeCase::Koan.subclasses.each_with_index do |koan,koan_index|
479 koan.testmethods.each do |method_name|
480 step = koan.new(method_name, koan.to_s, koan_index+1, step_count+=1)
490 EdgeCase::Koan.command_line(ARGV)
491 EdgeCase::ThePath.new.walk