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 in place 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)
97 color(color_value) + string + color(COLORS[:clear])
103 def color(color_value)
108 return false if ENV['NO_COLOR']
109 if ENV['ANSI_COLOR'].nil?
114 ENV['ANSI_COLOR'] =~ /^(t|y)/i
121 def using_win32console
123 !! Win32::Console::ANSI
131 attr_reader :failure, :failed_test, :pass_count
133 in_ruby_version("1.8") do
134 AssertionError = Test::Unit::AssertionFailedError
137 in_ruby_version("1.9") do
138 if defined?(MiniTest)
139 AssertionError = MiniTest::Assertion
141 AssertionError = Test::Unit::AssertionFailedError
152 PROGRESS_FILE_NAME = '.path_progress'
154 def add_progress(prog)
156 exists = File.exists?(PROGRESS_FILE_NAME)
157 File.open(PROGRESS_FILE_NAME,'a+') do |f|
158 f.print "#{',' if exists}#{prog}"
164 if File.exists?(PROGRESS_FILE_NAME)
165 File.open(PROGRESS_FILE_NAME,'r') do |f|
166 @_contents = f.read.to_s.gsub(/\s/,'').split(',')
178 if @pass_count > progress.last.to_i
179 @observations << Color.green("#{step.koan_file}##{step.name} has expanded your awareness.")
183 @failure = step.failure
184 add_progress(@pass_count)
185 @observations << Color.red("#{step.koan_file}##{step.name} has damaged your karma.")
195 failure.is_a?(AssertionError)
200 @observations.each{|c| puts c }
212 total_tests = EdgeCase::Koan.total_tests
213 scale = bar_width.to_f/total_tests
214 print Color.green("your path thus far [")
215 happy_steps = (pass_count*scale).to_i
216 happy_steps = 1 if happy_steps == 0 && pass_count > 0
217 print Color.green('.'*happy_steps)
220 print Color.cyan('_'*(bar_width-1-happy_steps))
222 print Color.green(']')
223 print " #{pass_count}/#{total_tests}"
228 if EdgeCase.simple_output
235 def boring_end_screen
236 puts "Mountains are again merely mountains"
239 def artistic_end_screen
241 ruby_version = "(in #{'J' if defined?(JRUBY_VERSION)}Ruby #{defined?(JRUBY_VERSION) ? JRUBY_VERSION : RUBY_VERSION})"
242 ruby_version = ruby_version.side_padding(54)
243 completed = <<-ENDTEXT
246 , ,,: :::::::::::::,, :::: : ,
247 , ,,, ,:::::::::::::::::::, ,: ,: ,,
248 :, ::, , , :, ,::::::::::::::::::, ::: ,::::
249 : : ::, ,:::::::: ::, ,::::
250 , ,::::: :,:::::::,::::,
251 ,: , ,:,,: :::::::::::::
252 ::,: ,,:::, ,::::::::::::,
253 ,:::, :,,::: ::::::::::::,
254 ,::: :::::::, Mountains are again merely mountains ,::::::::::::
255 :::,,,:::::: ::::::::::::
256 ,:::::::::::, ::::::::::::,
257 :::::::::::, ,::::::::::::
258 ::::::::::::: ,::::::::::::
259 :::::::::::: Ruby Koans ::::::::::::,
260 ::::::::::::#{ ruby_version },::::::::::::,
261 :::::::::::, , ::::::::::::
262 ,:::::::::::::, brought to you by ,,::::::::::::,
263 :::::::::::::: ,::::::::::::
264 ::::::::::::::, ,:::::::::::::
265 ::::::::::::, EdgeCase Software Artisans , ::::::::::::
266 :,::::::::: :::: :::::::::::::
267 ,::::::::::: ,: ,,:::::::::::::,
268 :::::::::::: ,::::::::::::::,
269 :::::::::::::::::, ::::::::::::::::
270 :::::::::::::::::::, ::::::::::::::::
271 ::::::::::::::::::::::, ,::::,:, , ::::,:::
272 :::::::::::::::::::::::, ::,: ::,::, ,,: ::::
273 ,:::::::::::::::::::: ::,, , ,, ,::::
274 ,:::::::::::::::: ::,, , ,:::,
283 puts "The Master says:"
284 puts Color.cyan(" You have not yet reached enlightenment.")
285 if ((recents = progress.last(5)) && recents.size == 5 && recents.uniq.size == 1)
286 puts Color.cyan(" I sense frustration. Do not be afraid to ask for help.")
287 elsif progress.last(2).size == 2 && progress.last(2).uniq.size == 1
288 puts Color.cyan(" Do not lose hope.")
289 elsif progress.last.to_i > 0
290 puts Color.cyan(" You are progressing. Excellent. #{progress.last} completed.")
294 def guide_through_error
296 puts "The answers you seek..."
297 puts Color.red(indent(failure.message).join)
299 puts "Please meditate on the following code:"
301 puts embolden_first_line_only(indent(find_interesting_lines(failure.backtrace)))
303 puts embolden_first_line_only(indent(failure.backtrace))
308 def embolden_first_line_only(text)
321 text = text.split(/\n/) if text.is_a?(String)
322 text.collect{|t| " #{t}"}
325 def find_interesting_lines(backtrace)
326 backtrace.reject { |line|
327 line =~ /test\/unit\/|edgecase\.rb|minitest/
331 # Hat's tip to Ara T. Howard for the zen statements from his
332 # metakoans Ruby Quiz (http://rubyquiz.com/quiz67.html)
333 def a_zenlike_statement
335 zen_statement = "Mountains are again merely mountains"
337 zen_statement = case (@pass_count % 10)
339 "mountains are merely mountains"
341 "learn the rules so you know how to break them properly"
343 "remember that silence is sometimes the best answer"
345 "sleep is the best meditation"
347 "when you lose, don't lose the lesson"
349 "things are not what they appear to be: nor are they otherwise"
352 puts Color.green(zen_statement)
357 include Test::Unit::Assertions
359 attr_reader :name, :failure, :koan_count, :step_count, :koan_file
361 def initialize(name, koan_file=nil, koan_count=0, step_count=0)
364 @koan_count = koan_count
365 @step_count = step_count
366 @koan_file = koan_file
387 rescue StandardError, EdgeCase::Sensei::AssertionError => ex
392 rescue StandardError, EdgeCase::Sensei::AssertionError => ex
393 failed(ex) if passed?
399 # Class methods for the EdgeCase test suite.
401 def inherited(subclass)
402 subclasses << subclass
405 def method_added(name)
406 testmethods << name if !tests_disabled? && Koan.test_pattern =~ name.to_s
409 def end_of_enlightenment
410 @tests_disabled = true
413 def command_line(args)
417 @test_pattern = Regexp.new($1)
419 @test_pattern = Regexp.new(Regexp.quote($1))
424 fail "Unknown command line argument '#{arg}'"
430 # Lazy initialize list of subclasses
435 # Lazy initialize list of test methods.
441 @tests_disabled ||= false
445 @test_pattern ||= /^test_/
449 self.subclasses.inject(0){|total, k| total + k.testmethods.size }
456 sensei = EdgeCase::Sensei.new
458 sensei.observe(step.meditate)
464 catch(:edgecase_exit) {
466 EdgeCase::Koan.subclasses.each_with_index do |koan,koan_index|
467 koan.testmethods.each do |method_name|
468 step = koan.new(method_name, koan.to_s, koan_index+1, step_count+=1)
478 EdgeCase::Koan.command_line(ARGV)
479 EdgeCase::ThePath.new.walk