4 require 'test/unit/assertions'
9 # --------------------------------------------------------------------
10 # Support code for the Ruby Koans.
11 # --------------------------------------------------------------------
13 class FillMeInError < StandardError
16 def ruby_version?(version)
17 RUBY_VERSION =~ /^#{version}/ ||
18 (version == 'jruby' && defined?(JRUBY_VERSION)) ||
19 (version == 'mri' && ! defined?(JRUBY_VERSION))
22 def in_ruby_version(*versions)
23 yield if versions.any? { |v| ruby_version?(v) }
26 # Standard, generic replacement value.
27 # If value19 is given, it is used in place of value for Ruby 1.9.
28 def __(value="FILL ME IN", value19=:mu)
29 if RUBY_VERSION < "1.9"
32 (value19 == :mu) ? value : value19
36 # Numeric replacement value.
37 def _n_(value=999999, value19=:mu)
38 if RUBY_VERSION < "1.9"
41 (value19 == :mu) ? value : value19
45 # Error object replacement value.
46 def ___(value=FillMeInError)
50 # Method name replacement.
58 in_ruby_version("1.9") do
59 public :method_missing
64 def side_padding(width)
65 extra = width - self.size
69 left_padding = extra / 2
70 right_padding = (extra+1)/2
71 (" " * left_padding) + self + (" " *right_padding)
79 ENV['SIMPLE_KOAN_OUTPUT'] == 'true'
84 #shamelessly stolen (and modified) from redgreen
86 :clear => 0, :black => 30, :red => 31,
87 :green => 32, :yellow => 33, :blue => 34,
88 :magenta => 35, :cyan => 36,
93 COLORS.each do |color, value|
94 module_eval "def #{color}(string); colorize(string, #{value}); end"
98 def colorize(string, color_value)
100 color(color_value) + string + color(COLORS[:clear])
106 def color(color_value)
111 return false if ENV['NO_COLOR']
112 if ENV['ANSI_COLOR'].nil?
117 ENV['ANSI_COLOR'] =~ /^(t|y)/i
124 def using_win32console
125 defined? Win32::Console
130 attr_reader :failure, :failed_test, :pass_count
132 in_ruby_version("1.8") do
133 AssertionError = Test::Unit::AssertionFailedError
136 in_ruby_version("1.9") do
137 if defined?(MiniTest)
138 AssertionError = MiniTest::Assertion
140 AssertionError = Test::Unit::AssertionFailedError
151 PROGRESS_FILE_NAME = '.path_progress'
153 def add_progress(prog)
155 exists = File.exists?(PROGRESS_FILE_NAME)
156 File.open(PROGRESS_FILE_NAME,'a+') do |f|
157 f.print "#{',' if exists}#{prog}"
163 if File.exists?(PROGRESS_FILE_NAME)
164 File.open(PROGRESS_FILE_NAME,'r') do |f|
165 @_contents = f.read.to_s.gsub(/\s/,'').split(',')
177 if @pass_count > progress.last.to_i
178 @observations << Color.green("#{step.koan_file}##{step.name} has expanded your awareness.")
182 @failure = step.failure
183 add_progress(@pass_count)
184 @observations << Color.red("#{step.koan_file}##{step.name} has damaged your karma.")
194 failure.is_a?(AssertionError)
199 @observations.each{|c| puts c }
211 total_tests = EdgeCase::Koan.total_tests
212 scale = bar_width.to_f/total_tests
213 print Color.green("your path thus far [")
214 happy_steps = (pass_count*scale).to_i
215 happy_steps = 1 if happy_steps == 0 && pass_count > 0
216 print Color.green('.'*happy_steps)
219 print Color.cyan('_'*(bar_width-1-happy_steps))
221 print Color.green(']')
222 print " #{pass_count}/#{total_tests}"
227 if EdgeCase.simple_output
234 def boring_end_screen
235 puts "Mountains are again merely mountains"
238 def artistic_end_screen
240 ruby_version = "(in #{'J' if defined?(JRUBY_VERSION)}Ruby #{defined?(JRUBY_VERSION) ? JRUBY_VERSION : RUBY_VERSION})"
241 ruby_version = ruby_version.side_padding(54)
242 completed = <<-ENDTEXT
245 , ,,: :::::::::::::,, :::: : ,
246 , ,,, ,:::::::::::::::::::, ,: ,: ,,
247 :, ::, , , :, ,::::::::::::::::::, ::: ,::::
248 : : ::, ,:::::::: ::, ,::::
249 , ,::::: :,:::::::,::::,
250 ,: , ,:,,: :::::::::::::
251 ::,: ,,:::, ,::::::::::::,
252 ,:::, :,,::: ::::::::::::,
253 ,::: :::::::, Mountains are again merely mountains ,::::::::::::
254 :::,,,:::::: ::::::::::::
255 ,:::::::::::, ::::::::::::,
256 :::::::::::, ,::::::::::::
257 ::::::::::::: ,::::::::::::
258 :::::::::::: Ruby Koans ::::::::::::,
259 ::::::::::::#{ ruby_version },::::::::::::,
260 :::::::::::, , ::::::::::::
261 ,:::::::::::::, brought to you by ,,::::::::::::,
262 :::::::::::::: ,::::::::::::
263 ::::::::::::::, ,:::::::::::::
264 ::::::::::::, EdgeCase Software Artisans , ::::::::::::
265 :,::::::::: :::: :::::::::::::
266 ,::::::::::: ,: ,,:::::::::::::,
267 :::::::::::: ,::::::::::::::,
268 :::::::::::::::::, ::::::::::::::::
269 :::::::::::::::::::, ::::::::::::::::
270 ::::::::::::::::::::::, ,::::,:, , ::::,:::
271 :::::::::::::::::::::::, ::,: ::,::, ,,: ::::
272 ,:::::::::::::::::::: ::,, , ,, ,::::
273 ,:::::::::::::::: ::,, , ,:::,
282 puts "The Master says:"
283 puts Color.cyan(" You have not yet reached enlightenment.")
284 if ((recents = progress.last(5)) && recents.size == 5 && recents.uniq.size == 1)
285 puts Color.cyan(" I sense frustration. Do not be afraid to ask for help.")
286 elsif progress.last(2).size == 2 && progress.last(2).uniq.size == 1
287 puts Color.cyan(" Do not lose hope.")
288 elsif progress.last.to_i > 0
289 puts Color.cyan(" You are progressing. Excellent. #{progress.last} completed.")
293 def guide_through_error
295 puts "The answers you seek..."
296 puts Color.red(indent(failure.message).join)
298 puts "Please meditate on the following code:"
300 puts embolden_first_line_only(indent(find_interesting_lines(failure.backtrace)))
302 puts embolden_first_line_only(indent(failure.backtrace))
307 def embolden_first_line_only(text)
320 text = text.split(/\n/) if text.is_a?(String)
321 text.collect{|t| " #{t}"}
324 def find_interesting_lines(backtrace)
325 backtrace.reject { |line|
326 line =~ /test\/unit\/|edgecase\.rb|minitest/
330 # Hat's tip to Ara T. Howard for the zen statements from his
331 # metakoans Ruby Quiz (http://rubyquiz.com/quiz67.html)
332 def a_zenlike_statement
334 zen_statement = "Mountains are again merely mountains"
336 zen_statement = case (@pass_count % 10)
338 "mountains are merely mountains"
340 "learn the rules so you know how to break them properly"
342 "remember that silence is sometimes the best answer"
344 "sleep is the best meditation"
346 "when you lose, don't lose the lesson"
348 "things are not what they appear to be: nor are they otherwise"
351 puts Color.green(zen_statement)
356 include Test::Unit::Assertions
358 attr_reader :name, :failure, :koan_count, :step_count, :koan_file
360 def initialize(name, koan_file=nil, koan_count=0, step_count=0)
363 @koan_count = koan_count
364 @step_count = step_count
365 @koan_file = koan_file
386 rescue StandardError, EdgeCase::Sensei::AssertionError => ex
391 rescue StandardError, EdgeCase::Sensei::AssertionError => ex
392 failed(ex) if passed?
398 # Class methods for the EdgeCase test suite.
400 def inherited(subclass)
401 subclasses << subclass
404 def method_added(name)
405 testmethods << name if !tests_disabled? && Koan.test_pattern =~ name.to_s
408 def end_of_enlightenment
409 @tests_disabled = true
412 def command_line(args)
416 @test_pattern = Regexp.new($1)
418 @test_pattern = Regexp.new(Regexp.quote($1))
423 fail "Unknown command line argument '#{arg}'"
429 # Lazy initialize list of subclasses
434 # Lazy initialize list of test methods.
440 @tests_disabled ||= false
444 @test_pattern ||= /^test_/
448 self.subclasses.inject(0){|total, k| total + k.testmethods.size }
455 sensei = EdgeCase::Sensei.new
457 sensei.observe(step.meditate)
463 catch(:edgecase_exit) {
465 EdgeCase::Koan.subclasses.each_with_index do |koan,koan_index|
466 koan.testmethods.each do |method_name|
467 step = koan.new(method_name, koan.to_s, koan_index+1, step_count+=1)
477 EdgeCase::Koan.command_line(ARGV)
478 EdgeCase::ThePath.new.walk