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, value19=:mu)
48 if RUBY_VERSION < "1.9"
51 (value19 == :mu) ? value : value19
55 # Method name replacement.
63 in_ruby_version("1.9") do
64 public :method_missing
69 def side_padding(width)
70 extra = width - self.size
74 left_padding = extra / 2
75 right_padding = (extra+1)/2
76 (" " * left_padding) + self + (" " *right_padding)
84 ENV['SIMPLE_KOAN_OUTPUT'] == 'true'
89 #shamelessly stolen (and modified) from redgreen
91 :clear => 0, :black => 30, :red => 31,
92 :green => 32, :yellow => 33, :blue => 34,
93 :magenta => 35, :cyan => 36,
98 COLORS.each do |color, value|
99 module_eval "def #{color}(string); colorize(string, #{value}); end"
100 module_function color
103 def colorize(string, color_value)
105 color(color_value) + string + color(COLORS[:clear])
111 def color(color_value)
116 return false if ENV['NO_COLOR']
117 if ENV['ANSI_COLOR'].nil?
122 ENV['ANSI_COLOR'] =~ /^(t|y)/i
130 def using_win32console
131 defined? Win32::Console
136 attr_reader :failure, :failed_test, :pass_count
138 in_ruby_version("1.8") do
139 AssertionError = Test::Unit::AssertionFailedError
142 in_ruby_version("1.9") do
143 if defined?(MiniTest)
144 AssertionError = MiniTest::Assertion
146 AssertionError = Test::Unit::AssertionFailedError
157 PROGRESS_FILE_NAME = '.path_progress'
159 def add_progress(prog)
161 exists = File.exists?(PROGRESS_FILE_NAME)
162 File.open(PROGRESS_FILE_NAME,'a+') do |f|
163 f.print "#{',' if exists}#{prog}"
169 if File.exists?(PROGRESS_FILE_NAME)
170 File.open(PROGRESS_FILE_NAME,'r') do |f|
171 @_contents = f.read.to_s.gsub(/\s/,'').split(',')
183 if @pass_count > progress.last.to_i
184 @observations << Color.green("#{step.koan_file}##{step.name} has expanded your awareness.")
188 @failure = step.failure
189 add_progress(@pass_count)
190 @observations << Color.red("#{step.koan_file}##{step.name} has damaged your karma.")
200 failure.is_a?(AssertionError)
205 @observations.each{|c| puts c }
217 total_tests = EdgeCase::Koan.total_tests
218 scale = bar_width.to_f/total_tests
219 print Color.green("your path thus far [")
220 happy_steps = (pass_count*scale).to_i
221 happy_steps = 1 if happy_steps == 0 && pass_count > 0
222 print Color.green('.'*happy_steps)
225 print Color.cyan('_'*(bar_width-1-happy_steps))
227 print Color.green(']')
228 print " #{pass_count}/#{total_tests}"
233 if EdgeCase.simple_output
240 def boring_end_screen
241 puts "Mountains are again merely mountains"
244 def artistic_end_screen
246 ruby_version = "(in #{'J' if defined?(JRUBY_VERSION)}Ruby #{defined?(JRUBY_VERSION) ? JRUBY_VERSION : RUBY_VERSION})"
247 ruby_version = ruby_version.side_padding(54)
248 completed = <<-ENDTEXT
251 , ,,: :::::::::::::,, :::: : ,
252 , ,,, ,:::::::::::::::::::, ,: ,: ,,
253 :, ::, , , :, ,::::::::::::::::::, ::: ,::::
254 : : ::, ,:::::::: ::, ,::::
255 , ,::::: :,:::::::,::::,
256 ,: , ,:,,: :::::::::::::
257 ::,: ,,:::, ,::::::::::::,
258 ,:::, :,,::: ::::::::::::,
259 ,::: :::::::, Mountains are again merely mountains ,::::::::::::
260 :::,,,:::::: ::::::::::::
261 ,:::::::::::, ::::::::::::,
262 :::::::::::, ,::::::::::::
263 ::::::::::::: ,::::::::::::
264 :::::::::::: Ruby Koans ::::::::::::,
265 ::::::::::::#{ ruby_version },::::::::::::,
266 :::::::::::, , ::::::::::::
267 ,:::::::::::::, brought to you by ,,::::::::::::,
268 :::::::::::::: ,::::::::::::
269 ::::::::::::::, ,:::::::::::::
270 ::::::::::::, EdgeCase Software Artisans , ::::::::::::
271 :,::::::::: :::: :::::::::::::
272 ,::::::::::: ,: ,,:::::::::::::,
273 :::::::::::: ,::::::::::::::,
274 :::::::::::::::::, ::::::::::::::::
275 :::::::::::::::::::, ::::::::::::::::
276 ::::::::::::::::::::::, ,::::,:, , ::::,:::
277 :::::::::::::::::::::::, ::,: ::,::, ,,: ::::
278 ,:::::::::::::::::::: ::,, , ,, ,::::
279 ,:::::::::::::::: ::,, , ,:::,
288 puts "The Master says:"
289 puts Color.cyan(" You have not yet reached enlightenment.")
290 if ((recents = progress.last(5)) && recents.size == 5 && recents.uniq.size == 1)
291 puts Color.cyan(" I sense frustration. Do not be afraid to ask for help.")
292 elsif progress.last(2).size == 2 && progress.last(2).uniq.size == 1
293 puts Color.cyan(" Do not lose hope.")
294 elsif progress.last.to_i > 0
295 puts Color.cyan(" You are progressing. Excellent. #{progress.last} completed.")
299 def guide_through_error
301 puts "The answers you seek..."
302 puts Color.red(indent(failure.message).join)
304 puts "Please meditate on the following code:"
306 puts embolden_first_line_only(indent(find_interesting_lines(failure.backtrace)))
308 puts embolden_first_line_only(indent(failure.backtrace))
313 def embolden_first_line_only(text)
326 text = text.split(/\n/) if text.is_a?(String)
327 text.collect{|t| " #{t}"}
330 def find_interesting_lines(backtrace)
331 backtrace.reject { |line|
332 line =~ /test\/unit\/|edgecase\.rb|minitest/
336 # Hat's tip to Ara T. Howard for the zen statements from his
337 # metakoans Ruby Quiz (http://rubyquiz.com/quiz67.html)
338 def a_zenlike_statement
340 zen_statement = "Mountains are again merely mountains"
342 zen_statement = case (@pass_count % 10)
344 "mountains are merely mountains"
346 "learn the rules so you know how to break them properly"
348 "remember that silence is sometimes the best answer"
350 "sleep is the best meditation"
352 "when you lose, don't lose the lesson"
354 "things are not what they appear to be: nor are they otherwise"
357 puts Color.green(zen_statement)
362 include Test::Unit::Assertions
364 attr_reader :name, :failure, :koan_count, :step_count, :koan_file
366 def initialize(name, koan_file=nil, koan_count=0, step_count=0)
369 @koan_count = koan_count
370 @step_count = step_count
371 @koan_file = koan_file
392 rescue StandardError, EdgeCase::Sensei::AssertionError => ex
397 rescue StandardError, EdgeCase::Sensei::AssertionError => ex
398 failed(ex) if passed?
404 # Class methods for the EdgeCase test suite.
406 def inherited(subclass)
407 subclasses << subclass
410 def method_added(name)
411 testmethods << name if !tests_disabled? && Koan.test_pattern =~ name.to_s
414 def end_of_enlightenment
415 @tests_disabled = true
418 def command_line(args)
422 @test_pattern = Regexp.new($1)
424 @test_pattern = Regexp.new(Regexp.quote($1))
429 fail "Unknown command line argument '#{arg}'"
435 # Lazy initialize list of subclasses
440 # Lazy initialize list of test methods.
446 @tests_disabled ||= false
450 @test_pattern ||= /^test_/
454 self.subclasses.inject(0){|total, k| total + k.testmethods.size }
461 sensei = EdgeCase::Sensei.new
463 sensei.observe(step.meditate)
469 catch(:edgecase_exit) {
471 EdgeCase::Koan.subclasses.each_with_index do |koan,koan_index|
472 koan.testmethods.each do |method_name|
473 step = koan.new(method_name, koan.to_s, koan_index+1, step_count+=1)
483 EdgeCase::Koan.command_line(ARGV)
484 EdgeCase::ThePath.new.walk