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?
112 ENV['ANSI_COLOR'] =~ /^(t|y)/i
122 attr_reader :failure, :failed_test, :pass_count
124 in_ruby_version("1.8") do
125 AssertionError = Test::Unit::AssertionFailedError
128 in_ruby_version("1.9") do
129 if defined?(MiniTest)
130 AssertionError = MiniTest::Assertion
132 AssertionError = Test::Unit::AssertionFailedError
143 PROGRESS_FILE_NAME = '.path_progress'
145 def add_progress(prog)
147 exists = File.exists?(PROGRESS_FILE_NAME)
148 File.open(PROGRESS_FILE_NAME,'a+') do |f|
149 f.print "#{',' if exists}#{prog}"
155 if File.exists?(PROGRESS_FILE_NAME)
156 File.open(PROGRESS_FILE_NAME,'r') do |f|
157 @_contents = f.read.to_s.gsub(/\s/,'').split(',')
169 if @pass_count > progress.last.to_i
170 @observations << Color.green("#{step.koan_file}##{step.name} has expanded your awareness.")
174 @failure = step.failure
175 add_progress(@pass_count)
176 @observations << Color.red("#{step.koan_file}##{step.name} has damaged your karma.")
186 failure.is_a?(AssertionError)
191 @observations.each{|c| puts c }
203 total_tests = EdgeCase::Koan.total_tests
204 scale = bar_width.to_f/total_tests
205 print Color.green("your path thus far [")
206 happy_steps = (pass_count*scale).to_i
207 happy_steps = 1 if happy_steps == 0 && pass_count > 0
208 print Color.green('.'*happy_steps)
211 print Color.cyan('_'*(bar_width-1-happy_steps))
213 print Color.green(']')
214 print " #{pass_count}/#{total_tests}"
219 if EdgeCase.simple_output
226 def boring_end_screen
227 puts "Mountains are again merely mountains"
230 def artistic_end_screen
232 ruby_version = "(in #{'J' if defined?(JRUBY_VERSION)}Ruby #{defined?(JRUBY_VERSION) ? JRUBY_VERSION : RUBY_VERSION})"
233 ruby_version = ruby_version.side_padding(54)
234 completed = <<-ENDTEXT
237 , ,,: :::::::::::::,, :::: : ,
238 , ,,, ,:::::::::::::::::::, ,: ,: ,,
239 :, ::, , , :, ,::::::::::::::::::, ::: ,::::
240 : : ::, ,:::::::: ::, ,::::
241 , ,::::: :,:::::::,::::,
242 ,: , ,:,,: :::::::::::::
243 ::,: ,,:::, ,::::::::::::,
244 ,:::, :,,::: ::::::::::::,
245 ,::: :::::::, Mountains are again merely mountains ,::::::::::::
246 :::,,,:::::: ::::::::::::
247 ,:::::::::::, ::::::::::::,
248 :::::::::::, ,::::::::::::
249 ::::::::::::: ,::::::::::::
250 :::::::::::: Ruby Koans ::::::::::::,
251 ::::::::::::#{ ruby_version },::::::::::::,
252 :::::::::::, , ::::::::::::
253 ,:::::::::::::, brought to you by ,,::::::::::::,
254 :::::::::::::: ,::::::::::::
255 ::::::::::::::, ,:::::::::::::
256 ::::::::::::, EdgeCase Software Artisans , ::::::::::::
257 :,::::::::: :::: :::::::::::::
258 ,::::::::::: ,: ,,:::::::::::::,
259 :::::::::::: ,::::::::::::::,
260 :::::::::::::::::, ::::::::::::::::
261 :::::::::::::::::::, ::::::::::::::::
262 ::::::::::::::::::::::, ,::::,:, , ::::,:::
263 :::::::::::::::::::::::, ::,: ::,::, ,,: ::::
264 ,:::::::::::::::::::: ::,, , ,, ,::::
265 ,:::::::::::::::: ::,, , ,:::,
274 puts "The Master says:"
275 puts Color.cyan(" You have not yet reached enlightenment.")
276 if ((recents = progress.last(5)) && recents.size == 5 && recents.uniq.size == 1)
277 puts Color.cyan(" I sense frustration. Do not be afraid to ask for help.")
278 elsif progress.last(2).size == 2 && progress.last(2).uniq.size == 1
279 puts Color.cyan(" Do not lose hope.")
280 elsif progress.last.to_i > 0
281 puts Color.cyan(" You are progressing. Excellent. #{progress.last} completed.")
285 def guide_through_error
287 puts "The answers you seek..."
288 puts Color.red(indent(failure.message).join)
290 puts "Please meditate on the following code:"
292 puts embolden_first_line_only(indent(find_interesting_lines(failure.backtrace)))
294 puts embolden_first_line_only(indent(failure.backtrace))
299 def embolden_first_line_only(text)
312 text = text.split(/\n/) if text.is_a?(String)
313 text.collect{|t| " #{t}"}
316 def find_interesting_lines(backtrace)
317 backtrace.reject { |line|
318 line =~ /test\/unit\/|edgecase\.rb|minitest/
322 # Hat's tip to Ara T. Howard for the zen statements from his
323 # metakoans Ruby Quiz (http://rubyquiz.com/quiz67.html)
324 def a_zenlike_statement
326 zen_statement = "Mountains are again merely mountains"
328 zen_statement = case (@pass_count % 10)
330 "mountains are merely mountains"
332 "learn the rules so you know how to break them properly"
334 "remember that silence is sometimes the best answer"
336 "sleep is the best meditation"
338 "when you lose, don't lose the lesson"
340 "things are not what they appear to be: nor are they otherwise"
343 puts Color.green(zen_statement)
348 include Test::Unit::Assertions
350 attr_reader :name, :failure, :koan_count, :step_count, :koan_file
352 def initialize(name, koan_file=nil, koan_count=0, step_count=0)
355 @koan_count = koan_count
356 @step_count = step_count
357 @koan_file = koan_file
378 rescue StandardError, EdgeCase::Sensei::AssertionError => ex
383 rescue StandardError, EdgeCase::Sensei::AssertionError => ex
384 failed(ex) if passed?
390 # Class methods for the EdgeCase test suite.
392 def inherited(subclass)
393 subclasses << subclass
396 def method_added(name)
397 testmethods << name if !tests_disabled? && Koan.test_pattern =~ name.to_s
400 def end_of_enlightenment
401 @tests_disabled = true
404 def command_line(args)
408 @test_pattern = Regexp.new($1)
410 @test_pattern = Regexp.new(Regexp.quote($1))
415 fail "Unknown command line argument '#{arg}'"
421 # Lazy initialize list of subclasses
426 # Lazy initialize list of test methods.
432 @tests_disabled ||= false
436 @test_pattern ||= /^test_/
440 self.subclasses.inject(0){|total, k| total + k.testmethods.size }
447 sensei = EdgeCase::Sensei.new
449 sensei.observe(step.meditate)
455 catch(:edgecase_exit) {
457 EdgeCase::Koan.subclasses.each_with_index do |koan,koan_index|
458 koan.testmethods.each do |method_name|
459 step = koan.new(method_name, koan.to_s, koan_index+1, step_count+=1)
469 EdgeCase::Koan.command_line(ARGV)
470 EdgeCase::ThePath.new.walk