-#!/usr/bin/env ruby
-# -*- ruby -*-
-
-require 'test/unit/assertions'
-begin
- require 'win32console'
-rescue LoadError
-end
-
-# --------------------------------------------------------------------
-# Support code for the Ruby Koans.
-# --------------------------------------------------------------------
-
-class FillMeInError < StandardError
-end
-
-def ruby_version?(version)
- RUBY_VERSION =~ /^#{version}/ ||
- (version == 'jruby' && defined?(JRUBY_VERSION)) ||
- (version == 'mri' && ! defined?(JRUBY_VERSION))
-end
-
-def in_ruby_version(*versions)
- yield if versions.any? { |v| ruby_version?(v) }
-end
-
-in_ruby_version("1.8") do
- class KeyError < StandardError
- end
-end
-
-# Standard, generic replacement value.
-# If value19 is given, it is used in place of value for Ruby 1.9.
-def __(value="FILL ME IN", value19=:mu)
- if RUBY_VERSION < "1.9"
- value
- else
- (value19 == :mu) ? value : value19
- end
-end
-
-# Numeric replacement value.
-def _n_(value=999999, value19=:mu)
- if RUBY_VERSION < "1.9"
- value
- else
- (value19 == :mu) ? value : value19
- end
-end
-
-# Error object replacement value.
-def ___(value=FillMeInError, value19=:mu)
- if RUBY_VERSION < "1.9"
- value
- else
- (value19 == :mu) ? value : value19
- end
-end
-
-# Method name replacement.
-class Object
- def ____(method=nil)
- if method
- self.send(method)
- end
- end
-
- in_ruby_version("1.9") do
- public :method_missing
- end
-end
-
-class String
- def side_padding(width)
- extra = width - self.size
- if width < 0
- self
- else
- left_padding = extra / 2
- right_padding = (extra+1)/2
- (" " * left_padding) + self + (" " *right_padding)
- end
- end
-end
-
-module EdgeCase
- class << self
- def simple_output
- ENV['SIMPLE_KOAN_OUTPUT'] == 'true'
- end
- end
-
- module Color
- #shamelessly stolen (and modified) from redgreen
- COLORS = {
- :clear => 0, :black => 30, :red => 31,
- :green => 32, :yellow => 33, :blue => 34,
- :magenta => 35, :cyan => 36,
- }
-
- module_function
-
- COLORS.each do |color, value|
- module_eval "def #{color}(string); colorize(string, #{value}); end"
- module_function color
- end
-
- def colorize(string, color_value)
- if use_colors?
- color(color_value) + string + color(COLORS[:clear])
- else
- string
- end
- end
-
- def color(color_value)
- "\e[#{color_value}m"
- end
-
- def use_colors?
- return false if ENV['NO_COLOR']
- if ENV['ANSI_COLOR'].nil?
- if using_windows?
- using_win32console
- end
- else
- ENV['ANSI_COLOR'] =~ /^(t|y)/i
- end
- end
-
- def using_windows?
- File::ALT_SEPARATOR
- end
-
- def using_win32console
- defined? Win32::Console
- end
- end
-
- class Sensei
- attr_reader :failure, :failed_test, :pass_count
-
- in_ruby_version("1.8") do
- AssertionError = Test::Unit::AssertionFailedError
- end
-
- in_ruby_version("1.9") do
- if defined?(MiniTest)
- AssertionError = MiniTest::Assertion
- else
- AssertionError = Test::Unit::AssertionFailedError
- end
- end
-
- def initialize
- @pass_count = 0
- @failure = nil
- @failed_test = nil
- @observations = []
- end
-
- PROGRESS_FILE_NAME = '.path_progress'
-
- def add_progress(prog)
- @_contents = nil
- exists = File.exists?(PROGRESS_FILE_NAME)
- File.open(PROGRESS_FILE_NAME,'a+') do |f|
- f.print "#{',' if exists}#{prog}"
- end
- end
-
- def progress
- if @_contents.nil?
- if File.exists?(PROGRESS_FILE_NAME)
- File.open(PROGRESS_FILE_NAME,'r') do |f|
- @_contents = f.read.to_s.gsub(/\s/,'').split(',')
- end
- else
- @_contents = []
- end
- end
- @_contents
- end
-
- def observe(step)
- if step.passed?
- @pass_count += 1
- if @pass_count > progress.last.to_i
- @observations << Color.green("#{step.koan_file}##{step.name} has expanded your awareness.")
- end
- else
- @failed_test = step
- @failure = step.failure
- add_progress(@pass_count)
- @observations << Color.red("#{step.koan_file}##{step.name} has damaged your karma.")
- throw :edgecase_exit
- end
- end
-
- def failed?
- ! @failure.nil?
- end
-
- def assert_failed?
- failure.is_a?(AssertionError)
- end
-
- def instruct
- if failed?
- @observations.each{|c| puts c }
- encourage
- guide_through_error
- a_zenlike_statement
- show_progress
- else
- end_screen
- end
- end
-
- def show_progress
- bar_width = 50
- total_tests = EdgeCase::Koan.total_tests
- scale = bar_width.to_f/total_tests
- print Color.green("your path thus far [")
- happy_steps = (pass_count*scale).to_i
- happy_steps = 1 if happy_steps == 0 && pass_count > 0
- print Color.green('.'*happy_steps)
- if failed?
- print Color.red('X')
- print Color.cyan('_'*(bar_width-1-happy_steps))
- end
- print Color.green(']')
- print " #{pass_count}/#{total_tests}"
- puts
- end
-
- def end_screen
- if EdgeCase.simple_output
- boring_end_screen
- else
- artistic_end_screen
- end
- end
-
- def boring_end_screen
- puts "Mountains are again merely mountains"
- end
-
- def artistic_end_screen
- "JRuby 1.9.x Koans"
- ruby_version = "(in #{'J' if defined?(JRUBY_VERSION)}Ruby #{defined?(JRUBY_VERSION) ? JRUBY_VERSION : RUBY_VERSION})"
- ruby_version = ruby_version.side_padding(54)
- completed = <<-ENDTEXT
- ,, , ,,
- : ::::, :::,
- , ,,: :::::::::::::,, :::: : ,
- , ,,, ,:::::::::::::::::::, ,: ,: ,,
- :, ::, , , :, ,::::::::::::::::::, ::: ,::::
- : : ::, ,:::::::: ::, ,::::
- , ,::::: :,:::::::,::::,
- ,: , ,:,,: :::::::::::::
- ::,: ,,:::, ,::::::::::::,
- ,:::, :,,::: ::::::::::::,
- ,::: :::::::, Mountains are again merely mountains ,::::::::::::
- :::,,,:::::: ::::::::::::
- ,:::::::::::, ::::::::::::,
- :::::::::::, ,::::::::::::
-::::::::::::: ,::::::::::::
-:::::::::::: Ruby Koans ::::::::::::,
-::::::::::::#{ ruby_version },::::::::::::,
-:::::::::::, , ::::::::::::
-,:::::::::::::, brought to you by ,,::::::::::::,
-:::::::::::::: ,::::::::::::
- ::::::::::::::, ,:::::::::::::
- ::::::::::::, EdgeCase Software Artisans , ::::::::::::
- :,::::::::: :::: :::::::::::::
- ,::::::::::: ,: ,,:::::::::::::,
- :::::::::::: ,::::::::::::::,
- :::::::::::::::::, ::::::::::::::::
- :::::::::::::::::::, ::::::::::::::::
- ::::::::::::::::::::::, ,::::,:, , ::::,:::
- :::::::::::::::::::::::, ::,: ::,::, ,,: ::::
- ,:::::::::::::::::::: ::,, , ,, ,::::
- ,:::::::::::::::: ::,, , ,:::,
- ,:::: , ,,
- ,,,
-ENDTEXT
- puts completed
- end
-
- def encourage
- puts
- puts "The Master says:"
- puts Color.cyan(" You have not yet reached enlightenment.")
- if ((recents = progress.last(5)) && recents.size == 5 && recents.uniq.size == 1)
- puts Color.cyan(" I sense frustration. Do not be afraid to ask for help.")
- elsif progress.last(2).size == 2 && progress.last(2).uniq.size == 1
- puts Color.cyan(" Do not lose hope.")
- elsif progress.last.to_i > 0
- puts Color.cyan(" You are progressing. Excellent. #{progress.last} completed.")
- end
- end
-
- def guide_through_error
- puts
- puts "The answers you seek..."
- puts Color.red(indent(failure.message).join)
- puts
- puts "Please meditate on the following code:"
- if assert_failed?
- puts embolden_first_line_only(indent(find_interesting_lines(failure.backtrace)))
- else
- puts embolden_first_line_only(indent(failure.backtrace))
- end
- puts
- end
-
- def embolden_first_line_only(text)
- first_line = true
- text.collect { |t|
- if first_line
- first_line = false
- Color.red(t)
- else
- Color.cyan(t)
- end
- }
- end
-
- def indent(text)
- text = text.split(/\n/) if text.is_a?(String)
- text.collect{|t| " #{t}"}
- end
-
- def find_interesting_lines(backtrace)
- backtrace.reject { |line|
- line =~ /test\/unit\/|edgecase\.rb|minitest/
- }
- end
-
- # Hat's tip to Ara T. Howard for the zen statements from his
- # metakoans Ruby Quiz (http://rubyquiz.com/quiz67.html)
- def a_zenlike_statement
- if !failed?
- zen_statement = "Mountains are again merely mountains"
- else
- zen_statement = case (@pass_count % 10)
- when 0
- "mountains are merely mountains"
- when 1, 2
- "learn the rules so you know how to break them properly"
- when 3, 4
- "remember that silence is sometimes the best answer"
- when 5, 6
- "sleep is the best meditation"
- when 7, 8
- "when you lose, don't lose the lesson"
- else
- "things are not what they appear to be: nor are they otherwise"
- end
- end
- puts Color.green(zen_statement)
- end
- end
-
- class Koan
- include Test::Unit::Assertions
-
- attr_reader :name, :failure, :koan_count, :step_count, :koan_file
-
- def initialize(name, koan_file=nil, koan_count=0, step_count=0)
- @name = name
- @failure = nil
- @koan_count = koan_count
- @step_count = step_count
- @koan_file = koan_file
- end
-
- def passed?
- @failure.nil?
- end
-
- def failed(failure)
- @failure = failure
- end
-
- def setup
- end
-
- def teardown
- end
-
- def meditate
- setup
- begin
- send(name)
- rescue StandardError, EdgeCase::Sensei::AssertionError => ex
- failed(ex)
- ensure
- begin
- teardown
- rescue StandardError, EdgeCase::Sensei::AssertionError => ex
- failed(ex) if passed?
- end
- end
- self
- end
-
- # Class methods for the EdgeCase test suite.
- class << self
- def inherited(subclass)
- subclasses << subclass
- end
-
- def method_added(name)
- testmethods << name if !tests_disabled? && Koan.test_pattern =~ name.to_s
- end
-
- def end_of_enlightenment
- @tests_disabled = true
- end
-
- def command_line(args)
- args.each do |arg|
- case arg
- when /^-n\/(.*)\/$/
- @test_pattern = Regexp.new($1)
- when /^-n(.*)$/
- @test_pattern = Regexp.new(Regexp.quote($1))
- else
- if File.exist?(arg)
- load(arg)
- else
- fail "Unknown command line argument '#{arg}'"
- end
- end
- end
- end
-
- # Lazy initialize list of subclasses
- def subclasses
- @subclasses ||= []
- end
-
- # Lazy initialize list of test methods.
- def testmethods
- @test_methods ||= []
- end
-
- def tests_disabled?
- @tests_disabled ||= false
- end
-
- def test_pattern
- @test_pattern ||= /^test_/
- end
-
- def total_tests
- self.subclasses.inject(0){|total, k| total + k.testmethods.size }
- end
- end
- end
-
- class ThePath
- def walk
- sensei = EdgeCase::Sensei.new
- each_step do |step|
- sensei.observe(step.meditate)
- end
- sensei.instruct
- end
-
- def each_step
- catch(:edgecase_exit) {
- step_count = 0
- EdgeCase::Koan.subclasses.each_with_index do |koan,koan_index|
- koan.testmethods.each do |method_name|
- step = koan.new(method_name, koan.to_s, koan_index+1, step_count+=1)
- yield step
- end
- end
- }
- end
- end
-end
-
-END {
- EdgeCase::Koan.command_line(ARGV)
- EdgeCase::ThePath.new.walk
-}