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?
127 ENV['ANSI_COLOR'] =~ /^(t|y)/i
135 def using_win32console
136 defined? Win32::Console
141 attr_reader :failure, :failed_test, :pass_count
143 in_ruby_version("1.8") do
144 AssertionError = Test::Unit::AssertionFailedError
147 in_ruby_version("1.9") do
148 if defined?(MiniTest)
149 AssertionError = MiniTest::Assertion
151 AssertionError = Test::Unit::AssertionFailedError
162 PROGRESS_FILE_NAME = '.path_progress'
164 def add_progress(prog)
166 exists = File.exists?(PROGRESS_FILE_NAME)
167 File.open(PROGRESS_FILE_NAME,'a+') do |f|
168 f.print "#{',' if exists}#{prog}"
174 if File.exists?(PROGRESS_FILE_NAME)
175 File.open(PROGRESS_FILE_NAME,'r') do |f|
176 @_contents = f.read.to_s.gsub(/\s/,'').split(',')
188 if @pass_count > progress.last.to_i
189 @observations << Color.green("#{step.koan_file}##{step.name} has expanded your awareness.")
193 @failure = step.failure
194 add_progress(@pass_count)
195 @observations << Color.red("#{step.koan_file}##{step.name} has damaged your karma.")
205 failure.is_a?(AssertionError)
210 @observations.each{|c| puts c }
222 total_tests = EdgeCase::Koan.total_tests
223 scale = bar_width.to_f/total_tests
224 print Color.green("your path thus far [")
225 happy_steps = (pass_count*scale).to_i
226 happy_steps = 1 if happy_steps == 0 && pass_count > 0
227 print Color.green('.'*happy_steps)
230 print Color.cyan('_'*(bar_width-1-happy_steps))
232 print Color.green(']')
233 print " #{pass_count}/#{total_tests}"
238 if EdgeCase.simple_output
245 def boring_end_screen
246 puts "Mountains are again merely mountains"
249 def artistic_end_screen
251 ruby_version = "(in #{'J' if defined?(JRUBY_VERSION)}Ruby #{defined?(JRUBY_VERSION) ? JRUBY_VERSION : RUBY_VERSION})"
252 ruby_version = ruby_version.side_padding(54)
253 completed = <<-ENDTEXT
256 , ,,: :::::::::::::,, :::: : ,
257 , ,,, ,:::::::::::::::::::, ,: ,: ,,
258 :, ::, , , :, ,::::::::::::::::::, ::: ,::::
259 : : ::, ,:::::::: ::, ,::::
260 , ,::::: :,:::::::,::::,
261 ,: , ,:,,: :::::::::::::
262 ::,: ,,:::, ,::::::::::::,
263 ,:::, :,,::: ::::::::::::,
264 ,::: :::::::, Mountains are again merely mountains ,::::::::::::
265 :::,,,:::::: ::::::::::::
266 ,:::::::::::, ::::::::::::,
267 :::::::::::, ,::::::::::::
268 ::::::::::::: ,::::::::::::
269 :::::::::::: Ruby Koans ::::::::::::,
270 ::::::::::::#{ ruby_version },::::::::::::,
271 :::::::::::, , ::::::::::::
272 ,:::::::::::::, brought to you by ,,::::::::::::,
273 :::::::::::::: ,::::::::::::
274 ::::::::::::::, ,:::::::::::::
275 ::::::::::::, EdgeCase Software Artisans , ::::::::::::
276 :,::::::::: :::: :::::::::::::
277 ,::::::::::: ,: ,,:::::::::::::,
278 :::::::::::: ,::::::::::::::,
279 :::::::::::::::::, ::::::::::::::::
280 :::::::::::::::::::, ::::::::::::::::
281 ::::::::::::::::::::::, ,::::,:, , ::::,:::
282 :::::::::::::::::::::::, ::,: ::,::, ,,: ::::
283 ,:::::::::::::::::::: ::,, , ,, ,::::
284 ,:::::::::::::::: ::,, , ,:::,
293 puts "The Master says:"
294 puts Color.cyan(" You have not yet reached enlightenment.")
295 if ((recents = progress.last(5)) && recents.size == 5 && recents.uniq.size == 1)
296 puts Color.cyan(" I sense frustration. Do not be afraid to ask for help.")
297 elsif progress.last(2).size == 2 && progress.last(2).uniq.size == 1
298 puts Color.cyan(" Do not lose hope.")
299 elsif progress.last.to_i > 0
300 puts Color.cyan(" You are progressing. Excellent. #{progress.last} completed.")
304 def guide_through_error
306 puts "The answers you seek..."
307 puts Color.red(indent(failure.message).join)
309 puts "Please meditate on the following code:"
311 puts embolden_first_line_only(indent(find_interesting_lines(failure.backtrace)))
313 puts embolden_first_line_only(indent(failure.backtrace))
318 def embolden_first_line_only(text)
331 text = text.split(/\n/) if text.is_a?(String)
332 text.collect{|t| " #{t}"}
335 def find_interesting_lines(backtrace)
336 backtrace.reject { |line|
337 line =~ /test\/unit\/|edgecase\.rb|minitest/
341 # Hat's tip to Ara T. Howard for the zen statements from his
342 # metakoans Ruby Quiz (http://rubyquiz.com/quiz67.html)
343 def a_zenlike_statement
345 zen_statement = "Mountains are again merely mountains"
347 zen_statement = case (@pass_count % 10)
349 "mountains are merely mountains"
351 "learn the rules so you know how to break them properly"
353 "remember that silence is sometimes the best answer"
355 "sleep is the best meditation"
357 "when you lose, don't lose the lesson"
359 "things are not what they appear to be: nor are they otherwise"
362 puts Color.green(zen_statement)
367 include Test::Unit::Assertions
369 attr_reader :name, :failure, :koan_count, :step_count, :koan_file
371 def initialize(name, koan_file=nil, koan_count=0, step_count=0)
374 @koan_count = koan_count
375 @step_count = step_count
376 @koan_file = koan_file
397 rescue StandardError, EdgeCase::Sensei::AssertionError => ex
402 rescue StandardError, EdgeCase::Sensei::AssertionError => ex
403 failed(ex) if passed?
409 # Class methods for the EdgeCase test suite.
411 def inherited(subclass)
412 subclasses << subclass
415 def method_added(name)
416 testmethods << name if !tests_disabled? && Koan.test_pattern =~ name.to_s
419 def end_of_enlightenment
420 @tests_disabled = true
423 def command_line(args)
427 @test_pattern = Regexp.new($1)
429 @test_pattern = Regexp.new(Regexp.quote($1))
434 fail "Unknown command line argument '#{arg}'"
440 # Lazy initialize list of subclasses
445 # Lazy initialize list of test methods.
451 @tests_disabled ||= false
455 @test_pattern ||= /^test_/
459 self.subclasses.inject(0){|total, k| total + k.testmethods.size }
466 sensei = EdgeCase::Sensei.new
468 sensei.observe(step.meditate)
474 catch(:edgecase_exit) {
476 EdgeCase::Koan.subclasses.each_with_index do |koan,koan_index|
477 koan.testmethods.each do |method_name|
478 step = koan.new(method_name, koan.to_s, koan_index+1, step_count+=1)
488 EdgeCase::Koan.command_line(ARGV)
489 EdgeCase::ThePath.new.walk