├── .gitignore ├── README.rdoc ├── Rakefile ├── koans ├── Rakefile ├── about_binding.rb ├── about_blocks.rb ├── about_class_as_constant.rb ├── about_class_inheritance.rb ├── about_class_methods.rb ├── about_define_method.rb ├── about_hook_methods.rb ├── about_instance_eval_and_class_eval.rb ├── about_metaclass.rb ├── about_method_added.rb ├── about_modules.rb ├── about_prototype_inheritance.rb ├── about_self.rb ├── about_singleton_methods.rb ├── about_template.rb ├── edgecase.rb ├── path_to_enlightenment.rb └── test_helper.rb ├── rakelib ├── checks.rake └── run.rake └── src ├── Rakefile ├── about_binding.rb ├── about_blocks.rb ├── about_class_as_constant.rb ├── about_class_inheritance.rb ├── about_class_methods.rb ├── about_define_method.rb ├── about_hook_methods.rb ├── about_instance_eval_and_class_eval.rb ├── about_metaclass.rb ├── about_method_added.rb ├── about_modules.rb ├── about_prototype_inheritance.rb ├── about_self.rb ├── about_singleton_methods.rb ├── about_template.rb ├── edgecase.rb ├── path_to_enlightenment.rb └── test_helper.rb /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | .project_env.rc 3 | .path_progress 4 | *.rbc 5 | .idea 6 | -------------------------------------------------------------------------------- /README.rdoc: -------------------------------------------------------------------------------- 1 | = Ruby Metaprogramming Koans 2 | 3 | Ruby Metaprogramming Koans walk you along the path to enlightenment in order to learn Metaprogramming in Ruby. 4 | It is inspired by the original Ruby koans (http://rubykoans.com) by Jim Weirich which can be forked here: https://github.com/edgecase/ruby_koans 5 | 6 | Koans teach you a programming language using Tests/TDD. 7 | Metaprogramming koans attempts to teach Ruby metaprogramming using the same philosophy of koans used for programming languages. 8 | 9 | == Metaprogramming examples 10 | 11 | The metaprogramming examples used in the koans are derived from the following sources: 12 | 13 | 1. Seeing Metaclasses clearly by _why 14 | http://dannytatom.github.com/metaid/ 15 | 2. Metaprogramming in Ruby: It’s All About the Self by Yehuda Katz 16 | http://yehudakatz.com/2009/11/15/metaprogramming-in-ruby-its-all-about-the-self/ 17 | 3. Ruby Object model and Metaprogramming screencasts by Dave Thomas 18 | http://pragprog.com/screencasts/v-dtrubyom/the-ruby-object-model-and-metaprogramming 19 | 20 | 21 | The koans are a starting point and only supplement the above articles/books/videos. 22 | Please use the original source for more enlightenment. 23 | 24 | == The Structure 25 | 26 | The koans are broken out into areas by file, metaclasses are covered in about_metaclasses.rb, 27 | define_method is covered in about_define_method.rb, etc. They are presented in order in the 28 | path_to_enlightenment.rb file. 29 | 30 | Each koan builds up your knowledge of Ruby metaprogramming and builds upon itself. Koans will 31 | have __ or ___ which you need to fill in with the correct answer to progress. 32 | 33 | == Installing Ruby 34 | 35 | The koans require Ruby 1.9.2. To install Ruby: 36 | 37 | 1. Install RVM http://beginrescueend.com/rvm/install/ 38 | 2. Install Ruby http://beginrescueend.com/rvm/install/ 39 | 40 | $ rvm install ruby-1.9.2 41 | 42 | All the koans are in Test::Unit and don't require additional gems 43 | 44 | == The Path To Enlightenment 45 | 46 | To start working your way through the koans, run rake 47 | 48 | metaprogramming_koans$ rake 49 | 50 | To run a specific koan, run it with ruby: 51 | 52 | metaprogramming_koans$ ruby about_metaclasses.rb 53 | 54 | Follow the instructions and keep filling the answers to start your journey towards mastering metaprogramming in Ruby 55 | 56 | == Inspiration 57 | 58 | The Little Schemer (http://www.amazon.com/Little-Schemer-Daniel-P-Friedman/dp/0262560992) 59 | by Daniel Friedman is one of my all time favorite programming books. 60 | It follows the socratic instruction method, which is teaching 61 | by way of asking progressively complex questions. You can learn the basic concepts 62 | of Lisp/Scheme in a few hours. 63 | 64 | I checked out Ruby Koans (http://rubykoans.com/) by JimWeirich and solved it. 65 | The idea of learning a new language using Test driven development was fascinating. 66 | It was very similar to the approach in Little Schemer, except that instead of a book, 67 | it is an automated testsuite that's asking you questions. 68 | 69 | If you're learning Ruby, you should check out the original koans (http://github.com/edgecase/ruby_koans) first: 70 | 71 | == Other Resources 72 | 73 | To get more insight on the Ruby koans way of learning, check out the EdgeCase Ruby koans (http://github.com/edgecase/ruby_koans): 74 | 75 | Following are some more resources to learn Metaprogramming in Ruby: 76 | 1. Seeing Metaclasses early http://dannytatom.github.com/metaid/ 77 | 2. Ruby Object model and metaprogramming screencasts http://pragprog.com/screencasts/v-dtrubyom/the-ruby-object-model-and-metaprogramming 78 | 3. Metaprogramming Ruby http://pragprog.com/book/ppmetr/metaprogramming-ruby 79 | 80 | == How do i add my own koans? 81 | 82 | To add your own koans and contribute to the community: 83 | 1. Fork this repo 84 | 2. Copy src/about_template.rb to src/about_foo.rb 85 | 3. Add src/about_foo to src/path_to_enlightenment.rb 86 | 4. Start adding your koans as test cases 87 | 5. Make sure the solved koans work by running: ruby about_foo.rb 88 | 6. Execute $rake gen to generate koans/about_foo.rb with all assertions replaced with __ 89 | 7. Create pull request 90 | 91 | == License 92 | 93 | You're free to copy, add, change or distribute the koans here. 94 | The structure of this whole project is forked from Edgecase Ruby koans (https://github.com/edgecase/ruby_koans) 95 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # -*- ruby -*- 3 | 4 | require 'rake/clean' 5 | 6 | SRC_DIR = 'src' 7 | KOAN_DIR = 'koans' 8 | 9 | SRC_FILES = FileList["#{SRC_DIR}/*"] 10 | KOAN_FILES = SRC_FILES.pathmap("#{KOAN_DIR}/%f") 11 | 12 | module Koans 13 | # Remove solution info from source 14 | # __(a,b) => __ 15 | # _n_(number) => __ 16 | # # __ => 17 | def Koans.remove_solution(line) 18 | line = line.gsub(/\b____\([^\)]+\)/, "____") 19 | line = line.gsub(/\b___\([^\)]+\)/, "___") 20 | line = line.gsub(/\b__\([^\)]+\)/, "__") 21 | line = line.gsub(/\b_n_\([^\)]+\)/, "_n_") 22 | line = line.gsub(%r(/\#\{__\}/), "/__/") 23 | line = line.gsub(/\s*#\s*__\s*$/, '') 24 | line = line.gsub(/assert_equal [\:"']?([\w\s\!']+)['"]?,/, "assert_equal __,") 25 | line = line.gsub(/assert_match \/(.*?)\//, "assert_match /__/") 26 | line = line.gsub(/assert_raises(\w+)\//, "assert_raises(___)") 27 | line = line[line.index('#Write code here'), line.length] if line =~ /#Write code here/ 28 | line 29 | end 30 | 31 | def Koans.make_koan_file(infile, outfile) 32 | if infile =~ /edgecase/ 33 | cp infile, outfile 34 | elsif infile =~ /autotest/ 35 | cp_r infile, outfile 36 | else 37 | open(infile) do |ins| 38 | open(outfile, "w") do |outs| 39 | state = :copy 40 | ins.each do |line| 41 | state = :skip if line =~ /^ *#--/ 42 | case state 43 | when :copy 44 | outs.puts remove_solution(line) 45 | else 46 | # do nothing 47 | end 48 | state = :copy if line =~ /^ *#\+\+/ 49 | end 50 | end 51 | end 52 | end 53 | end 54 | end 55 | 56 | task :default => :walk_the_path 57 | 58 | task :walk_the_path do 59 | cd 'koans' 60 | ruby 'path_to_enlightenment.rb' 61 | end 62 | 63 | task :run do 64 | puts 'koans' 65 | Dir.chdir("src") do 66 | puts "in #{Dir.pwd}" 67 | sh "ruby path_to_enlightenment.rb" 68 | end 69 | end 70 | 71 | directory KOAN_DIR 72 | 73 | desc "Generate the Koans from the source files from scratch." 74 | task :regen => [:clobber_koans, :gen] 75 | 76 | desc "Generate the Koans from the changed source files." 77 | task :gen => KOAN_FILES 78 | task :clobber_koans do 79 | rm_r KOAN_DIR 80 | end 81 | 82 | SRC_FILES.each do |koan_src| 83 | file koan_src.pathmap("#{KOAN_DIR}/%f") => [KOAN_DIR, koan_src] do |t| 84 | Koans.make_koan_file koan_src, t.name 85 | end 86 | end -------------------------------------------------------------------------------- /koans/Rakefile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # -*- ruby -*- 3 | 4 | require 'rake/clean' 5 | require 'rake/testtask' 6 | 7 | task :default => :test 8 | 9 | task :test do 10 | ruby 'path_to_enlightenment.rb' 11 | end 12 | 13 | -------------------------------------------------------------------------------- /koans/about_binding.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/edgecase') 2 | 3 | class AboutBinding < EdgeCase::Koan 4 | 5 | class Foo 6 | def initialize 7 | @ivar = 22 8 | end 9 | 10 | def bar(param) 11 | lvar = 11 12 | binding 13 | end 14 | end 15 | 16 | def test_binding_binds_method_parameters 17 | binding = Foo.new.bar(99) 18 | assert_equal __, eval("param", binding) 19 | end 20 | 21 | def test_binding_binds_local_vars 22 | binding = Foo.new.bar(99) 23 | assert_equal __, eval("lvar", binding) 24 | end 25 | 26 | def test_binding_binds_instance_vars 27 | binding = Foo.new.bar(99) 28 | assert_equal __, eval("@ivar", binding) 29 | end 30 | 31 | def test_binding_binds_blocks 32 | binding = Foo.new.bar(99) { 33 } 33 | assert_equal __, eval("yield", binding) 34 | end 35 | 36 | def test_binding_binds_self 37 | foo = Foo.new 38 | binding = foo.bar(99) 39 | assert_equal __, eval("self", binding) 40 | end 41 | 42 | def n_times(n) 43 | lambda {|value| n * value} 44 | end 45 | 46 | def test_lambda_binds_to_the_surrounding_context 47 | two_times = n_times(2) 48 | assert_equal __, two_times.call(3) 49 | end 50 | 51 | def count_with_increment(start, inc) 52 | lambda { start += inc} 53 | end 54 | 55 | def test_lambda_remembers_state_of_bound_variables 56 | counter = count_with_increment(7, 3) 57 | assert_equal __, counter.call 58 | assert_equal __, counter.call 59 | assert_equal __, counter.call 60 | end 61 | 62 | end 63 | -------------------------------------------------------------------------------- /koans/about_blocks.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/edgecase') 2 | 3 | class AboutBlocks < EdgeCase::Koan 4 | 5 | def test_calling_a_lambda 6 | l = lambda {|a| a + 1} 7 | assert_equal __, l.call(99) 8 | end 9 | 10 | def test_calling_a_proc 11 | p = Proc.new {|a| a + 1} 12 | assert_equal __, p.call(99) 13 | end 14 | 15 | def convert(&block) 16 | block 17 | end 18 | 19 | def test_block_is_proc 20 | b = convert {|a| a + 1} 21 | assert_equal __, b.class 22 | assert_equal __, b.call(99) 23 | end 24 | 25 | def test_proc_takes_fewer_or_more_arguments 26 | p = Proc.new {|a, b, c| a.to_i + b.to_i + c.to_i} 27 | assert_equal __, p.call(1,2) 28 | assert_equal __, p.call(1,2,3,4) 29 | end 30 | 31 | def test_lambda_does_not_take_fewer_or_more_arguments 32 | l = lambda {|a, b, c| a.to_i + b.to_i + c.to_i} 33 | assert_raises(ArgumentError) do 34 | l.call(1, 2) 35 | end 36 | 37 | assert_raises(ArgumentError) do 38 | l.call(1,2,3,4) 39 | end 40 | end 41 | 42 | def method(lambda_or_proc) 43 | lambda_or_proc.call 44 | :from_method 45 | end 46 | 47 | def test_return_inside_lambda_returns_from_the_lambda 48 | l = lambda { return :from_lambda } 49 | result = method(l) 50 | assert_equal __, result 51 | end 52 | 53 | def test_return_inside_proc_returns_from_the_context 54 | p = Proc.new { return :from_proc } 55 | result = method(p) 56 | # The execution never reaches this line because Proc returns 57 | # outside the test method 58 | assert_equal __, p.call 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /koans/about_class_as_constant.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/edgecase') 2 | 3 | class AboutClassAsConstant < EdgeCase::Koan 4 | 5 | class Foo 6 | def say_hello 7 | "Hi" 8 | end 9 | end 10 | 11 | def test_defined_tells_if_a_class_is_defined_or_not 12 | assert_not_nil defined?(Foo) 13 | assert_nil defined?(Bar) 14 | end 15 | 16 | def test_class_is_a_constant 17 | assert_equal __, defined?(Foo) 18 | end 19 | 20 | def test_class_constant_can_be_assigned_to_var 21 | my_class = Foo 22 | assert_equal __, my_class.new.say_hello 23 | end 24 | 25 | @@return_value_of_class = 26 | class Baz 27 | def say_hi 28 | "Hello" 29 | end 30 | 99 31 | end 32 | 33 | def test_class_definitions_are_active 34 | assert_equal __, @@return_value_of_class 35 | end 36 | 37 | @@self_inside_a_class = 38 | class Baz 39 | def say_hi 40 | "Hi" 41 | end 42 | self 43 | end 44 | 45 | def test_self_inside_class_is_class_itself 46 | assert_equal __, @@self_inside_a_class 47 | end 48 | 49 | def test_class_is_an_object_of_type_class_and_can_be_created_dynamically 50 | cls = Class.new 51 | assert_match /__/, cls.to_s 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /koans/about_class_inheritance.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/edgecase') 2 | 3 | class AboutClassInheritance < EdgeCase::Koan 4 | 5 | def test_singleton_class_can_be_used_to_define_singleton_methods 6 | animal = "cat" 7 | class << animal 8 | def speak 9 | "miaow" 10 | end 11 | end 12 | assert_equal __, animal.speak 13 | end 14 | 15 | class Foo 16 | class << self 17 | def say_hello 18 | "Hello" 19 | end 20 | end 21 | end 22 | 23 | def test_singleton_class_can_be_used_to_define_class_methods 24 | assert_equal __, Foo.say_hello 25 | end 26 | 27 | end 28 | -------------------------------------------------------------------------------- /koans/about_class_methods.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/edgecase') 2 | 3 | class AboutClassMethods < EdgeCase::Koan 4 | 5 | class Foo 6 | def self.say_hello 7 | "Hello" 8 | end 9 | end 10 | 11 | def test_class_is_an_instance_of_class_Class 12 | assert_equal __, Foo.class == Class 13 | end 14 | 15 | def test_class_methods_are_just_singleton_methods_on_the_class 16 | assert_equal __, Foo.say_hello 17 | end 18 | 19 | def test_classes_are_not_special_and_are_just_like_other_objects 20 | assert_equal __, Foo.is_a?(Object) 21 | assert_equal __, Foo.superclass == Object 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /koans/about_define_method.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/edgecase') 2 | 3 | class AboutDefineMethod < EdgeCase::Koan 4 | 5 | class Example 6 | def start 7 | def stop 8 | :stopped 9 | end 10 | :started 11 | end 12 | end 13 | 14 | def test_methods_can_define_other_methods 15 | o = Example.new 16 | assert_raises(NoMethodError) do 17 | o.stop 18 | end 19 | 20 | o.start 21 | 22 | assert_equal __, o.stop 23 | end 24 | 25 | class Example 26 | def foo 27 | def foo 28 | :new_value 29 | end 30 | :first_value 31 | end 32 | end 33 | 34 | def test_methods_can_redefine_themselves 35 | o = Example.new 36 | assert_equal __, o.foo 37 | assert_equal __, o.foo 38 | end 39 | 40 | class Multiplier 41 | def self.create_multiplier(n) 42 | define_method "times_#{n}" do |val| 43 | val * n 44 | end 45 | end 46 | 47 | 10.times {|i| create_multiplier(i) } 48 | end 49 | 50 | def test_define_method_creates_methods_dynamically 51 | m = Multiplier.new 52 | assert_equal __, m.times_3(10) 53 | assert_equal __, m.times_6(10) 54 | assert_equal __, m.times_9(10) 55 | end 56 | 57 | module Accessor 58 | def my_writer(name) 59 | ivar_name = "@#{name}" 60 | define_method "#{name}=" do |value| 61 | #Write code here to set value of ivar 62 | end 63 | end 64 | 65 | def my_reader(name) 66 | ivar_name = "@#{name}" 67 | define_method name do 68 | #Write code here to get value of ivar 69 | end 70 | end 71 | end 72 | 73 | class Cat 74 | extend Accessor 75 | my_writer :name 76 | my_reader :name 77 | end 78 | 79 | def test_instance_variable_set_and_instance_variable_get_can_be_used_to_access_ivars 80 | cat = Cat.new 81 | cat.name = 'Fred' 82 | assert_equal __, cat.name 83 | end 84 | end 85 | 86 | -------------------------------------------------------------------------------- /koans/about_hook_methods.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/edgecase') 2 | 3 | class AboutHookMethods < EdgeCase::Koan 4 | 5 | module Bar 6 | def self.included(klass) 7 | @included = true 8 | end 9 | 10 | def self.included? 11 | @included 12 | end 13 | end 14 | 15 | class Foo 16 | include Bar 17 | end 18 | 19 | def test_included_hook_method_is_called_when_module_is_included_in_class 20 | assert_equal __, Bar.included? 21 | end 22 | 23 | class Parent 24 | def self.inherited(klass) 25 | @inherited = true 26 | end 27 | 28 | def self.inherited? 29 | @inherited 30 | end 31 | end 32 | 33 | class Child < Parent 34 | end 35 | 36 | def test_inherited_hook_method_is_called_when_class_is_subclassed 37 | assert_equal __, Parent.inherited? 38 | end 39 | 40 | class ::Struct 41 | @children = [] 42 | 43 | def self.inherited(klass) 44 | @children << klass 45 | end 46 | 47 | def self.children 48 | @children 49 | end 50 | end 51 | 52 | Cat = Struct.new(:name, :tail) 53 | Dog = Struct.new(:name, :legs) 54 | 55 | def test_inherited_can_track_subclasses 56 | assert_equal [Cat, Dog], Struct.children 57 | end 58 | 59 | class ::Module 60 | def const_missing(name) 61 | if name.to_s =~ /(X?)(IX|IV|(V?)(I{0,3}))/ 62 | to_roman($~) 63 | end 64 | end 65 | end 66 | 67 | def test_const_missing_hook_method_can_be_used_to_dynamically_evaluate_constants 68 | assert_equal __, VIII 69 | end 70 | 71 | class Color 72 | def self.const_missing(name) 73 | const_set(name, new) 74 | end 75 | 76 | end 77 | 78 | def test_const_set_can_be_used_to_dynamically_create_constants 79 | Color::Red 80 | assert_equal __, defined?(Color::Red) 81 | end 82 | end 83 | 84 | def to_roman(match) 85 | value = 0 86 | value += 10 if match[1] == 'X' 87 | value += 9 if match[2] == 'IX' 88 | value += 4 if match[2] == 'IV' 89 | value += 5 if match[3] == 'V' 90 | value += match[4].chars.count if match[4] 91 | value 92 | end 93 | -------------------------------------------------------------------------------- /koans/about_instance_eval_and_class_eval.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/edgecase') 2 | 3 | class AboutInstanceEvalAndClassEval < EdgeCase::Koan 4 | 5 | def test_instance_eval_executes_block_in_the_context_of_the_receiver 6 | assert_equal __, "cat".instance_eval { upcase } 7 | end 8 | 9 | class Foo 10 | def initialize 11 | @ivar = 99 12 | end 13 | end 14 | 15 | def test_instance_eval_can_access_instance_variables 16 | assert_equal __, Foo.new.instance_eval { @ivar } 17 | end 18 | 19 | class Foo 20 | private 21 | def secret 22 | 123 23 | end 24 | end 25 | 26 | def test_instance_eval_can_access_private_methods 27 | assert_equal __, Foo.new.instance_eval { secret } 28 | end 29 | 30 | def test_instance_eval_can_be_used_to_define_singleton_methods 31 | animal = "cat" 32 | animal.instance_eval do 33 | def speak 34 | "miaow" 35 | end 36 | end 37 | assert_equal __, animal.speak 38 | end 39 | 40 | class Cat 41 | end 42 | 43 | def test_instance_eval_can_be_used_to_define_class_methods 44 | Cat.instance_eval do 45 | def say_hello 46 | "Hello" 47 | end 48 | end 49 | 50 | assert_equal __, Cat.say_hello 51 | end 52 | 53 | def test_class_eval_executes_block_in_the_context_of_class_or_module 54 | assert_equal __, Cat.class_eval { self } 55 | end 56 | 57 | def test_class_eval_can_be_used_to_define_instance_methods 58 | Cat.class_eval do 59 | def speak 60 | "miaow" 61 | end 62 | end 63 | assert_equal __, Cat.new.speak 64 | end 65 | 66 | def test_module_eval_is_same_as_class_eval 67 | Cat.module_eval do 68 | def miaow 69 | "miaow" 70 | end 71 | end 72 | assert_equal __, Cat.new.miaow 73 | end 74 | 75 | module Accessor 76 | def my_eval_accessor(name) 77 | # WRITE code here to generate accessors 78 | class_eval %{ 79 | def #{name} 80 | @#{name} 81 | end 82 | 83 | def #{name}=(val) 84 | @#{name} = val 85 | end 86 | } 87 | end 88 | end 89 | 90 | class Cat 91 | extend Accessor 92 | my_eval_accessor :name 93 | end 94 | 95 | def test_class_eval_can_be_used_to_create_instance_methods_like_accessors 96 | cat = Cat.new 97 | cat.name = 'Frisky' 98 | assert_equal __, cat.name 99 | end 100 | 101 | module Hello 102 | def say_hello 103 | "hi" 104 | end 105 | end 106 | 107 | def test_class_eval_can_be_used_to_call_private_methods_on_class 108 | String.class_eval { include Hello } 109 | assert_equal __, "hello".say_hello 110 | end 111 | 112 | class Turtle 113 | attr_reader :path 114 | def initialize 115 | @path = "" 116 | end 117 | 118 | def right(n=1) 119 | @path << 'r' * n 120 | end 121 | 122 | def up(n=1) 123 | @path << 'u' * n 124 | end 125 | end 126 | 127 | class Turtle 128 | def move_yield(&block) 129 | yield 130 | end 131 | end 132 | 133 | def test_yield_executes_block_with_self_as_caller 134 | t = Turtle.new 135 | here = :here 136 | assert_equal __, t.move_yield { here } 137 | end 138 | 139 | class Turtle 140 | def move_eval(&block) 141 | instance_eval(&block) 142 | end 143 | end 144 | 145 | def test_instance_eval_executes_block_with_self_as_called_object 146 | t = Turtle.new 147 | t.move_eval do 148 | right(3) 149 | up(2) 150 | right(1) 151 | end 152 | assert_equal __, t.path 153 | end 154 | 155 | class Turtle 156 | def move_eval_yield(&block) 157 | instance_eval { yield } 158 | end 159 | end 160 | 161 | def test_yield_inside_instance_eval_executes_block_with_self_as_caller 162 | still_here = :still_here 163 | t = Turtle.new 164 | assert_equal __, t.move_eval_yield { still_here } 165 | end 166 | end 167 | -------------------------------------------------------------------------------- /koans/about_metaclass.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/edgecase') 2 | 3 | # Based on _why's article: Seeing Metaclasses clearly 4 | # http://dannytatom.github.com/metaid/ 5 | 6 | class AboutMetaclass < EdgeCase::Koan 7 | 8 | class MailTruck 9 | attr_accessor :driver, :route 10 | def initialize(driver = nil, route = nil) 11 | @driver, @route = driver, route 12 | end 13 | end 14 | 15 | def setup 16 | @truck = MailTruck.new("Harold", ['12 Corrigan way', '23 Antler Ave']) 17 | end 18 | 19 | def test_class_of_an_object 20 | assert_equal __, @truck.class 21 | end 22 | 23 | def test_class_of_a_class 24 | assert_equal __, MailTruck.class 25 | end 26 | 27 | def test_object_is_a_storage_for_variables 28 | assert_equal __, @truck.driver 29 | end 30 | 31 | def test_object_can_hold_any_other_instance_variables 32 | @truck.instance_variable_set("@speed", 45) 33 | assert_equal __, @truck.instance_variable_get("@speed") 34 | end 35 | 36 | def test_attr_accessor_defines_reader_and_writer 37 | @truck.driver = 'Kumar' 38 | assert_equal __, @truck.driver 39 | end 40 | 41 | def test_classes_store_methods 42 | assert_equal __, MailTruck.instance_methods.include?(:driver) 43 | end 44 | 45 | =begin 46 | 47 | BasicObject 48 | | 49 | Object 50 | | 51 | Module 52 | | 53 | Class 54 | 55 | =end 56 | 57 | def test_class_is_an_object 58 | assert_equal __, Class.is_a?(Object) 59 | assert_equal __, Class.superclass 60 | assert_equal __, Class.superclass.superclass 61 | end 62 | 63 | def test_class_has_object_id 64 | assert_equal __, @truck.object_id > 0 65 | assert_equal __, MailTruck.object_id > 0 66 | end 67 | 68 | def test_Object_class_is_Class 69 | assert_equal __, Object.class 70 | end 71 | 72 | def test_Object_inherits_from_Basic_Object 73 | assert_equal __, Object.superclass 74 | end 75 | 76 | def test_Basic_Object_sits_at_the_very_top 77 | assert_equal __, BasicObject.superclass 78 | end 79 | 80 | class MailTruck 81 | def has_mail? 82 | !(@mails.nil? || @mails.empty?) 83 | end 84 | end 85 | 86 | def test_metaclass_is_a_class_which_an_object_uses_to_redefine_itself 87 | assert_equal __, @truck.has_mail? 88 | end 89 | 90 | class ::Class 91 | def is_everything_an_object? 92 | true 93 | end 94 | end 95 | 96 | def test_metaclass_is_a_class_which_even_Class_uses_to_redefine_itself 97 | assert_equal __, Class.is_everything_an_object? 98 | assert_equal __, MailTruck.is_everything_an_object? 99 | end 100 | 101 | def test_singleton_methods_are_defined_only_for_that_instance 102 | red_truck = MailTruck.new 103 | blue_truck = MailTruck.new 104 | def red_truck.honk 105 | "Honk!Honk!" 106 | end 107 | 108 | assert_equal __, red_truck.honk 109 | assert_raises(NoMethodError) do 110 | blue_truck.honk 111 | end 112 | end 113 | 114 | =begin 115 | 116 | MailTruck 117 | | 118 | Metaclass 119 | | 120 | @truck 121 | 122 | =end 123 | 124 | def test_metaclass_sits_between_object_and_class 125 | assert_equal __, @truck.metaclass.superclass 126 | end 127 | 128 | def test_singleton_methods_are_defined_in_metaclass 129 | def @truck.honk 130 | "Honk" 131 | end 132 | assert_equal __, @truck.honk 133 | assert_equal __, @truck.metaclass.instance_methods.include?(:honk) 134 | assert_equal __, @truck.singleton_methods.include?(:honk) 135 | end 136 | 137 | class ::Object 138 | def metaclass 139 | class << self ; self ; end 140 | end 141 | end 142 | 143 | def test_class_lt_lt_opens_up_metaclass 144 | klass = class << @truck ; self ; end 145 | assert_equal __, klass == @truck.metaclass 146 | end 147 | 148 | def test_metaclass_can_have_metaclass_ad_infinitum 149 | assert_equal __, @truck.metaclass.metaclass.nil? 150 | assert_equal __, @truck.metaclass.metaclass.metaclass.nil? 151 | end 152 | 153 | def test_metaclass_of_a_metaclass_does_not_affect_the_original_object 154 | def @truck.honk 155 | "Honk" 156 | end 157 | 158 | metaclass = @truck.metaclass 159 | def metaclass.honk_honk 160 | "Honk Honk" 161 | end 162 | 163 | assert_equal __, @truck.honk 164 | assert_equal __, @truck.meta_eval { honk_honk } 165 | assert_raises(NoMethodError) do 166 | @truck.honk_honk 167 | end 168 | end 169 | 170 | =begin 171 | MailTruck 172 | | 173 | Metaclass -> Metaclass -> Metaclass ... 174 | | 175 | @truck 176 | 177 | =end 178 | class MailTruck 179 | @@trucks = [] 180 | 181 | def MailTruck.add_truck(truck) 182 | @@trucks << truck 183 | end 184 | 185 | def MailTruck.count_trucks 186 | @@trucks.count 187 | end 188 | end 189 | 190 | def test_classes_can_have_class_variables 191 | red_truck = MailTruck.new 192 | blue_truck = MailTruck.new 193 | MailTruck.add_truck(red_truck) 194 | MailTruck.add_truck(blue_truck) 195 | 196 | assert_equal __, MailTruck.count_trucks 197 | end 198 | 199 | class MailTruck 200 | @trucks = [] 201 | 202 | def MailTruck.add_a_truck(truck) 203 | @trucks << truck 204 | end 205 | 206 | def MailTruck.total_trucks 207 | @trucks.count 208 | end 209 | end 210 | 211 | def test_classes_can_have_instance_variables 212 | red_truck = MailTruck.new 213 | blue_truck = MailTruck.new 214 | green_truck = MailTruck.new 215 | MailTruck.add_a_truck(red_truck) 216 | MailTruck.add_a_truck(blue_truck) 217 | MailTruck.add_a_truck(green_truck) 218 | 219 | assert_equal __, MailTruck.total_trucks 220 | end 221 | 222 | def test_class_variable_and_class_instance_variable_are_not_the_same 223 | assert_equal __, MailTruck.count_trucks == MailTruck.total_trucks 224 | end 225 | 226 | class MailTruck 227 | def say_hi 228 | "Hi! I'm one of #{@@trucks.length} trucks" 229 | end 230 | end 231 | 232 | def test_only_class_variables_can_be_accessed_by_instances_of_class 233 | MailTruck.add_truck(@truck) 234 | assert_equal __, @truck.say_hi 235 | end 236 | 237 | def test_class_methods_are_defined_in_metaclass_of_class 238 | assert_equal __, MailTruck.metaclass.instance_methods.include?(:add_truck) 239 | assert_equal __, MailTruck.metaclass.instance_methods.include?(:add_a_truck) 240 | end 241 | 242 | class MailTruck 243 | def self.add_another_truck(truck) 244 | @@trucks << truck 245 | end 246 | end 247 | 248 | def test_class_methods_can_also_be_defined_using_self 249 | MailTruck.add_another_truck(MailTruck.new) 250 | assert_equal __, MailTruck.count_trucks 251 | end 252 | 253 | def test_all_class_methods_are_defined_in_metaclass_of_class 254 | assert_equal __, MailTruck.metaclass.instance_methods.include?(:add_another_truck) 255 | end 256 | 257 | class ::Object 258 | def meta_eval(&block) 259 | metaclass.instance_eval(&block) 260 | end 261 | # Add methods to metaclass 262 | def meta_def name, &block 263 | meta_eval { define_method name, &block } 264 | end 265 | end 266 | 267 | class MailTruck 268 | def self.made_by(name) 269 | meta_def :company do 270 | name 271 | end 272 | end 273 | end 274 | 275 | class ManualTruck < MailTruck 276 | made_by "TrucksRUs" 277 | end 278 | 279 | class RobotTruck < MailTruck 280 | made_by "Lego" 281 | end 282 | 283 | def test_meta_def_can_be_used_to_add_methods_dynamically_to_metaclass 284 | assert_equal __, ManualTruck.company 285 | assert_equal __, RobotTruck.company 286 | end 287 | 288 | class ::Object 289 | # Defines an instance method within a class 290 | def class_def name, &block 291 | class_eval { define_method name, &block } 292 | end 293 | end 294 | 295 | class MailTruck 296 | def self.check_for(attr) 297 | class_def :can_drive? do 298 | instance_variable_get("@#{attr}") != nil 299 | end 300 | end 301 | end 302 | 303 | class ManualTruck < MailTruck 304 | check_for :driver 305 | end 306 | 307 | class RobotTruck < MailTruck 308 | check_for :route 309 | end 310 | 311 | def test_class_def_can_be_used_to_add_instance_methods_dynamically 312 | assert_equal __, ManualTruck.new.can_drive? 313 | assert_equal __, RobotTruck.new.can_drive? 314 | 315 | assert_equal __, ManualTruck.new('Harold', nil).can_drive? 316 | assert_equal __, RobotTruck.new(nil, ['SF']).can_drive? 317 | end 318 | 319 | class ::Object 320 | def meta_eval(&block) 321 | metaclass.instance_eval(&block) 322 | end 323 | 324 | # Add methods to metaclass 325 | def meta_def name, &block 326 | meta_eval { define_method name, &block } 327 | end 328 | 329 | # Defines an instance method within a class 330 | def class_def name, &block 331 | class_eval { define_method name, &block } 332 | end 333 | end 334 | 335 | end 336 | -------------------------------------------------------------------------------- /koans/about_method_added.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/edgecase') 2 | 3 | class AboutMethodAdded < EdgeCase::Koan 4 | 5 | class Cat 6 | @num_of_methods = 0 7 | 8 | def self.method_added(name) 9 | @num_of_methods += 1 10 | end 11 | 12 | def miaow 13 | end 14 | 15 | def speak 16 | end 17 | 18 | def self.num_of_methods 19 | @num_of_methods 20 | end 21 | end 22 | 23 | def test_method_added_hook_method_is_called_for_new_methods 24 | assert_equal __, Cat.num_of_methods 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /koans/about_modules.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/edgecase') 2 | 3 | class AboutModules < EdgeCase::Koan 4 | 5 | module Greeting 6 | def say_hello 7 | "Hello" 8 | end 9 | end 10 | 11 | class Foo 12 | include Greeting 13 | end 14 | 15 | module Greeting 16 | def say_hello 17 | "Hi" 18 | end 19 | end 20 | 21 | def test_module_methods_are_active 22 | assert_equal __, Foo.new.say_hello 23 | end 24 | 25 | def test_extend_adds_singleton_methods 26 | animal = "cat" 27 | animal.extend Greeting 28 | 29 | assert_equal __, animal.say_hello 30 | end 31 | 32 | def test_another_way_to_add_singleton_methods_from_module 33 | animal = "cat" 34 | class << animal 35 | include Greeting 36 | end 37 | 38 | assert_equal __, animal.say_hello 39 | end 40 | 41 | class Bar 42 | extend Greeting 43 | end 44 | 45 | def test_extend_adds_class_methods_or_singleton_methods_on_the_class 46 | assert_equal __, Bar.say_hello 47 | end 48 | 49 | module Moo 50 | def instance_method 51 | :instance_value 52 | end 53 | 54 | module ClassMethods 55 | def class_method 56 | :class_value 57 | end 58 | end 59 | end 60 | 61 | class Baz 62 | include Moo 63 | extend Moo::ClassMethods 64 | end 65 | 66 | def test_include_instance_methods_and_extend_class_methods 67 | assert_equal __, Baz.new.instance_method 68 | assert_equal __, Baz.class_method 69 | end 70 | 71 | module Moo 72 | def self.included(klass) 73 | #WRITE CODE HERE 74 | klass.extend(ClassMethods) 75 | end 76 | end 77 | 78 | class Foo 79 | include Moo 80 | end 81 | 82 | def test_included_is_a_hook_method_that_can_be_used_to_extend_automatically 83 | assert_equal __, Foo.new.instance_method 84 | assert_equal __, Foo.class_method 85 | end 86 | 87 | end 88 | -------------------------------------------------------------------------------- /koans/about_prototype_inheritance.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/edgecase') 2 | 3 | class AboutPrototypeInheritance < EdgeCase::Koan 4 | 5 | def test_clone_copies_singleton_methods 6 | animal = "cat" 7 | def animal.speak 8 | "miaow" 9 | end 10 | other = animal.clone 11 | assert_equal __, other.speak 12 | end 13 | 14 | def test_dup_does_not_copy_singleton_methods 15 | animal = "cat" 16 | def animal.speak 17 | "miaow" 18 | end 19 | other = animal.dup 20 | assert_raises(NoMethodError) do 21 | other.speak 22 | end 23 | end 24 | 25 | def test_state_is_inherited_in_prototype_inheritance 26 | animal = Object.new 27 | def animal.num_of_lives=(lives) 28 | @num_of_lives = lives 29 | end 30 | 31 | def animal.num_of_lives 32 | @num_of_lives 33 | end 34 | 35 | cat = animal.clone 36 | cat.num_of_lives = 9 37 | 38 | felix = cat.clone 39 | assert_equal __, felix.num_of_lives 40 | end 41 | 42 | end 43 | -------------------------------------------------------------------------------- /koans/about_self.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/edgecase') 2 | 3 | # Based on Yehuda Katz's article: Metaprogramming in Ruby: It's all about the self 4 | # http://yehudakatz.com/2009/11/15/metaprogramming-in-ruby-its-all-about-the-self/ 5 | 6 | class AboutSelf < EdgeCase::Koan 7 | 8 | class Person 9 | def self.species 10 | "Homo Sapiens" 11 | end 12 | end 13 | 14 | def test_self_inside_class_defines_class_method 15 | assert_equal __, Person.species 16 | end 17 | 18 | @@self_inside_class = class Person 19 | self 20 | end 21 | 22 | def test_self_inside_class_is_the_class_itself 23 | assert_equal __, @@self_inside_class 24 | end 25 | 26 | class Dog ; end 27 | 28 | class << Dog 29 | def species 30 | "Canis Lupus" 31 | end 32 | end 33 | 34 | def test_self_inside_class_ltlt_class_is_the_metaclass 35 | assert_equal __, Dog.species 36 | end 37 | 38 | class Cat 39 | class << self 40 | def species 41 | "Felis Catus" 42 | end 43 | end 44 | end 45 | 46 | def test_self_inside_class_ltlt_self_is_the_metaclass 47 | # inside class self = Cat 48 | # class << self is same as class << Cat 49 | assert_equal __, Cat.species 50 | end 51 | 52 | class Lion ; end 53 | 54 | @@object = Lion.new 55 | @@self_inside_instance_eval_of_object = @@object.instance_eval { self } 56 | 57 | def test_self_inside_instance_eval_of_object_is_the_object_itself 58 | assert_equal __, @@self_inside_instance_eval_of_object 59 | end 60 | 61 | @@self_inside_instance_eval_of_class = Lion.instance_eval { self } 62 | 63 | def test_self_inside_instance_eval_of_class_is_the_class_itself 64 | assert_equal __, @@self_inside_instance_eval_of_class 65 | end 66 | 67 | Lion.instance_eval do 68 | def species 69 | "Panthera Leo" 70 | end 71 | end 72 | 73 | def test_self_inside_instance_eval_is_class_and_defines_class_methods 74 | assert_equal __, Lion.species 75 | end 76 | 77 | class Tiger ; end 78 | 79 | def Tiger.species 80 | "Panthera Tigris" 81 | end 82 | 83 | def test_singleton_method_on_Class_defines_a_class_method 84 | assert_equal __, Tiger.species 85 | end 86 | 87 | class Person 88 | def name 89 | "Matz" 90 | end 91 | end 92 | 93 | def test_methods_defined_in_a_class_are_instance_methods 94 | assert_equal __, Person.new.name 95 | end 96 | 97 | @@self_inside_class_eval = Cat.class_eval { self } 98 | 99 | def test_self_inside_class_eval_is_the_class_itself 100 | assert_equal __, @@self_inside_class_eval 101 | end 102 | 103 | Cat.class_eval do 104 | def name 105 | "Frisky" 106 | end 107 | end 108 | 109 | def test_class_eval_defines_methods_in_class 110 | assert_equal __, Cat.new.name 111 | end 112 | 113 | class ::Class 114 | def loud_name 115 | "#{name.upcase}" 116 | end 117 | end 118 | 119 | def test_methods_defined_in_Class_class_is_available_to_all_classes 120 | # All classes are subclasses of Class and inherit methods of Class 121 | assert_match __, Person.loud_name 122 | end 123 | 124 | end 125 | -------------------------------------------------------------------------------- /koans/about_singleton_methods.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/edgecase') 2 | 3 | class AboutSingletonMethods < EdgeCase::Koan 4 | 5 | def test_instance_method_calls_method_on_class 6 | animal = "cat" 7 | assert_equal __, animal.upcase 8 | end 9 | 10 | def test_instance_method_calls_method_on_parent_class_if_not_found_in_class 11 | animal = "cat" 12 | assert_equal __, animal.frozen? 13 | end 14 | 15 | def test_singleton_method_calls_method_on_anonymous_or_ghost_or_eigen_or_meta_class 16 | animal = "cat" 17 | def animal.speak 18 | "miaow" 19 | end 20 | assert_equal __, animal.speak 21 | end 22 | 23 | def test_singleton_method_is_available_only_on_that_instance 24 | cat = "cat" 25 | def cat.speak 26 | "miaow" 27 | end 28 | dog = "dog" 29 | assert_raises(NoMethodError) do 30 | dog.speak 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /koans/about_template.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/edgecase') 2 | 3 | class AboutTemplate < EdgeCase::Koan 4 | 5 | def test_foo 6 | assert_equal __, true 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /koans/edgecase.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # -*- ruby -*- 3 | 4 | require 'test/unit/assertions' 5 | 6 | # -------------------------------------------------------------------- 7 | # Support code for the Ruby Koans. 8 | # -------------------------------------------------------------------- 9 | 10 | class FillMeInError < StandardError 11 | end 12 | 13 | def ruby_version?(version) 14 | RUBY_VERSION =~ /^#{version}/ || 15 | (version == 'jruby' && defined?(JRUBY_VERSION)) || 16 | (version == 'mri' && ! defined?(JRUBY_VERSION)) 17 | end 18 | 19 | def in_ruby_version(*versions) 20 | yield if versions.any? { |v| ruby_version?(v) } 21 | end 22 | 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" 27 | value 28 | else 29 | (value19 == :mu) ? value : value19 30 | end 31 | end 32 | 33 | # Numeric replacement value. 34 | def _n_(value=999999, value19=:mu) 35 | if RUBY_VERSION < "1.9" 36 | value 37 | else 38 | (value19 == :mu) ? value : value19 39 | end 40 | end 41 | 42 | # Error object replacement value. 43 | def ___(value=FillMeInError) 44 | value 45 | end 46 | 47 | # Method name replacement. 48 | class Object 49 | def ____(method=nil) 50 | if method 51 | self.send(method) 52 | end 53 | end 54 | 55 | in_ruby_version("1.9") do 56 | public :method_missing 57 | end 58 | end 59 | 60 | class String 61 | def side_padding(width) 62 | extra = width - self.size 63 | if width < 0 64 | self 65 | else 66 | left_padding = extra / 2 67 | right_padding = (extra+1)/2 68 | (" " * left_padding) + self + (" " *right_padding) 69 | end 70 | end 71 | end 72 | 73 | module EdgeCase 74 | class << self 75 | def simple_output 76 | ENV['SIMPLE_KOAN_OUTPUT'] == 'true' 77 | end 78 | end 79 | 80 | module Color 81 | #shamelessly stolen (and modified) from redgreen 82 | COLORS = { 83 | :clear => 0, :black => 30, :red => 31, 84 | :green => 32, :yellow => 33, :blue => 34, 85 | :magenta => 35, :cyan => 36, 86 | } 87 | 88 | module_function 89 | 90 | COLORS.each do |color, value| 91 | module_eval "def #{color}(string); colorize(string, #{value}); end" 92 | module_function color 93 | end 94 | 95 | def colorize(string, color_value) 96 | if use_colors? 97 | color(color_value) + string + color(COLORS[:clear]) 98 | else 99 | string 100 | end 101 | end 102 | 103 | def color(color_value) 104 | "\e[#{color_value}m" 105 | end 106 | 107 | def use_colors? 108 | return false if ENV['NO_COLOR'] 109 | if ENV['ANSI_COLOR'].nil? 110 | ! using_windows? 111 | else 112 | ENV['ANSI_COLOR'] =~ /^(t|y)/i 113 | end 114 | end 115 | 116 | def using_windows? 117 | File::ALT_SEPARATOR 118 | end 119 | end 120 | 121 | class Sensei 122 | attr_reader :failure, :failed_test, :pass_count 123 | 124 | in_ruby_version("1.8") do 125 | AssertionError = Test::Unit::AssertionFailedError 126 | end 127 | 128 | in_ruby_version("1.9") do 129 | if defined?(MiniTest) 130 | AssertionError = MiniTest::Assertion 131 | else 132 | AssertionError = Test::Unit::AssertionFailedError 133 | end 134 | end 135 | 136 | def initialize 137 | @pass_count = 0 138 | @failure = nil 139 | @failed_test = nil 140 | @observations = [] 141 | end 142 | 143 | PROGRESS_FILE_NAME = '.path_progress' 144 | 145 | def add_progress(prog) 146 | @_contents = nil 147 | exists = File.exists?(PROGRESS_FILE_NAME) 148 | File.open(PROGRESS_FILE_NAME,'a+') do |f| 149 | f.print "#{',' if exists}#{prog}" 150 | end 151 | end 152 | 153 | def progress 154 | if @_contents.nil? 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(',') 158 | end 159 | else 160 | @_contents = [] 161 | end 162 | end 163 | @_contents 164 | end 165 | 166 | def observe(step) 167 | if step.passed? 168 | @pass_count += 1 169 | if @pass_count > progress.last.to_i 170 | @observations << Color.green("#{step.koan_file}##{step.name} has expanded your awareness.") 171 | end 172 | else 173 | @failed_test = step 174 | @failure = step.failure 175 | add_progress(@pass_count) 176 | @observations << Color.red("#{step.koan_file}##{step.name} has damaged your karma.") 177 | throw :edgecase_exit 178 | end 179 | end 180 | 181 | def failed? 182 | ! @failure.nil? 183 | end 184 | 185 | def assert_failed? 186 | failure.is_a?(AssertionError) 187 | end 188 | 189 | def instruct 190 | if failed? 191 | @observations.each{|c| puts c } 192 | encourage 193 | guide_through_error 194 | a_zenlike_statement 195 | show_progress 196 | else 197 | end_screen 198 | end 199 | end 200 | 201 | def show_progress 202 | bar_width = 50 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) 209 | if failed? 210 | print Color.red('X') 211 | print Color.cyan('_'*(bar_width-1-happy_steps)) 212 | end 213 | print Color.green(']') 214 | print " #{pass_count}/#{total_tests}" 215 | puts 216 | end 217 | 218 | def end_screen 219 | if EdgeCase.simple_output 220 | boring_end_screen 221 | else 222 | artistic_end_screen 223 | end 224 | end 225 | 226 | def boring_end_screen 227 | puts "Mountains are again merely mountains" 228 | end 229 | 230 | def artistic_end_screen 231 | "JRuby 1.9.x Koans" 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 235 | ,, , ,, 236 | : ::::, :::, 237 | , ,,: :::::::::::::,, :::: : , 238 | , ,,, ,:::::::::::::::::::, ,: ,: ,, 239 | :, ::, , , :, ,::::::::::::::::::, ::: ,:::: 240 | : : ::, ,:::::::: ::, ,:::: 241 | , ,::::: :,:::::::,::::, 242 | ,: , ,:,,: ::::::::::::: 243 | ::,: ,,:::, ,::::::::::::, 244 | ,:::, :,,::: ::::::::::::, 245 | ,::: :::::::, Mountains are again merely mountains ,:::::::::::: 246 | :::,,,:::::: :::::::::::: 247 | ,:::::::::::, ::::::::::::, 248 | :::::::::::, ,:::::::::::: 249 | ::::::::::::: ,:::::::::::: 250 | :::::::::::: Ruby Metaprogramming Koans ::::::::::::, 251 | ::::::::::::#{ ruby_version },::::::::::::, 252 | :::::::::::, , :::::::::::: 253 | ,:::::::::::::, ,,::::::::::::, 254 | :::::::::::::: ,:::::::::::: 255 | ::::::::::::::, ,::::::::::::: 256 | ::::::::::::, , :::::::::::: 257 | :,::::::::: :::: ::::::::::::: 258 | ,::::::::::: ,: ,,:::::::::::::, 259 | :::::::::::: ,::::::::::::::, 260 | :::::::::::::::::, :::::::::::::::: 261 | :::::::::::::::::::, :::::::::::::::: 262 | ::::::::::::::::::::::, ,::::,:, , ::::,::: 263 | :::::::::::::::::::::::, ::,: ::,::, ,,: :::: 264 | ,:::::::::::::::::::: ::,, , ,, ,:::: 265 | ,:::::::::::::::: ::,, , ,:::, 266 | ,:::: , ,, 267 | ,,, 268 | ENDTEXT 269 | puts completed 270 | end 271 | 272 | def encourage 273 | puts 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.") 282 | end 283 | end 284 | 285 | def guide_through_error 286 | puts 287 | puts "The answers you seek..." 288 | puts Color.red(indent(failure.message).join) 289 | puts 290 | puts "Please meditate on the following code:" 291 | if assert_failed? 292 | puts embolden_first_line_only(indent(find_interesting_lines(failure.backtrace))) 293 | else 294 | puts embolden_first_line_only(indent(failure.backtrace)) 295 | end 296 | puts 297 | end 298 | 299 | def embolden_first_line_only(text) 300 | first_line = true 301 | text.collect { |t| 302 | if first_line 303 | first_line = false 304 | Color.red(t) 305 | else 306 | Color.cyan(t) 307 | end 308 | } 309 | end 310 | 311 | def indent(text) 312 | text = text.split(/\n/) if text.is_a?(String) 313 | text.collect{|t| " #{t}"} 314 | end 315 | 316 | def find_interesting_lines(backtrace) 317 | backtrace.reject { |line| 318 | line =~ /test\/unit\/|edgecase\.rb|minitest/ 319 | } 320 | end 321 | 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 325 | if !failed? 326 | zen_statement = "Mountains are again merely mountains" 327 | else 328 | zen_statement = case (@pass_count % 10) 329 | when 0 330 | "mountains are merely mountains" 331 | when 1, 2 332 | "learn the rules so you know how to break them properly" 333 | when 3, 4 334 | "remember that silence is sometimes the best answer" 335 | when 5, 6 336 | "sleep is the best meditation" 337 | when 7, 8 338 | "when you lose, don't lose the lesson" 339 | else 340 | "things are not what they appear to be: nor are they otherwise" 341 | end 342 | end 343 | puts Color.green(zen_statement) 344 | end 345 | end 346 | 347 | class Koan 348 | include Test::Unit::Assertions 349 | 350 | attr_reader :name, :failure, :koan_count, :step_count, :koan_file 351 | 352 | def initialize(name, koan_file=nil, koan_count=0, step_count=0) 353 | @name = name 354 | @failure = nil 355 | @koan_count = koan_count 356 | @step_count = step_count 357 | @koan_file = koan_file 358 | end 359 | 360 | def passed? 361 | @failure.nil? 362 | end 363 | 364 | def failed(failure) 365 | @failure = failure 366 | end 367 | 368 | def setup 369 | end 370 | 371 | def teardown 372 | end 373 | 374 | def meditate 375 | setup 376 | begin 377 | send(name) 378 | rescue StandardError, EdgeCase::Sensei::AssertionError => ex 379 | failed(ex) 380 | ensure 381 | begin 382 | teardown 383 | rescue StandardError, EdgeCase::Sensei::AssertionError => ex 384 | failed(ex) if passed? 385 | end 386 | end 387 | self 388 | end 389 | 390 | # Class methods for the EdgeCase test suite. 391 | class << self 392 | def inherited(subclass) 393 | subclasses << subclass 394 | end 395 | 396 | def method_added(name) 397 | testmethods << name if !tests_disabled? && Koan.test_pattern =~ name.to_s 398 | end 399 | 400 | def end_of_enlightenment 401 | @tests_disabled = true 402 | end 403 | 404 | def command_line(args) 405 | args.each do |arg| 406 | case arg 407 | when /^-n\/(.*)\/$/ 408 | @test_pattern = Regexp.new($1) 409 | when /^-n(.*)$/ 410 | @test_pattern = Regexp.new(Regexp.quote($1)) 411 | else 412 | if File.exist?(arg) 413 | load(arg) 414 | else 415 | fail "Unknown command line argument '#{arg}'" 416 | end 417 | end 418 | end 419 | end 420 | 421 | # Lazy initialize list of subclasses 422 | def subclasses 423 | @subclasses ||= [] 424 | end 425 | 426 | # Lazy initialize list of test methods. 427 | def testmethods 428 | @test_methods ||= [] 429 | end 430 | 431 | def tests_disabled? 432 | @tests_disabled ||= false 433 | end 434 | 435 | def test_pattern 436 | @test_pattern ||= /^test_/ 437 | end 438 | 439 | def total_tests 440 | self.subclasses.inject(0){|total, k| total + k.testmethods.size } 441 | end 442 | end 443 | end 444 | 445 | class ThePath 446 | def walk 447 | sensei = EdgeCase::Sensei.new 448 | each_step do |step| 449 | sensei.observe(step.meditate) 450 | end 451 | sensei.instruct 452 | end 453 | 454 | def each_step 455 | catch(:edgecase_exit) { 456 | step_count = 0 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) 460 | yield step 461 | end 462 | end 463 | } 464 | end 465 | end 466 | end 467 | 468 | END { 469 | EdgeCase::Koan.command_line(ARGV) 470 | EdgeCase::ThePath.new.walk 471 | } 472 | -------------------------------------------------------------------------------- /koans/path_to_enlightenment.rb: -------------------------------------------------------------------------------- 1 | # The path to Ruby Metaprogramming Enlightenment starts with the following: 2 | 3 | $LOAD_PATH << File.dirname(__FILE__) 4 | 5 | require 'about_metaclass' 6 | require 'about_self' 7 | require 'about_singleton_methods' 8 | require 'about_class_as_constant' 9 | require 'about_class_methods' 10 | require 'about_prototype_inheritance' 11 | require 'about_class_inheritance' 12 | require 'about_modules' 13 | require 'about_blocks' 14 | require 'about_binding' 15 | require 'about_define_method' 16 | require 'about_instance_eval_and_class_eval' 17 | require 'about_hook_methods' 18 | require 'about_method_added' 19 | -------------------------------------------------------------------------------- /koans/test_helper.rb: -------------------------------------------------------------------------------- 1 | require 'test/unit' 2 | 3 | def __ 4 | "FILL ME IN" 5 | end 6 | 7 | EdgeCase = Test::Unit 8 | -------------------------------------------------------------------------------- /rakelib/checks.rake: -------------------------------------------------------------------------------- 1 | namespace "check" do 2 | 3 | desc "Check that the require files match the about_* files" 4 | task :abouts do 5 | about_files = Dir['src/about_*.rb'].size 6 | about_requires = `grep require src/path_to_enlightenment.rb | wc -l`.to_i 7 | puts "Checking path_to_enlightenment completeness" 8 | puts "# of about files: #{about_files}" 9 | puts "# of about requires: #{about_requires}" 10 | if about_files > about_requires 11 | puts "*** There seems to be requires missing in the path to enlightenment" 12 | else 13 | puts "OK" 14 | end 15 | puts 16 | end 17 | 18 | desc "Check that asserts have __ replacements" 19 | task :asserts do 20 | puts "Checking for asserts missing the replacement text:" 21 | begin 22 | sh "egrep -n 'assert( |_)' src/about_* | egrep -v '__|_n_|project|about_assert' | egrep -v ' *#'" 23 | puts 24 | puts "Examine the above lines for missing __ replacements" 25 | rescue RuntimeError => ex 26 | puts "OK" 27 | end 28 | puts 29 | end 30 | end 31 | 32 | desc "Run some simple consistency checks" 33 | task :check => ["check:abouts", "check:asserts"] 34 | -------------------------------------------------------------------------------- /rakelib/run.rake: -------------------------------------------------------------------------------- 1 | RUBIES = ENV['KOAN_RUBIES'] || %w(ruby-1.8.7-p299,ruby-1.9.2-p0,jruby-1.5.2,jruby-head) 2 | 3 | task :runall do 4 | chdir('src') do 5 | ENV['SIMPLE_KOAN_OUTPUT'] = 'true' 6 | sh "rvm #{RUBIES} path_to_enlightenment.rb" 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /src/Rakefile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # -*- ruby -*- 3 | 4 | require 'rake/clean' 5 | require 'rake/testtask' 6 | 7 | task :default => :test 8 | 9 | task :test do 10 | ruby 'path_to_enlightenment.rb' 11 | end 12 | 13 | -------------------------------------------------------------------------------- /src/about_binding.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/edgecase') 2 | 3 | class AboutBinding < EdgeCase::Koan 4 | 5 | class Foo 6 | def initialize 7 | @ivar = 22 8 | end 9 | 10 | def bar(param) 11 | lvar = 11 12 | binding 13 | end 14 | end 15 | 16 | def test_binding_binds_method_parameters 17 | binding = Foo.new.bar(99) 18 | assert_equal 99, eval("param", binding) 19 | end 20 | 21 | def test_binding_binds_local_vars 22 | binding = Foo.new.bar(99) 23 | assert_equal 11, eval("lvar", binding) 24 | end 25 | 26 | def test_binding_binds_instance_vars 27 | binding = Foo.new.bar(99) 28 | assert_equal 22, eval("@ivar", binding) 29 | end 30 | 31 | def test_binding_binds_blocks 32 | binding = Foo.new.bar(99) { 33 } 33 | assert_equal 33, eval("yield", binding) 34 | end 35 | 36 | def test_binding_binds_self 37 | foo = Foo.new 38 | binding = foo.bar(99) 39 | assert_equal foo, eval("self", binding) 40 | end 41 | 42 | def n_times(n) 43 | lambda {|value| n * value} 44 | end 45 | 46 | def test_lambda_binds_to_the_surrounding_context 47 | two_times = n_times(2) 48 | assert_equal 6, two_times.call(3) 49 | end 50 | 51 | def count_with_increment(start, inc) 52 | lambda { start += inc} 53 | end 54 | 55 | def test_lambda_remembers_state_of_bound_variables 56 | counter = count_with_increment(7, 3) 57 | assert_equal 10, counter.call 58 | assert_equal 13, counter.call 59 | assert_equal 16, counter.call 60 | end 61 | 62 | end 63 | -------------------------------------------------------------------------------- /src/about_blocks.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/edgecase') 2 | 3 | class AboutBlocks < EdgeCase::Koan 4 | 5 | def test_calling_a_lambda 6 | l = lambda {|a| a + 1} 7 | assert_equal 100, l.call(99) 8 | end 9 | 10 | def test_calling_a_proc 11 | p = Proc.new {|a| a + 1} 12 | assert_equal 100, p.call(99) 13 | end 14 | 15 | def convert(&block) 16 | block 17 | end 18 | 19 | def test_block_is_proc 20 | b = convert {|a| a + 1} 21 | assert_equal Proc, b.class 22 | assert_equal 100, b.call(99) 23 | end 24 | 25 | def test_proc_takes_fewer_or_more_arguments 26 | p = Proc.new {|a, b, c| a.to_i + b.to_i + c.to_i} 27 | assert_equal 3 , p.call(1,2) 28 | assert_equal 6, p.call(1,2,3,4) 29 | end 30 | 31 | def test_lambda_does_not_take_fewer_or_more_arguments 32 | l = lambda {|a, b, c| a.to_i + b.to_i + c.to_i} 33 | assert_raises(ArgumentError) do 34 | l.call(1, 2) 35 | end 36 | 37 | assert_raises(ArgumentError) do 38 | l.call(1,2,3,4) 39 | end 40 | end 41 | 42 | def method(lambda_or_proc) 43 | lambda_or_proc.call 44 | :from_method 45 | end 46 | 47 | def test_return_inside_lambda_returns_from_the_lambda 48 | l = lambda { return :from_lambda } 49 | result = method(l) 50 | assert_equal :from_method, result 51 | end 52 | 53 | def test_return_inside_proc_returns_from_the_context 54 | p = Proc.new { return :from_proc } 55 | result = method(p) 56 | # The execution never reaches this line because Proc returns 57 | # outside the test method 58 | assert_equal __, p.call 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /src/about_class_as_constant.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/edgecase') 2 | 3 | class AboutClassAsConstant < EdgeCase::Koan 4 | 5 | class Foo 6 | def say_hello 7 | "Hi" 8 | end 9 | end 10 | 11 | def test_defined_tells_if_a_class_is_defined_or_not 12 | assert_not_nil defined?(Foo) 13 | assert_nil defined?(Bar) 14 | end 15 | 16 | def test_class_is_a_constant 17 | assert_equal "constant", defined?(Foo) 18 | end 19 | 20 | def test_class_constant_can_be_assigned_to_var 21 | my_class = Foo 22 | assert_equal "Hi", my_class.new.say_hello 23 | end 24 | 25 | @@return_value_of_class = 26 | class Baz 27 | def say_hi 28 | "Hello" 29 | end 30 | 99 31 | end 32 | 33 | def test_class_definitions_are_active 34 | assert_equal 99, @@return_value_of_class 35 | end 36 | 37 | @@self_inside_a_class = 38 | class Baz 39 | def say_hi 40 | "Hi" 41 | end 42 | self 43 | end 44 | 45 | def test_self_inside_class_is_class_itself 46 | assert_equal Baz, @@self_inside_a_class 47 | end 48 | 49 | def test_class_is_an_object_of_type_class_and_can_be_created_dynamically 50 | cls = Class.new 51 | assert_match /Class/, cls.to_s 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /src/about_class_inheritance.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/edgecase') 2 | 3 | class AboutClassInheritance < EdgeCase::Koan 4 | 5 | def test_singleton_class_can_be_used_to_define_singleton_methods 6 | animal = "cat" 7 | class << animal 8 | def speak 9 | "miaow" 10 | end 11 | end 12 | assert_equal "miaow", animal.speak 13 | end 14 | 15 | class Foo 16 | class << self 17 | def say_hello 18 | "Hello" 19 | end 20 | end 21 | end 22 | 23 | def test_singleton_class_can_be_used_to_define_class_methods 24 | assert_equal "Hello", Foo.say_hello 25 | end 26 | 27 | end 28 | -------------------------------------------------------------------------------- /src/about_class_methods.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/edgecase') 2 | 3 | class AboutClassMethods < EdgeCase::Koan 4 | 5 | class Foo 6 | def self.say_hello 7 | "Hello" 8 | end 9 | end 10 | 11 | def test_class_is_an_instance_of_class_Class 12 | assert_equal true, Foo.class == Class 13 | end 14 | 15 | def test_class_methods_are_just_singleton_methods_on_the_class 16 | assert_equal "Hello", Foo.say_hello 17 | end 18 | 19 | def test_classes_are_not_special_and_are_just_like_other_objects 20 | assert_equal true, Foo.is_a?(Object) 21 | assert_equal true, Foo.superclass == Object 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /src/about_define_method.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/edgecase') 2 | 3 | class AboutDefineMethod < EdgeCase::Koan 4 | 5 | class Example 6 | def start 7 | def stop 8 | :stopped 9 | end 10 | :started 11 | end 12 | end 13 | 14 | def test_methods_can_define_other_methods 15 | o = Example.new 16 | assert_raises(NoMethodError) do 17 | o.stop 18 | end 19 | 20 | o.start 21 | 22 | assert_equal :stopped, o.stop 23 | end 24 | 25 | class Example 26 | def foo 27 | def foo 28 | :new_value 29 | end 30 | :first_value 31 | end 32 | end 33 | 34 | def test_methods_can_redefine_themselves 35 | o = Example.new 36 | assert_equal :first_value, o.foo 37 | assert_equal :new_value, o.foo 38 | end 39 | 40 | class Multiplier 41 | def self.create_multiplier(n) 42 | define_method "times_#{n}" do |val| 43 | val * n 44 | end 45 | end 46 | 47 | 10.times {|i| create_multiplier(i) } 48 | end 49 | 50 | def test_define_method_creates_methods_dynamically 51 | m = Multiplier.new 52 | assert_equal 30, m.times_3(10) 53 | assert_equal 60, m.times_6(10) 54 | assert_equal 90, m.times_9(10) 55 | end 56 | 57 | module Accessor 58 | def my_writer(name) 59 | ivar_name = "@#{name}" 60 | define_method "#{name}=" do |value| 61 | instance_variable_set(ivar_name, value) #Write code here to set value of ivar 62 | end 63 | end 64 | 65 | def my_reader(name) 66 | ivar_name = "@#{name}" 67 | define_method name do 68 | instance_variable_get(ivar_name) #Write code here to get value of ivar 69 | end 70 | end 71 | end 72 | 73 | class Cat 74 | extend Accessor 75 | my_writer :name 76 | my_reader :name 77 | end 78 | 79 | def test_instance_variable_set_and_instance_variable_get_can_be_used_to_access_ivars 80 | cat = Cat.new 81 | cat.name = 'Fred' 82 | assert_equal 'Fred', cat.name 83 | end 84 | end 85 | 86 | -------------------------------------------------------------------------------- /src/about_hook_methods.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/edgecase') 2 | 3 | class AboutHookMethods < EdgeCase::Koan 4 | 5 | module Bar 6 | def self.included(klass) 7 | @included = true 8 | end 9 | 10 | def self.included? 11 | @included 12 | end 13 | end 14 | 15 | class Foo 16 | include Bar 17 | end 18 | 19 | def test_included_hook_method_is_called_when_module_is_included_in_class 20 | assert_equal true, Bar.included? 21 | end 22 | 23 | class Parent 24 | def self.inherited(klass) 25 | @inherited = true 26 | end 27 | 28 | def self.inherited? 29 | @inherited 30 | end 31 | end 32 | 33 | class Child < Parent 34 | end 35 | 36 | def test_inherited_hook_method_is_called_when_class_is_subclassed 37 | assert_equal true, Parent.inherited? 38 | end 39 | 40 | class ::Struct 41 | @children = [] 42 | 43 | def self.inherited(klass) 44 | @children << klass 45 | end 46 | 47 | def self.children 48 | @children 49 | end 50 | end 51 | 52 | Cat = Struct.new(:name, :tail) 53 | Dog = Struct.new(:name, :legs) 54 | 55 | def test_inherited_can_track_subclasses 56 | assert_equal [Cat, Dog], Struct.children 57 | end 58 | 59 | class ::Module 60 | def const_missing(name) 61 | if name.to_s =~ /(X?)(IX|IV|(V?)(I{0,3}))/ 62 | to_roman($~) 63 | end 64 | end 65 | end 66 | 67 | def test_const_missing_hook_method_can_be_used_to_dynamically_evaluate_constants 68 | assert_equal 8, VIII 69 | end 70 | 71 | class Color 72 | def self.const_missing(name) 73 | const_set(name, new) 74 | end 75 | 76 | end 77 | 78 | def test_const_set_can_be_used_to_dynamically_create_constants 79 | Color::Red 80 | assert_equal 'constant', defined?(Color::Red) 81 | end 82 | end 83 | 84 | def to_roman(match) 85 | value = 0 86 | value += 10 if match[1] == 'X' 87 | value += 9 if match[2] == 'IX' 88 | value += 4 if match[2] == 'IV' 89 | value += 5 if match[3] == 'V' 90 | value += match[4].chars.count if match[4] 91 | value 92 | end 93 | -------------------------------------------------------------------------------- /src/about_instance_eval_and_class_eval.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/edgecase') 2 | 3 | class AboutInstanceEvalAndClassEval < EdgeCase::Koan 4 | 5 | def test_instance_eval_executes_block_in_the_context_of_the_receiver 6 | assert_equal "CAT", "cat".instance_eval { upcase } 7 | end 8 | 9 | class Foo 10 | def initialize 11 | @ivar = 99 12 | end 13 | end 14 | 15 | def test_instance_eval_can_access_instance_variables 16 | assert_equal 99, Foo.new.instance_eval { @ivar } 17 | end 18 | 19 | class Foo 20 | private 21 | def secret 22 | 123 23 | end 24 | end 25 | 26 | def test_instance_eval_can_access_private_methods 27 | assert_equal 123, Foo.new.instance_eval { secret } 28 | end 29 | 30 | def test_instance_eval_can_be_used_to_define_singleton_methods 31 | animal = "cat" 32 | animal.instance_eval do 33 | def speak 34 | "miaow" 35 | end 36 | end 37 | assert_equal "miaow", animal.speak 38 | end 39 | 40 | class Cat 41 | end 42 | 43 | def test_instance_eval_can_be_used_to_define_class_methods 44 | Cat.instance_eval do 45 | def say_hello 46 | "Hello" 47 | end 48 | end 49 | 50 | assert_equal "Hello", Cat.say_hello 51 | end 52 | 53 | def test_class_eval_executes_block_in_the_context_of_class_or_module 54 | assert_equal Cat, Cat.class_eval { self } 55 | end 56 | 57 | def test_class_eval_can_be_used_to_define_instance_methods 58 | Cat.class_eval do 59 | def speak 60 | "miaow" 61 | end 62 | end 63 | assert_equal "miaow", Cat.new.speak 64 | end 65 | 66 | def test_module_eval_is_same_as_class_eval 67 | Cat.module_eval do 68 | def miaow 69 | "miaow" 70 | end 71 | end 72 | assert_equal "miaow", Cat.new.miaow 73 | end 74 | 75 | module Accessor 76 | def my_eval_accessor(name) 77 | # WRITE code here to generate accessors 78 | class_eval %{ 79 | def #{name} 80 | @#{name} 81 | end 82 | 83 | def #{name}=(val) 84 | @#{name} = val 85 | end 86 | } 87 | end 88 | end 89 | 90 | class Cat 91 | extend Accessor 92 | my_eval_accessor :name 93 | end 94 | 95 | def test_class_eval_can_be_used_to_create_instance_methods_like_accessors 96 | cat = Cat.new 97 | cat.name = 'Frisky' 98 | assert_equal 'Frisky', cat.name 99 | end 100 | 101 | module Hello 102 | def say_hello 103 | "hi" 104 | end 105 | end 106 | 107 | def test_class_eval_can_be_used_to_call_private_methods_on_class 108 | String.class_eval { include Hello } 109 | assert_equal "hi", "hello".say_hello 110 | end 111 | 112 | class Turtle 113 | attr_reader :path 114 | def initialize 115 | @path = "" 116 | end 117 | 118 | def right(n=1) 119 | @path << 'r' * n 120 | end 121 | 122 | def up(n=1) 123 | @path << 'u' * n 124 | end 125 | end 126 | 127 | class Turtle 128 | def move_yield(&block) 129 | yield 130 | end 131 | end 132 | 133 | def test_yield_executes_block_with_self_as_caller 134 | t = Turtle.new 135 | here = :here 136 | assert_equal :here, t.move_yield { here } 137 | end 138 | 139 | class Turtle 140 | def move_eval(&block) 141 | instance_eval(&block) 142 | end 143 | end 144 | 145 | def test_instance_eval_executes_block_with_self_as_called_object 146 | t = Turtle.new 147 | t.move_eval do 148 | right(3) 149 | up(2) 150 | right(1) 151 | end 152 | assert_equal 'rrruur', t.path 153 | end 154 | 155 | class Turtle 156 | def move_eval_yield(&block) 157 | instance_eval { yield } 158 | end 159 | end 160 | 161 | def test_yield_inside_instance_eval_executes_block_with_self_as_caller 162 | still_here = :still_here 163 | t = Turtle.new 164 | assert_equal :still_here, t.move_eval_yield { still_here } 165 | end 166 | end 167 | -------------------------------------------------------------------------------- /src/about_metaclass.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/edgecase') 2 | 3 | # Based on _why's article: Seeing Metaclasses clearly 4 | # http://dannytatom.github.com/metaid/ 5 | 6 | class AboutMetaclass < EdgeCase::Koan 7 | 8 | class MailTruck 9 | attr_accessor :driver, :route 10 | def initialize(driver = nil, route = nil) 11 | @driver, @route = driver, route 12 | end 13 | end 14 | 15 | def setup 16 | @truck = MailTruck.new("Harold", ['12 Corrigan way', '23 Antler Ave']) 17 | end 18 | 19 | def test_class_of_an_object 20 | assert_equal MailTruck, @truck.class 21 | end 22 | 23 | def test_class_of_a_class 24 | assert_equal Class, MailTruck.class 25 | end 26 | 27 | def test_object_is_a_storage_for_variables 28 | assert_equal "Harold", @truck.driver 29 | end 30 | 31 | def test_object_can_hold_any_other_instance_variables 32 | @truck.instance_variable_set("@speed", 45) 33 | assert_equal 45, @truck.instance_variable_get("@speed") 34 | end 35 | 36 | def test_attr_accessor_defines_reader_and_writer 37 | @truck.driver = 'Kumar' 38 | assert_equal 'Kumar', @truck.driver 39 | end 40 | 41 | def test_classes_store_methods 42 | assert_equal true, MailTruck.instance_methods.include?(:driver) 43 | end 44 | 45 | =begin 46 | 47 | BasicObject 48 | | 49 | Object 50 | | 51 | Module 52 | | 53 | Class 54 | 55 | =end 56 | 57 | def test_class_is_an_object 58 | assert_equal true, Class.is_a?(Object) 59 | assert_equal Module, Class.superclass 60 | assert_equal Object, Class.superclass.superclass 61 | end 62 | 63 | def test_class_has_object_id 64 | assert_equal true, @truck.object_id > 0 65 | assert_equal true, MailTruck.object_id > 0 66 | end 67 | 68 | def test_Object_class_is_Class 69 | assert_equal Class, Object.class 70 | end 71 | 72 | def test_Object_inherits_from_Basic_Object 73 | assert_equal BasicObject, Object.superclass 74 | end 75 | 76 | def test_Basic_Object_sits_at_the_very_top 77 | assert_equal nil, BasicObject.superclass 78 | end 79 | 80 | class MailTruck 81 | def has_mail? 82 | !(@mails.nil? || @mails.empty?) 83 | end 84 | end 85 | 86 | def test_metaclass_is_a_class_which_an_object_uses_to_redefine_itself 87 | assert_equal false, @truck.has_mail? 88 | end 89 | 90 | class ::Class 91 | def is_everything_an_object? 92 | true 93 | end 94 | end 95 | 96 | def test_metaclass_is_a_class_which_even_Class_uses_to_redefine_itself 97 | assert_equal true, Class.is_everything_an_object? 98 | assert_equal true, MailTruck.is_everything_an_object? 99 | end 100 | 101 | def test_singleton_methods_are_defined_only_for_that_instance 102 | red_truck = MailTruck.new 103 | blue_truck = MailTruck.new 104 | def red_truck.honk 105 | "Honk!Honk!" 106 | end 107 | 108 | assert_equal "Honk!Honk!", red_truck.honk 109 | assert_raises(NoMethodError) do 110 | blue_truck.honk 111 | end 112 | end 113 | 114 | =begin 115 | 116 | MailTruck 117 | | 118 | Metaclass 119 | | 120 | @truck 121 | 122 | =end 123 | 124 | def test_metaclass_sits_between_object_and_class 125 | assert_equal MailTruck, @truck.metaclass.superclass 126 | end 127 | 128 | def test_singleton_methods_are_defined_in_metaclass 129 | def @truck.honk 130 | "Honk" 131 | end 132 | assert_equal "Honk", @truck.honk 133 | assert_equal true, @truck.metaclass.instance_methods.include?(:honk) 134 | assert_equal true, @truck.singleton_methods.include?(:honk) 135 | end 136 | 137 | class ::Object 138 | def metaclass 139 | class << self ; self ; end 140 | end 141 | end 142 | 143 | def test_class_lt_lt_opens_up_metaclass 144 | klass = class << @truck ; self ; end 145 | assert_equal true, klass == @truck.metaclass 146 | end 147 | 148 | def test_metaclass_can_have_metaclass_ad_infinitum 149 | assert_equal false, @truck.metaclass.metaclass.nil? 150 | assert_equal false, @truck.metaclass.metaclass.metaclass.nil? 151 | end 152 | 153 | def test_metaclass_of_a_metaclass_does_not_affect_the_original_object 154 | def @truck.honk 155 | "Honk" 156 | end 157 | 158 | metaclass = @truck.metaclass 159 | def metaclass.honk_honk 160 | "Honk Honk" 161 | end 162 | 163 | assert_equal "Honk", @truck.honk 164 | assert_equal "Honk Honk", @truck.meta_eval { honk_honk } 165 | assert_raises(NoMethodError) do 166 | @truck.honk_honk 167 | end 168 | end 169 | 170 | =begin 171 | MailTruck 172 | | 173 | Metaclass -> Metaclass -> Metaclass ... 174 | | 175 | @truck 176 | 177 | =end 178 | class MailTruck 179 | @@trucks = [] 180 | 181 | def MailTruck.add_truck(truck) 182 | @@trucks << truck 183 | end 184 | 185 | def MailTruck.count_trucks 186 | @@trucks.count 187 | end 188 | end 189 | 190 | def test_classes_can_have_class_variables 191 | red_truck = MailTruck.new 192 | blue_truck = MailTruck.new 193 | MailTruck.add_truck(red_truck) 194 | MailTruck.add_truck(blue_truck) 195 | 196 | assert_equal 2, MailTruck.count_trucks 197 | end 198 | 199 | class MailTruck 200 | @trucks = [] 201 | 202 | def MailTruck.add_a_truck(truck) 203 | @trucks << truck 204 | end 205 | 206 | def MailTruck.total_trucks 207 | @trucks.count 208 | end 209 | end 210 | 211 | def test_classes_can_have_instance_variables 212 | red_truck = MailTruck.new 213 | blue_truck = MailTruck.new 214 | green_truck = MailTruck.new 215 | MailTruck.add_a_truck(red_truck) 216 | MailTruck.add_a_truck(blue_truck) 217 | MailTruck.add_a_truck(green_truck) 218 | 219 | assert_equal 3, MailTruck.total_trucks 220 | end 221 | 222 | def test_class_variable_and_class_instance_variable_are_not_the_same 223 | assert_equal false, MailTruck.count_trucks == MailTruck.total_trucks 224 | end 225 | 226 | class MailTruck 227 | def say_hi 228 | "Hi! I'm one of #{@@trucks.length} trucks" 229 | end 230 | end 231 | 232 | def test_only_class_variables_can_be_accessed_by_instances_of_class 233 | MailTruck.add_truck(@truck) 234 | assert_equal "Hi! I'm one of 3 trucks", @truck.say_hi 235 | end 236 | 237 | def test_class_methods_are_defined_in_metaclass_of_class 238 | assert_equal true, MailTruck.metaclass.instance_methods.include?(:add_truck) 239 | assert_equal true, MailTruck.metaclass.instance_methods.include?(:add_a_truck) 240 | end 241 | 242 | class MailTruck 243 | def self.add_another_truck(truck) 244 | @@trucks << truck 245 | end 246 | end 247 | 248 | def test_class_methods_can_also_be_defined_using_self 249 | MailTruck.add_another_truck(MailTruck.new) 250 | assert_equal 4, MailTruck.count_trucks 251 | end 252 | 253 | def test_all_class_methods_are_defined_in_metaclass_of_class 254 | assert_equal true, MailTruck.metaclass.instance_methods.include?(:add_another_truck) 255 | end 256 | 257 | class ::Object 258 | def meta_eval(&block) 259 | metaclass.instance_eval(&block) 260 | end 261 | # Add methods to metaclass 262 | def meta_def name, &block 263 | meta_eval { define_method name, &block } 264 | end 265 | end 266 | 267 | class MailTruck 268 | def self.made_by(name) 269 | meta_def :company do 270 | name 271 | end 272 | end 273 | end 274 | 275 | class ManualTruck < MailTruck 276 | made_by "TrucksRUs" 277 | end 278 | 279 | class RobotTruck < MailTruck 280 | made_by "Lego" 281 | end 282 | 283 | def test_meta_def_can_be_used_to_add_methods_dynamically_to_metaclass 284 | assert_equal "TrucksRUs", ManualTruck.company 285 | assert_equal "Lego", RobotTruck.company 286 | end 287 | 288 | class ::Object 289 | # Defines an instance method within a class 290 | def class_def name, &block 291 | class_eval { define_method name, &block } 292 | end 293 | end 294 | 295 | class MailTruck 296 | def self.check_for(attr) 297 | class_def :can_drive? do 298 | instance_variable_get("@#{attr}") != nil 299 | end 300 | end 301 | end 302 | 303 | class ManualTruck < MailTruck 304 | check_for :driver 305 | end 306 | 307 | class RobotTruck < MailTruck 308 | check_for :route 309 | end 310 | 311 | def test_class_def_can_be_used_to_add_instance_methods_dynamically 312 | assert_equal false, ManualTruck.new.can_drive? 313 | assert_equal false, RobotTruck.new.can_drive? 314 | 315 | assert_equal true, ManualTruck.new('Harold', nil).can_drive? 316 | assert_equal true, RobotTruck.new(nil, ['SF']).can_drive? 317 | end 318 | 319 | class ::Object 320 | def meta_eval(&block) 321 | metaclass.instance_eval(&block) 322 | end 323 | 324 | # Add methods to metaclass 325 | def meta_def name, &block 326 | meta_eval { define_method name, &block } 327 | end 328 | 329 | # Defines an instance method within a class 330 | def class_def name, &block 331 | class_eval { define_method name, &block } 332 | end 333 | end 334 | 335 | end 336 | -------------------------------------------------------------------------------- /src/about_method_added.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/edgecase') 2 | 3 | class AboutMethodAdded < EdgeCase::Koan 4 | 5 | class Cat 6 | @num_of_methods = 0 7 | 8 | def self.method_added(name) 9 | @num_of_methods += 1 10 | end 11 | 12 | def miaow 13 | end 14 | 15 | def speak 16 | end 17 | 18 | def self.num_of_methods 19 | @num_of_methods 20 | end 21 | end 22 | 23 | def test_method_added_hook_method_is_called_for_new_methods 24 | assert_equal 2, Cat.num_of_methods 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /src/about_modules.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/edgecase') 2 | 3 | class AboutModules < EdgeCase::Koan 4 | 5 | module Greeting 6 | def say_hello 7 | "Hello" 8 | end 9 | end 10 | 11 | class Foo 12 | include Greeting 13 | end 14 | 15 | module Greeting 16 | def say_hello 17 | "Hi" 18 | end 19 | end 20 | 21 | def test_module_methods_are_active 22 | assert_equal "Hi", Foo.new.say_hello 23 | end 24 | 25 | def test_extend_adds_singleton_methods 26 | animal = "cat" 27 | animal.extend Greeting 28 | 29 | assert_equal "Hi", animal.say_hello 30 | end 31 | 32 | def test_another_way_to_add_singleton_methods_from_module 33 | animal = "cat" 34 | class << animal 35 | include Greeting 36 | end 37 | 38 | assert_equal "Hi", animal.say_hello 39 | end 40 | 41 | class Bar 42 | extend Greeting 43 | end 44 | 45 | def test_extend_adds_class_methods_or_singleton_methods_on_the_class 46 | assert_equal "Hi", Bar.say_hello 47 | end 48 | 49 | module Moo 50 | def instance_method 51 | :instance_value 52 | end 53 | 54 | module ClassMethods 55 | def class_method 56 | :class_value 57 | end 58 | end 59 | end 60 | 61 | class Baz 62 | include Moo 63 | extend Moo::ClassMethods 64 | end 65 | 66 | def test_include_instance_methods_and_extend_class_methods 67 | assert_equal :instance_value, Baz.new.instance_method 68 | assert_equal :class_value, Baz.class_method 69 | end 70 | 71 | module Moo 72 | def self.included(klass) 73 | #WRITE CODE HERE 74 | klass.extend(ClassMethods) 75 | end 76 | end 77 | 78 | class Foo 79 | include Moo 80 | end 81 | 82 | def test_included_is_a_hook_method_that_can_be_used_to_extend_automatically 83 | assert_equal :instance_value, Foo.new.instance_method 84 | assert_equal :class_value, Foo.class_method 85 | end 86 | 87 | end 88 | -------------------------------------------------------------------------------- /src/about_prototype_inheritance.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/edgecase') 2 | 3 | class AboutPrototypeInheritance < EdgeCase::Koan 4 | 5 | def test_clone_copies_singleton_methods 6 | animal = "cat" 7 | def animal.speak 8 | "miaow" 9 | end 10 | other = animal.clone 11 | assert_equal "miaow", other.speak 12 | end 13 | 14 | def test_dup_does_not_copy_singleton_methods 15 | animal = "cat" 16 | def animal.speak 17 | "miaow" 18 | end 19 | other = animal.dup 20 | assert_raises(NoMethodError) do 21 | other.speak 22 | end 23 | end 24 | 25 | def test_state_is_inherited_in_prototype_inheritance 26 | animal = Object.new 27 | def animal.num_of_lives=(lives) 28 | @num_of_lives = lives 29 | end 30 | 31 | def animal.num_of_lives 32 | @num_of_lives 33 | end 34 | 35 | cat = animal.clone 36 | cat.num_of_lives = 9 37 | 38 | felix = cat.clone 39 | assert_equal 9, felix.num_of_lives 40 | end 41 | 42 | end 43 | -------------------------------------------------------------------------------- /src/about_self.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/edgecase') 2 | 3 | # Based on Yehuda Katz's article: Metaprogramming in Ruby: It's all about the self 4 | # http://yehudakatz.com/2009/11/15/metaprogramming-in-ruby-its-all-about-the-self/ 5 | 6 | class AboutSelf < EdgeCase::Koan 7 | 8 | class Person 9 | def self.species 10 | "Homo Sapiens" 11 | end 12 | end 13 | 14 | def test_self_inside_class_defines_class_method 15 | assert_equal "Homo Sapiens", Person.species 16 | end 17 | 18 | @@self_inside_class = class Person 19 | self 20 | end 21 | 22 | def test_self_inside_class_is_the_class_itself 23 | assert_equal Person, @@self_inside_class 24 | end 25 | 26 | class Dog ; end 27 | 28 | class << Dog 29 | def species 30 | "Canis Lupus" 31 | end 32 | end 33 | 34 | def test_self_inside_class_ltlt_class_is_the_metaclass 35 | assert_equal "Canis Lupus", Dog.species 36 | end 37 | 38 | class Cat 39 | class << self 40 | def species 41 | "Felis Catus" 42 | end 43 | end 44 | end 45 | 46 | def test_self_inside_class_ltlt_self_is_the_metaclass 47 | # inside class self = Cat 48 | # class << self is same as class << Cat 49 | assert_equal "Felis Catus", Cat.species 50 | end 51 | 52 | class Lion ; end 53 | 54 | @@object = Lion.new 55 | @@self_inside_instance_eval_of_object = @@object.instance_eval { self } 56 | 57 | def test_self_inside_instance_eval_of_object_is_the_object_itself 58 | assert_equal @@object, @@self_inside_instance_eval_of_object 59 | end 60 | 61 | @@self_inside_instance_eval_of_class = Lion.instance_eval { self } 62 | 63 | def test_self_inside_instance_eval_of_class_is_the_class_itself 64 | assert_equal Lion, @@self_inside_instance_eval_of_class 65 | end 66 | 67 | Lion.instance_eval do 68 | def species 69 | "Panthera Leo" 70 | end 71 | end 72 | 73 | def test_self_inside_instance_eval_is_class_and_defines_class_methods 74 | assert_equal "Panthera Leo", Lion.species 75 | end 76 | 77 | class Tiger ; end 78 | 79 | def Tiger.species 80 | "Panthera Tigris" 81 | end 82 | 83 | def test_singleton_method_on_Class_defines_a_class_method 84 | assert_equal "Panthera Tigris", Tiger.species 85 | end 86 | 87 | class Person 88 | def name 89 | "Matz" 90 | end 91 | end 92 | 93 | def test_methods_defined_in_a_class_are_instance_methods 94 | assert_equal "Matz", Person.new.name 95 | end 96 | 97 | @@self_inside_class_eval = Cat.class_eval { self } 98 | 99 | def test_self_inside_class_eval_is_the_class_itself 100 | assert_equal Cat, @@self_inside_class_eval 101 | end 102 | 103 | Cat.class_eval do 104 | def name 105 | "Frisky" 106 | end 107 | end 108 | 109 | def test_class_eval_defines_methods_in_class 110 | assert_equal "Frisky", Cat.new.name 111 | end 112 | 113 | class ::Class 114 | def loud_name 115 | "#{name.upcase}" 116 | end 117 | end 118 | 119 | def test_methods_defined_in_Class_class_is_available_to_all_classes 120 | # All classes are subclasses of Class and inherit methods of Class 121 | assert_match "PERSON", Person.loud_name 122 | end 123 | 124 | end 125 | -------------------------------------------------------------------------------- /src/about_singleton_methods.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/edgecase') 2 | 3 | class AboutSingletonMethods < EdgeCase::Koan 4 | 5 | def test_instance_method_calls_method_on_class 6 | animal = "cat" 7 | assert_equal "CAT", animal.upcase 8 | end 9 | 10 | def test_instance_method_calls_method_on_parent_class_if_not_found_in_class 11 | animal = "cat" 12 | assert_equal false, animal.frozen? 13 | end 14 | 15 | def test_singleton_method_calls_method_on_anonymous_or_ghost_or_eigen_or_meta_class 16 | animal = "cat" 17 | def animal.speak 18 | "miaow" 19 | end 20 | assert_equal "miaow", animal.speak 21 | end 22 | 23 | def test_singleton_method_is_available_only_on_that_instance 24 | cat = "cat" 25 | def cat.speak 26 | "miaow" 27 | end 28 | dog = "dog" 29 | assert_raises(NoMethodError) do 30 | dog.speak 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /src/about_template.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/edgecase') 2 | 3 | class AboutTemplate < EdgeCase::Koan 4 | 5 | def test_foo 6 | assert_equal __, true 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /src/edgecase.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # -*- ruby -*- 3 | 4 | require 'test/unit/assertions' 5 | 6 | # -------------------------------------------------------------------- 7 | # Support code for the Ruby Koans. 8 | # -------------------------------------------------------------------- 9 | 10 | class FillMeInError < StandardError 11 | end 12 | 13 | def ruby_version?(version) 14 | RUBY_VERSION =~ /^#{version}/ || 15 | (version == 'jruby' && defined?(JRUBY_VERSION)) || 16 | (version == 'mri' && ! defined?(JRUBY_VERSION)) 17 | end 18 | 19 | def in_ruby_version(*versions) 20 | yield if versions.any? { |v| ruby_version?(v) } 21 | end 22 | 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" 27 | value 28 | else 29 | (value19 == :mu) ? value : value19 30 | end 31 | end 32 | 33 | # Numeric replacement value. 34 | def _n_(value=999999, value19=:mu) 35 | if RUBY_VERSION < "1.9" 36 | value 37 | else 38 | (value19 == :mu) ? value : value19 39 | end 40 | end 41 | 42 | # Error object replacement value. 43 | def ___(value=FillMeInError) 44 | value 45 | end 46 | 47 | # Method name replacement. 48 | class Object 49 | def ____(method=nil) 50 | if method 51 | self.send(method) 52 | end 53 | end 54 | 55 | in_ruby_version("1.9") do 56 | public :method_missing 57 | end 58 | end 59 | 60 | class String 61 | def side_padding(width) 62 | extra = width - self.size 63 | if width < 0 64 | self 65 | else 66 | left_padding = extra / 2 67 | right_padding = (extra+1)/2 68 | (" " * left_padding) + self + (" " *right_padding) 69 | end 70 | end 71 | end 72 | 73 | module EdgeCase 74 | class << self 75 | def simple_output 76 | ENV['SIMPLE_KOAN_OUTPUT'] == 'true' 77 | end 78 | end 79 | 80 | module Color 81 | #shamelessly stolen (and modified) from redgreen 82 | COLORS = { 83 | :clear => 0, :black => 30, :red => 31, 84 | :green => 32, :yellow => 33, :blue => 34, 85 | :magenta => 35, :cyan => 36, 86 | } 87 | 88 | module_function 89 | 90 | COLORS.each do |color, value| 91 | module_eval "def #{color}(string); colorize(string, #{value}); end" 92 | module_function color 93 | end 94 | 95 | def colorize(string, color_value) 96 | if use_colors? 97 | color(color_value) + string + color(COLORS[:clear]) 98 | else 99 | string 100 | end 101 | end 102 | 103 | def color(color_value) 104 | "\e[#{color_value}m" 105 | end 106 | 107 | def use_colors? 108 | return false if ENV['NO_COLOR'] 109 | if ENV['ANSI_COLOR'].nil? 110 | ! using_windows? 111 | else 112 | ENV['ANSI_COLOR'] =~ /^(t|y)/i 113 | end 114 | end 115 | 116 | def using_windows? 117 | File::ALT_SEPARATOR 118 | end 119 | end 120 | 121 | class Sensei 122 | attr_reader :failure, :failed_test, :pass_count 123 | 124 | in_ruby_version("1.8") do 125 | AssertionError = Test::Unit::AssertionFailedError 126 | end 127 | 128 | in_ruby_version("1.9") do 129 | if defined?(MiniTest) 130 | AssertionError = MiniTest::Assertion 131 | else 132 | AssertionError = Test::Unit::AssertionFailedError 133 | end 134 | end 135 | 136 | def initialize 137 | @pass_count = 0 138 | @failure = nil 139 | @failed_test = nil 140 | @observations = [] 141 | end 142 | 143 | PROGRESS_FILE_NAME = '.path_progress' 144 | 145 | def add_progress(prog) 146 | @_contents = nil 147 | exists = File.exists?(PROGRESS_FILE_NAME) 148 | File.open(PROGRESS_FILE_NAME,'a+') do |f| 149 | f.print "#{',' if exists}#{prog}" 150 | end 151 | end 152 | 153 | def progress 154 | if @_contents.nil? 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(',') 158 | end 159 | else 160 | @_contents = [] 161 | end 162 | end 163 | @_contents 164 | end 165 | 166 | def observe(step) 167 | if step.passed? 168 | @pass_count += 1 169 | if @pass_count > progress.last.to_i 170 | @observations << Color.green("#{step.koan_file}##{step.name} has expanded your awareness.") 171 | end 172 | else 173 | @failed_test = step 174 | @failure = step.failure 175 | add_progress(@pass_count) 176 | @observations << Color.red("#{step.koan_file}##{step.name} has damaged your karma.") 177 | throw :edgecase_exit 178 | end 179 | end 180 | 181 | def failed? 182 | ! @failure.nil? 183 | end 184 | 185 | def assert_failed? 186 | failure.is_a?(AssertionError) 187 | end 188 | 189 | def instruct 190 | if failed? 191 | @observations.each{|c| puts c } 192 | encourage 193 | guide_through_error 194 | a_zenlike_statement 195 | show_progress 196 | else 197 | end_screen 198 | end 199 | end 200 | 201 | def show_progress 202 | bar_width = 50 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) 209 | if failed? 210 | print Color.red('X') 211 | print Color.cyan('_'*(bar_width-1-happy_steps)) 212 | end 213 | print Color.green(']') 214 | print " #{pass_count}/#{total_tests}" 215 | puts 216 | end 217 | 218 | def end_screen 219 | if EdgeCase.simple_output 220 | boring_end_screen 221 | else 222 | artistic_end_screen 223 | end 224 | end 225 | 226 | def boring_end_screen 227 | puts "Mountains are again merely mountains" 228 | end 229 | 230 | def artistic_end_screen 231 | "JRuby 1.9.x Koans" 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 235 | ,, , ,, 236 | : ::::, :::, 237 | , ,,: :::::::::::::,, :::: : , 238 | , ,,, ,:::::::::::::::::::, ,: ,: ,, 239 | :, ::, , , :, ,::::::::::::::::::, ::: ,:::: 240 | : : ::, ,:::::::: ::, ,:::: 241 | , ,::::: :,:::::::,::::, 242 | ,: , ,:,,: ::::::::::::: 243 | ::,: ,,:::, ,::::::::::::, 244 | ,:::, :,,::: ::::::::::::, 245 | ,::: :::::::, Mountains are again merely mountains ,:::::::::::: 246 | :::,,,:::::: :::::::::::: 247 | ,:::::::::::, ::::::::::::, 248 | :::::::::::, ,:::::::::::: 249 | ::::::::::::: ,:::::::::::: 250 | :::::::::::: Ruby Metaprogramming Koans ::::::::::::, 251 | ::::::::::::#{ ruby_version },::::::::::::, 252 | :::::::::::, , :::::::::::: 253 | ,:::::::::::::, ,,::::::::::::, 254 | :::::::::::::: ,:::::::::::: 255 | ::::::::::::::, ,::::::::::::: 256 | ::::::::::::, , :::::::::::: 257 | :,::::::::: :::: ::::::::::::: 258 | ,::::::::::: ,: ,,:::::::::::::, 259 | :::::::::::: ,::::::::::::::, 260 | :::::::::::::::::, :::::::::::::::: 261 | :::::::::::::::::::, :::::::::::::::: 262 | ::::::::::::::::::::::, ,::::,:, , ::::,::: 263 | :::::::::::::::::::::::, ::,: ::,::, ,,: :::: 264 | ,:::::::::::::::::::: ::,, , ,, ,:::: 265 | ,:::::::::::::::: ::,, , ,:::, 266 | ,:::: , ,, 267 | ,,, 268 | ENDTEXT 269 | puts completed 270 | end 271 | 272 | def encourage 273 | puts 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.") 282 | end 283 | end 284 | 285 | def guide_through_error 286 | puts 287 | puts "The answers you seek..." 288 | puts Color.red(indent(failure.message).join) 289 | puts 290 | puts "Please meditate on the following code:" 291 | if assert_failed? 292 | puts embolden_first_line_only(indent(find_interesting_lines(failure.backtrace))) 293 | else 294 | puts embolden_first_line_only(indent(failure.backtrace)) 295 | end 296 | puts 297 | end 298 | 299 | def embolden_first_line_only(text) 300 | first_line = true 301 | text.collect { |t| 302 | if first_line 303 | first_line = false 304 | Color.red(t) 305 | else 306 | Color.cyan(t) 307 | end 308 | } 309 | end 310 | 311 | def indent(text) 312 | text = text.split(/\n/) if text.is_a?(String) 313 | text.collect{|t| " #{t}"} 314 | end 315 | 316 | def find_interesting_lines(backtrace) 317 | backtrace.reject { |line| 318 | line =~ /test\/unit\/|edgecase\.rb|minitest/ 319 | } 320 | end 321 | 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 325 | if !failed? 326 | zen_statement = "Mountains are again merely mountains" 327 | else 328 | zen_statement = case (@pass_count % 10) 329 | when 0 330 | "mountains are merely mountains" 331 | when 1, 2 332 | "learn the rules so you know how to break them properly" 333 | when 3, 4 334 | "remember that silence is sometimes the best answer" 335 | when 5, 6 336 | "sleep is the best meditation" 337 | when 7, 8 338 | "when you lose, don't lose the lesson" 339 | else 340 | "things are not what they appear to be: nor are they otherwise" 341 | end 342 | end 343 | puts Color.green(zen_statement) 344 | end 345 | end 346 | 347 | class Koan 348 | include Test::Unit::Assertions 349 | 350 | attr_reader :name, :failure, :koan_count, :step_count, :koan_file 351 | 352 | def initialize(name, koan_file=nil, koan_count=0, step_count=0) 353 | @name = name 354 | @failure = nil 355 | @koan_count = koan_count 356 | @step_count = step_count 357 | @koan_file = koan_file 358 | end 359 | 360 | def passed? 361 | @failure.nil? 362 | end 363 | 364 | def failed(failure) 365 | @failure = failure 366 | end 367 | 368 | def setup 369 | end 370 | 371 | def teardown 372 | end 373 | 374 | def meditate 375 | setup 376 | begin 377 | send(name) 378 | rescue StandardError, EdgeCase::Sensei::AssertionError => ex 379 | failed(ex) 380 | ensure 381 | begin 382 | teardown 383 | rescue StandardError, EdgeCase::Sensei::AssertionError => ex 384 | failed(ex) if passed? 385 | end 386 | end 387 | self 388 | end 389 | 390 | # Class methods for the EdgeCase test suite. 391 | class << self 392 | def inherited(subclass) 393 | subclasses << subclass 394 | end 395 | 396 | def method_added(name) 397 | testmethods << name if !tests_disabled? && Koan.test_pattern =~ name.to_s 398 | end 399 | 400 | def end_of_enlightenment 401 | @tests_disabled = true 402 | end 403 | 404 | def command_line(args) 405 | args.each do |arg| 406 | case arg 407 | when /^-n\/(.*)\/$/ 408 | @test_pattern = Regexp.new($1) 409 | when /^-n(.*)$/ 410 | @test_pattern = Regexp.new(Regexp.quote($1)) 411 | else 412 | if File.exist?(arg) 413 | load(arg) 414 | else 415 | fail "Unknown command line argument '#{arg}'" 416 | end 417 | end 418 | end 419 | end 420 | 421 | # Lazy initialize list of subclasses 422 | def subclasses 423 | @subclasses ||= [] 424 | end 425 | 426 | # Lazy initialize list of test methods. 427 | def testmethods 428 | @test_methods ||= [] 429 | end 430 | 431 | def tests_disabled? 432 | @tests_disabled ||= false 433 | end 434 | 435 | def test_pattern 436 | @test_pattern ||= /^test_/ 437 | end 438 | 439 | def total_tests 440 | self.subclasses.inject(0){|total, k| total + k.testmethods.size } 441 | end 442 | end 443 | end 444 | 445 | class ThePath 446 | def walk 447 | sensei = EdgeCase::Sensei.new 448 | each_step do |step| 449 | sensei.observe(step.meditate) 450 | end 451 | sensei.instruct 452 | end 453 | 454 | def each_step 455 | catch(:edgecase_exit) { 456 | step_count = 0 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) 460 | yield step 461 | end 462 | end 463 | } 464 | end 465 | end 466 | end 467 | 468 | END { 469 | EdgeCase::Koan.command_line(ARGV) 470 | EdgeCase::ThePath.new.walk 471 | } 472 | -------------------------------------------------------------------------------- /src/path_to_enlightenment.rb: -------------------------------------------------------------------------------- 1 | # The path to Ruby Metaprogramming Enlightenment starts with the following: 2 | 3 | $LOAD_PATH << File.dirname(__FILE__) 4 | 5 | require 'about_metaclass' 6 | require 'about_self' 7 | require 'about_singleton_methods' 8 | require 'about_class_as_constant' 9 | require 'about_class_methods' 10 | require 'about_prototype_inheritance' 11 | require 'about_class_inheritance' 12 | require 'about_modules' 13 | require 'about_blocks' 14 | require 'about_binding' 15 | require 'about_define_method' 16 | require 'about_instance_eval_and_class_eval' 17 | require 'about_hook_methods' 18 | require 'about_method_added' 19 | -------------------------------------------------------------------------------- /src/test_helper.rb: -------------------------------------------------------------------------------- 1 | require 'test/unit' 2 | 3 | def __ 4 | "FILL ME IN" 5 | end 6 | 7 | EdgeCase = Test::Unit 8 | --------------------------------------------------------------------------------