├── .github └── workflows │ └── ci.yml ├── .gitignore ├── DEPLOYING ├── README.rdoc ├── Rakefile ├── download └── rubykoans.zip ├── keynote └── RubyKoans.key ├── rakelib ├── checks.rake ├── run.rake └── test.rake ├── src ├── GREED_RULES.txt ├── Rakefile ├── about_array_assignment.rb ├── about_arrays.rb ├── about_asserts.rb ├── about_blocks.rb ├── about_class_methods.rb ├── about_classes.rb ├── about_constants.rb ├── about_control_statements.rb ├── about_dice_project.rb ├── about_exceptions.rb ├── about_extra_credit.rb ├── about_hashes.rb ├── about_inheritance.rb ├── about_iteration.rb ├── about_java_interop.rb ├── about_keyword_arguments.rb ├── about_message_passing.rb ├── about_methods.rb ├── about_modules.rb ├── about_nil.rb ├── about_objects.rb ├── about_open_classes.rb ├── about_pattern_matching.rb ├── about_proxy_object_project.rb ├── about_regular_expressions.rb ├── about_sandwich_code.rb ├── about_scope.rb ├── about_scoring_project.rb ├── about_strings.rb ├── about_symbols.rb ├── about_to_str.rb ├── about_triangle_project.rb ├── about_triangle_project_2.rb ├── about_true_and_false.rb ├── example_file.txt ├── koans.watchr ├── neo.rb ├── path_to_enlightenment.rb └── triangle.rb └── tests ├── check_test.rb └── test_helper.rb /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: [push] 3 | jobs: 4 | test: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - name: Checkout code 8 | uses: actions/checkout@v3 9 | 10 | - name: Install Ruby! 11 | uses: ruby/setup-ruby@v1 12 | with: 13 | ruby-version: "3.2.2" 14 | 15 | - name: run tests 16 | run: rake test -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | .project_env.rc 3 | .path_progress 4 | .rvmrc 5 | .ruby-version 6 | *.rbc 7 | koans/* 8 | *~ 9 | -------------------------------------------------------------------------------- /DEPLOYING: -------------------------------------------------------------------------------- 1 | = Deploying a new Ruby Koans ZIP file 2 | 3 | The "Download" button on the rubykoans.com web-site points to the 4 | download/rubykoans.zip file in the github repository. So to update the 5 | download target on the web-site, just rebuild the .zip file, commit 6 | and push the changes. 7 | 8 | rake package 9 | git add download 10 | git push 11 | 12 | That's it. -------------------------------------------------------------------------------- /README.rdoc: -------------------------------------------------------------------------------- 1 | = Ruby Koans 2 | 3 | The Ruby Koans walk you along the path to enlightenment in order to learn Ruby. 4 | The goal is to learn the Ruby language, syntax, structure, and some common 5 | functions and libraries. We also teach you culture by basing the koans on tests. 6 | Testing is not just something we pay lip service to, but something we 7 | live. Testing is essential in your quest to learn and do great things in Ruby. 8 | 9 | == The Structure 10 | 11 | The koans are broken out into areas by file, hashes are covered in +about_hashes.rb+, 12 | modules are introduced in +about_modules.rb+, etc. They are presented in 13 | order in the +path_to_enlightenment.rb+ file. 14 | 15 | Each koan builds up your knowledge of Ruby and builds upon itself. It will stop at 16 | the first place you need to correct. 17 | 18 | Some koans simply need to have the correct answer substituted for an incorrect one. 19 | Some, however, require you to supply your own answer. If you see the method +__+ (a 20 | double underscore) listed, it is a hint to you to supply your own code in order to 21 | make it work correctly. 22 | 23 | == Installing Ruby 24 | 25 | If you do not have Ruby setup, please visit http://ruby-lang.org/en/downloads/ for 26 | operating specific instructions. In order to run the koans you need +ruby+ and 27 | +rake+ installed. To check your installations simply type: 28 | 29 | *nix platforms from any terminal window: 30 | 31 | [~] $ ruby --version 32 | [~] $ rake --version 33 | 34 | Windows from the command prompt (+cmd.exe+) 35 | 36 | c:\ruby --version 37 | c:\rake --version 38 | 39 | If you don't have +rake+ installed, just run gem install rake 40 | 41 | Any response for Ruby with a version number greater than 1.8 is fine (should be 42 | around 1.8.6 or more). Any version of +rake+ will do. 43 | 44 | == Generating the Koans 45 | 46 | A fresh checkout will not include the koans, you will need to generate 47 | them. 48 | 49 | [ruby_koans] $ rake gen # generates the koans directory 50 | 51 | If you need to regenerate the koans, thus wiping your current `koans`, 52 | 53 | [ruby_koans] $ rake regen # regenerates the koans directory, wiping the original 54 | 55 | == The Path To Enlightenment 56 | 57 | You can run the tests through +rake+ or by calling the file itself (+rake+ is the 58 | recommended way to run them as we might build more functionality into this task). 59 | 60 | *nix platforms, from the +ruby_koans+ directory 61 | 62 | [ruby_koans] $ rake # runs the default target :walk_the_path 63 | [ruby_koans] $ ruby path_to_enlightenment.rb # simply call the file directly 64 | 65 | Windows is the same thing 66 | 67 | c:\ruby_koans\rake # runs the default target :walk_the_path 68 | c:\ruby_koans\ruby path_to_enlightenment.rb # simply call the file directly 69 | 70 | === Red, Green, Refactor 71 | 72 | In test-driven development the mantra has always been red, green, refactor. 73 | Write a failing test and run it (red), make the test pass (green), 74 | then look at the code and consider if you can make it any better (refactor). 75 | 76 | While walking the path to Ruby enlightenment you will need to run the koan and 77 | see it fail (red), make the test pass (green), then take a moment 78 | and reflect upon the test to see what it is teaching you and improve the code to 79 | better communicate its intent (refactor). 80 | 81 | The very first time you run the koans you will see the following output: 82 | 83 | [ ruby_koans ] $ rake 84 | (in /Users/person/dev/ruby_koans) 85 | /usr/bin/ruby1.8 path_to_enlightenment.rb 86 | 87 | AboutAsserts#test_assert_truth has damaged your karma. 88 | 89 | The Master says: 90 | You have not yet reached enlightenment. 91 | 92 | The answers you seek... 93 | is not true. 94 | 95 | Please meditate on the following code: 96 | ./about_asserts.rb:10:in `test_assert_truth' 97 | path_to_enlightenment.rb:38:in `each_with_index' 98 | path_to_enlightenment.rb:38 99 | 100 | mountains are merely mountains 101 | your path thus far [X_________________________________________________] 0/280 (0%) 102 | 103 | You have come to your first stage. Notice it is telling you where to look for 104 | the first solution: 105 | 106 | Please meditate on the following code: 107 | ./about_asserts.rb:10:in `test_assert_truth' 108 | path_to_enlightenment.rb:38:in `each_with_index' 109 | path_to_enlightenment.rb:38 110 | 111 | Open the +about_asserts.rb+ file and look at the first test: 112 | 113 | # We shall contemplate truth by testing reality, via asserts. 114 | def test_assert_truth 115 | assert false # This should be true 116 | end 117 | 118 | Change the +false+ to +true+ and re-run the test. After you are 119 | done, think about what you are learning. In this case, ignore everything except 120 | the method name (+test_assert_truth+) and the parts inside the method (everything 121 | before the +end+). 122 | 123 | In this case the goal is for you to see that if you pass a value to the +assert+ 124 | method, it will either ensure it is +true+ and continue on, or fail if 125 | the statement is +false+. 126 | 127 | === Running the Koans automatically 128 | 129 | This section is optional. 130 | 131 | Normally the path to enlightenment looks like this: 132 | 133 | cd ruby_koans 134 | rake 135 | # edit 136 | rake 137 | # edit 138 | rake 139 | # etc 140 | 141 | If you prefer, you can keep the koans running in the background so that after you 142 | make a change in your editor, the koans will immediately run again. This will 143 | hopefully keep your focus on learning Ruby instead of on the command line. 144 | 145 | Install the Ruby gem (library) called +observr+ and then ask it to 146 | "watch" the koans for changes: 147 | 148 | cd ruby_koans 149 | rake 150 | # decide to run rake automatically from now on as you edit 151 | gem install observr 152 | observr ./koans.watchr 153 | 154 | == Inspiration 155 | 156 | A special thanks to Mike Clark and Ara Howard for inspiring this 157 | project. Mike Clark wrote an excellent blog post about learning Ruby 158 | through unit testing. This sparked an idea that has taken a bit to 159 | solidify, that of bringing new rubyists into the community through 160 | testing. Ara Howard then gave us the idea for the Koans in his ruby 161 | quiz entry on Meta Koans (a must for any rubyist wanting to improve 162 | their skills). Also, "The Little Lisper" taught us all the value of 163 | the short questions/simple answers style of learning. 164 | 165 | Mike Clark's post :: http://www.clarkware.com/cgi/blosxom/2005/03/18 166 | Meta Koans :: http://rubyquiz.com/quiz67.html 167 | The Little Lisper :: http://www.amazon.com/Little-LISPer-Third-Daniel-Friedman/dp/0023397632 168 | 169 | == Other Resources 170 | 171 | The Ruby Language :: http://ruby-lang.org 172 | Try Ruby in your browser :: http://tryruby.org 173 | 174 | Dave Thomas' introduction to Ruby Programming Ruby (the Pick Axe) :: http://pragprog.com/titles/ruby/programming-ruby 175 | 176 | Brian Marick's fantastic guide for beginners Everyday Scripting with Ruby :: http://pragprog.com/titles/bmsft/everyday-scripting-with-ruby 177 | 178 | = Other stuff 179 | 180 | Author :: Jim Weirich 181 | Author :: Joe O'Brien 182 | Issue Tracker :: https://github.com/edgecase/ruby_koans/issues 183 | Requires :: Ruby 1.8.x or later and Rake (any recent version) 184 | 185 | = License 186 | 187 | http://i.creativecommons.org/l/by-nc-sa/3.0/88x31.png 188 | 189 | RubyKoans is released under a Creative Commons, 190 | Attribution-NonCommercial-ShareAlike, Version 3.0 191 | (http://creativecommons.org/licenses/by-nc-sa/3.0/) License. 192 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # -*- ruby -*- 3 | 4 | require 'rake/clean' 5 | 6 | SRC_DIR = 'src' 7 | PROB_DIR = 'koans' 8 | DOWNLOAD_DIR = 'download' 9 | 10 | SRC_FILES = FileList["#{SRC_DIR}/*"] 11 | KOAN_FILES = SRC_FILES.pathmap("#{PROB_DIR}/%f") 12 | 13 | ZIP_FILE = "#{DOWNLOAD_DIR}/rubykoans.zip" 14 | 15 | CLEAN.include("**/*.rbc") 16 | 17 | module Koans 18 | extend Rake::DSL if defined?(Rake::DSL) 19 | 20 | # Remove solution info from source 21 | # __(a,b) => __ 22 | # _n_(number) => __ 23 | # # __ => 24 | def Koans.remove_solution(line) 25 | line = line.gsub(/\b____\([^\)]+\)/, "____") 26 | line = line.gsub(/\b___\([^\)]+\)/, "___") 27 | line = line.gsub(/\b__\([^\)]+\)/, "__") 28 | line = line.gsub(/\b_n_\([^\)]+\)/, "_n_") 29 | line = line.gsub(%r(/\#\{__\}/), "/__/") 30 | line = line.gsub(/\s*#\s*__\s*$/, '') 31 | line 32 | end 33 | 34 | def Koans.make_koan_file(infile, outfile) 35 | if infile =~ /neo/ 36 | cp infile, outfile 37 | else 38 | open(infile) do |ins| 39 | open(outfile, "w") do |outs| 40 | state = :copy 41 | ins.each do |line| 42 | state = :skip if line =~ /^ *#--/ 43 | case state 44 | when :copy 45 | outs.puts remove_solution(line) 46 | else 47 | # do nothing 48 | end 49 | state = :copy if line =~ /^ *#\+\+/ 50 | end 51 | end 52 | end 53 | end 54 | end 55 | end 56 | 57 | module RubyImpls 58 | # Calculate the list of relevant Ruby implementations. 59 | def self.find_ruby_impls 60 | rubys = `rvm list`.gsub(/=>/,'').split(/\n/).map { |x| x.strip }.reject { |x| x.empty? || x =~ /^rvm/ }.sort 61 | expected.map { |impl| 62 | last = rubys.grep(Regexp.new(Regexp.quote(impl))).last 63 | last ? last.split.first : nil 64 | }.compact 65 | end 66 | 67 | # Return a (cached) list of relevant Ruby implementations. 68 | def self.list 69 | @list ||= find_ruby_impls 70 | end 71 | 72 | # List of expected ruby implementations. 73 | def self.expected 74 | %w(ruby-1.8.7 ruby-1.9.2 jruby ree) 75 | end 76 | end 77 | 78 | task :default => :walk_the_path 79 | 80 | task :walk_the_path do 81 | cd PROB_DIR 82 | ruby 'path_to_enlightenment.rb' 83 | end 84 | 85 | directory DOWNLOAD_DIR 86 | directory PROB_DIR 87 | 88 | desc "(re)Build zip file" 89 | task :zip => [:clobber_zip, :package] 90 | 91 | task :clobber_zip do 92 | rm ZIP_FILE 93 | end 94 | 95 | file ZIP_FILE => KOAN_FILES + [DOWNLOAD_DIR] do 96 | sh "zip #{ZIP_FILE} #{PROB_DIR}/*" 97 | end 98 | 99 | desc "Create packaged files for distribution" 100 | task :package => [ZIP_FILE] 101 | 102 | desc "Upload the package files to the web server" 103 | task :upload => [ZIP_FILE] do 104 | sh "scp #{ZIP_FILE} linode:sites/onestepback.org/download" 105 | end 106 | 107 | desc "Generate the Koans from the source files from scratch." 108 | task :regen => [:clobber_koans, :gen] 109 | 110 | desc "Generate the Koans from the changed source files." 111 | task :gen => KOAN_FILES + [PROB_DIR + "/README.rdoc"] 112 | task :clobber_koans do 113 | rm_r PROB_DIR 114 | end 115 | 116 | file PROB_DIR + "/README.rdoc" => "README.rdoc" do |t| 117 | cp "README.rdoc", t.name 118 | end 119 | 120 | SRC_FILES.each do |koan_src| 121 | file koan_src.pathmap("#{PROB_DIR}/%f") => [PROB_DIR, koan_src] do |t| 122 | Koans.make_koan_file koan_src, t.name 123 | end 124 | end 125 | 126 | task :run do 127 | puts 'koans' 128 | Dir.chdir("#{SRC_DIR}") do 129 | puts "in #{Dir.pwd}" 130 | sh "ruby path_to_enlightenment.rb" 131 | end 132 | end 133 | 134 | 135 | desc "Pre-checkin tests (=> run_all)" 136 | task :cruise => :run_all 137 | 138 | desc "Run the completed koans againts a list of relevant Ruby Implementations" 139 | task :run_all do 140 | results = [] 141 | RubyImpls.list.each do |impl| 142 | puts "=" * 40 143 | puts "On Ruby #{impl}" 144 | sh ". rvm #{impl}; rake run" 145 | results << [impl, "RAN"] 146 | puts 147 | end 148 | puts "=" * 40 149 | puts "Summary:" 150 | puts 151 | results.each do |impl, res| 152 | puts "#{impl} => RAN" 153 | end 154 | puts 155 | RubyImpls.expected.each do |requested_impl| 156 | impl_pattern = Regexp.new(Regexp.quote(requested_impl)) 157 | puts "No Results for #{requested_impl}" unless results.detect { |x| x.first =~ impl_pattern } 158 | end 159 | end 160 | -------------------------------------------------------------------------------- /download/rubykoans.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edgecase/ruby_koans/8e70162472b46b6e1af4bf49368c0c49e5c6dcbc/download/rubykoans.zip -------------------------------------------------------------------------------- /keynote/RubyKoans.key: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edgecase/ruby_koans/8e70162472b46b6e1af4bf49368c0c49e5c6dcbc/keynote/RubyKoans.key -------------------------------------------------------------------------------- /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_*.rb | 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 | -------------------------------------------------------------------------------- /rakelib/test.rake: -------------------------------------------------------------------------------- 1 | require 'rake/testtask' 2 | 3 | Rake::TestTask.new do |t| 4 | t.libs << "tests" 5 | t.test_files = FileList["tests/**/*_test.rb"] 6 | t.verbose = true 7 | end 8 | desc 'Run tests' 9 | 10 | -------------------------------------------------------------------------------- /src/GREED_RULES.txt: -------------------------------------------------------------------------------- 1 | = Playing Greed 2 | 3 | Greed is a dice game played among 2 or more players, using 5 4 | six-sided dice. 5 | 6 | == Playing Greed 7 | 8 | Each player takes a turn consisting of one or more rolls of the dice. 9 | On the first roll of the game, a player rolls all five dice which are 10 | scored according to the following: 11 | 12 | Three 1's => 1000 points 13 | Three 6's => 600 points 14 | Three 5's => 500 points 15 | Three 4's => 400 points 16 | Three 3's => 300 points 17 | Three 2's => 200 points 18 | One 1 => 100 points 19 | One 5 => 50 points 20 | 21 | A single die can only be counted once in each roll. For example, 22 | a "5" can only count as part of a triplet (contributing to the 500 23 | points) or as a single 50 points, but not both in the same roll. 24 | 25 | Example Scoring 26 | 27 | Throw Score 28 | --------- ------------------ 29 | 5 1 3 4 1 50 + 2 * 100 = 250 30 | 1 1 1 3 1 1000 + 100 = 1100 31 | 2 4 4 5 4 400 + 50 = 450 32 | 33 | The dice not contributing to the score are called the non-scoring 34 | dice. "3" and "4" are non-scoring dice in the first example. "3" is 35 | a non-scoring die in the second, and "2" is a non-score die in the 36 | final example. 37 | 38 | After a player rolls and the score is calculated, the scoring dice are 39 | removed and the player has the option of rolling again using only the 40 | non-scoring dice. If all of the thrown dice are scoring, then the 41 | player may roll all 5 dice in the next roll. 42 | 43 | The player may continue to roll as long as each roll scores points. If 44 | a roll has zero points, then the player loses not only their turn, but 45 | also accumulated score for that turn. If a player decides to stop 46 | rolling before rolling a zero-point roll, then the accumulated points 47 | for the turn is added to his total score. 48 | 49 | == Getting "In The Game" 50 | 51 | Before a player is allowed to accumulate points, they must get at 52 | least 300 points in a single turn. Once they have achieved 300 points 53 | in a single turn, the points earned in that turn and each following 54 | turn will be counted toward their total score. 55 | 56 | == End Game 57 | 58 | Once a player reaches 3000 (or more) points, the game enters the final 59 | round where each of the other players gets one more turn. The winner 60 | is the player with the highest score after the final round. 61 | 62 | == References 63 | 64 | Greed is described on Wikipedia at 65 | http://en.wikipedia.org/wiki/Greed_(dice_game), however the rules are 66 | a bit different from the rules given here. 67 | -------------------------------------------------------------------------------- /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_array_assignment.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/neo') 2 | 3 | class AboutArrayAssignment < Neo::Koan 4 | def test_non_parallel_assignment 5 | names = ["John", "Smith"] 6 | assert_equal __(["John", "Smith"]), names 7 | end 8 | 9 | def test_parallel_assignments 10 | first_name, last_name = ["John", "Smith"] 11 | assert_equal __("John"), first_name 12 | assert_equal __("Smith"), last_name 13 | end 14 | 15 | def test_parallel_assignments_with_extra_values 16 | first_name, last_name = ["John", "Smith", "III"] 17 | assert_equal __("John"), first_name 18 | assert_equal __("Smith"), last_name 19 | end 20 | 21 | def test_parallel_assignments_with_splat_operator 22 | first_name, *last_name = ["John", "Smith", "III"] 23 | assert_equal __("John"), first_name 24 | assert_equal __(["Smith","III"]), last_name 25 | end 26 | 27 | def test_parallel_assignments_with_too_few_values 28 | first_name, last_name = ["Cher"] 29 | assert_equal __("Cher"), first_name 30 | assert_equal __(nil), last_name 31 | end 32 | 33 | def test_parallel_assignments_with_subarrays 34 | first_name, last_name = [["Willie", "Rae"], "Johnson"] 35 | assert_equal __(["Willie", "Rae"]), first_name 36 | assert_equal __("Johnson"), last_name 37 | end 38 | 39 | def test_parallel_assignment_with_one_variable 40 | first_name, = ["John", "Smith"] 41 | assert_equal __("John"), first_name 42 | end 43 | 44 | def test_swapping_with_parallel_assignment 45 | first_name = "Roy" 46 | last_name = "Rob" 47 | first_name, last_name = last_name, first_name 48 | assert_equal __('Rob'), first_name 49 | assert_equal __('Roy'), last_name 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /src/about_arrays.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/neo') 2 | 3 | class AboutArrays < Neo::Koan 4 | def test_creating_arrays 5 | empty_array = Array.new 6 | assert_equal __(Array), empty_array.class 7 | assert_equal __(0), empty_array.size 8 | end 9 | 10 | def test_array_literals 11 | array = Array.new 12 | assert_equal [], array # __ 13 | 14 | array[0] = 1 15 | assert_equal [1], array # __ 16 | 17 | array[1] = 2 18 | assert_equal [1, __(2)], array 19 | 20 | array << 333 21 | assert_equal __([1, 2, 333]), array 22 | end 23 | 24 | def test_accessing_array_elements 25 | array = [:peanut, :butter, :and, :jelly] 26 | 27 | assert_equal __(:peanut), array[0] 28 | assert_equal __(:peanut), array.first 29 | assert_equal __(:jelly), array[3] 30 | assert_equal __(:jelly), array.last 31 | assert_equal __(:jelly), array[-1] 32 | assert_equal __(:butter), array[-3] 33 | end 34 | 35 | def test_slicing_arrays 36 | array = [:peanut, :butter, :and, :jelly] 37 | 38 | assert_equal __([:peanut]), array[0,1] 39 | assert_equal __([:peanut, :butter]), array[0,2] 40 | assert_equal __([:and, :jelly]), array[2,2] 41 | assert_equal __([:and, :jelly]), array[2,20] 42 | assert_equal __([]), array[4,0] 43 | assert_equal __([]), array[4,100] 44 | assert_equal __(nil), array[5,0] 45 | end 46 | 47 | def test_arrays_and_ranges 48 | assert_equal __(Range), (1..5).class 49 | assert_not_equal [1,2,3,4,5], (1..5) # __ 50 | assert_equal __([1,2,3,4,5]), (1..5).to_a 51 | assert_equal __([1,2,3,4]), (1...5).to_a 52 | end 53 | 54 | def test_slicing_with_ranges 55 | array = [:peanut, :butter, :and, :jelly] 56 | 57 | assert_equal __([:peanut, :butter, :and]), array[0..2] 58 | assert_equal __([:peanut, :butter]), array[0...2] 59 | assert_equal __([:and, :jelly]), array[2..-1] 60 | end 61 | 62 | def test_pushing_and_popping_arrays 63 | array = [1,2] 64 | array.push(:last) 65 | 66 | assert_equal __([1, 2, :last]), array 67 | 68 | popped_value = array.pop 69 | assert_equal __(:last), popped_value 70 | assert_equal __([1, 2]), array 71 | end 72 | 73 | def test_shifting_arrays 74 | array = [1,2] 75 | array.unshift(:first) 76 | 77 | assert_equal __([:first, 1, 2]), array 78 | 79 | shifted_value = array.shift 80 | assert_equal __(:first), shifted_value 81 | assert_equal __([1, 2]), array 82 | end 83 | 84 | end 85 | -------------------------------------------------------------------------------- /src/about_asserts.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # -*- ruby -*- 3 | 4 | require File.expand_path(File.dirname(__FILE__) + '/neo') 5 | 6 | class AboutAsserts < Neo::Koan 7 | 8 | # We shall contemplate truth by testing reality, via asserts. 9 | def test_assert_truth 10 | #-- 11 | assert true # This should be true 12 | if false 13 | #++ 14 | assert false # This should be true 15 | #-- 16 | end 17 | #++ 18 | end 19 | 20 | # Enlightenment may be more easily achieved with appropriate 21 | # messages. 22 | def test_assert_with_message 23 | #-- 24 | assert true, "This should be true -- Please fix this" 25 | if false 26 | #++ 27 | assert false, "This should be true -- Please fix this" 28 | #-- 29 | end 30 | #++ 31 | end 32 | 33 | # To understand reality, we must compare our expectations against 34 | # reality. 35 | def test_assert_equality 36 | expected_value = __(2) 37 | actual_value = 1 + 1 38 | 39 | assert expected_value == actual_value 40 | end 41 | 42 | # Some ways of asserting equality are better than others. 43 | def test_a_better_way_of_asserting_equality 44 | expected_value = __(2) 45 | actual_value = 1 + 1 46 | 47 | assert_equal expected_value, actual_value 48 | end 49 | 50 | # Sometimes we will ask you to fill in the values 51 | def test_fill_in_values 52 | assert_equal __(2), 1 + 1 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /src/about_blocks.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/neo') 2 | 3 | class AboutBlocks < Neo::Koan 4 | def method_with_block 5 | result = yield 6 | result 7 | end 8 | 9 | def test_methods_can_take_blocks 10 | yielded_result = method_with_block { 1 + 2 } 11 | assert_equal __(3), yielded_result 12 | end 13 | 14 | def test_blocks_can_be_defined_with_do_end_too 15 | yielded_result = method_with_block do 1 + 2 end 16 | assert_equal __(3), yielded_result 17 | end 18 | 19 | # ------------------------------------------------------------------ 20 | 21 | def method_with_block_arguments 22 | yield("Jim") 23 | end 24 | 25 | def test_blocks_can_take_arguments 26 | method_with_block_arguments do |argument| 27 | assert_equal __("Jim"), argument 28 | end 29 | end 30 | 31 | # ------------------------------------------------------------------ 32 | 33 | def many_yields 34 | yield(:peanut) 35 | yield(:butter) 36 | yield(:and) 37 | yield(:jelly) 38 | end 39 | 40 | def test_methods_can_call_yield_many_times 41 | result = [] 42 | many_yields { |item| result << item } 43 | assert_equal __([:peanut, :butter, :and, :jelly]), result 44 | end 45 | 46 | # ------------------------------------------------------------------ 47 | 48 | def yield_tester 49 | if block_given? 50 | yield 51 | else 52 | :no_block 53 | end 54 | end 55 | 56 | def test_methods_can_see_if_they_have_been_called_with_a_block 57 | assert_equal __(:with_block), yield_tester { :with_block } 58 | assert_equal __(:no_block), yield_tester 59 | end 60 | 61 | # ------------------------------------------------------------------ 62 | 63 | def test_block_can_affect_variables_in_the_code_where_they_are_created 64 | value = :initial_value 65 | method_with_block { value = :modified_in_a_block } 66 | assert_equal __(:modified_in_a_block), value 67 | end 68 | 69 | def test_blocks_can_be_assigned_to_variables_and_called_explicitly 70 | add_one = lambda { |n| n + 1 } 71 | assert_equal __(11), add_one.call(10) 72 | 73 | # Alternative calling syntax 74 | assert_equal __(11), add_one[10] 75 | end 76 | 77 | def test_stand_alone_blocks_can_be_passed_to_methods_expecting_blocks 78 | make_upper = lambda { |n| n.upcase } 79 | result = method_with_block_arguments(&make_upper) 80 | assert_equal __("JIM"), result 81 | end 82 | 83 | # ------------------------------------------------------------------ 84 | 85 | def method_with_explicit_block(&block) 86 | block.call(10) 87 | end 88 | 89 | def test_methods_can_take_an_explicit_block_argument 90 | assert_equal __(20), method_with_explicit_block { |n| n * 2 } 91 | 92 | add_one = lambda { |n| n + 1 } 93 | assert_equal __(11), method_with_explicit_block(&add_one) 94 | end 95 | 96 | end 97 | -------------------------------------------------------------------------------- /src/about_class_methods.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/neo') 2 | 3 | class AboutClassMethods < Neo::Koan 4 | class Dog 5 | end 6 | 7 | def test_objects_are_objects 8 | fido = Dog.new 9 | assert_equal __(true), fido.is_a?(Object) 10 | end 11 | 12 | def test_classes_are_classes 13 | assert_equal __(true), Dog.is_a?(Class) 14 | end 15 | 16 | def test_classes_are_objects_too 17 | assert_equal __(true), Dog.is_a?(Object) 18 | end 19 | 20 | def test_objects_have_methods 21 | fido = Dog.new 22 | assert fido.methods.size > _n_(30) 23 | end 24 | 25 | def test_classes_have_methods 26 | assert Dog.methods.size > _n_(40) 27 | end 28 | 29 | def test_you_can_define_methods_on_individual_objects 30 | fido = Dog.new 31 | def fido.wag 32 | :fidos_wag 33 | end 34 | assert_equal __(:fidos_wag), fido.wag 35 | end 36 | 37 | def test_other_objects_are_not_affected_by_these_singleton_methods 38 | fido = Dog.new 39 | rover = Dog.new 40 | def fido.wag 41 | :fidos_wag 42 | end 43 | 44 | assert_raise(___(NoMethodError)) do 45 | rover.wag 46 | end 47 | end 48 | 49 | # ------------------------------------------------------------------ 50 | 51 | class Dog2 52 | def wag 53 | :instance_level_wag 54 | end 55 | end 56 | 57 | def Dog2.wag 58 | :class_level_wag 59 | end 60 | 61 | def test_since_classes_are_objects_you_can_define_singleton_methods_on_them_too 62 | assert_equal __(:class_level_wag), Dog2.wag 63 | end 64 | 65 | def test_class_methods_are_independent_of_instance_methods 66 | fido = Dog2.new 67 | assert_equal __(:instance_level_wag), fido.wag 68 | assert_equal __(:class_level_wag), Dog2.wag 69 | end 70 | 71 | # ------------------------------------------------------------------ 72 | 73 | class Dog 74 | attr_accessor :name 75 | end 76 | 77 | def Dog.name 78 | @name 79 | end 80 | 81 | def test_classes_and_instances_do_not_share_instance_variables 82 | fido = Dog.new 83 | fido.name = "Fido" 84 | assert_equal __("Fido"), fido.name 85 | assert_equal __(nil), Dog.name 86 | end 87 | 88 | # ------------------------------------------------------------------ 89 | 90 | class Dog 91 | def Dog.a_class_method 92 | :dogs_class_method 93 | end 94 | end 95 | 96 | def test_you_can_define_class_methods_inside_the_class 97 | assert_equal __(:dogs_class_method), Dog.a_class_method 98 | end 99 | 100 | # ------------------------------------------------------------------ 101 | 102 | LastExpressionInClassStatement = class Dog 103 | 21 104 | end 105 | 106 | def test_class_statements_return_the_value_of_their_last_expression 107 | assert_equal __(21), LastExpressionInClassStatement 108 | end 109 | 110 | # ------------------------------------------------------------------ 111 | 112 | SelfInsideOfClassStatement = class Dog 113 | self 114 | end 115 | 116 | def test_self_while_inside_class_is_class_object_not_instance 117 | assert_equal __(true), Dog == SelfInsideOfClassStatement 118 | end 119 | 120 | # ------------------------------------------------------------------ 121 | 122 | class Dog 123 | def self.class_method2 124 | :another_way_to_write_class_methods 125 | end 126 | end 127 | 128 | def test_you_can_use_self_instead_of_an_explicit_reference_to_dog 129 | assert_equal __(:another_way_to_write_class_methods), Dog.class_method2 130 | end 131 | 132 | # ------------------------------------------------------------------ 133 | 134 | class Dog 135 | class << self 136 | def another_class_method 137 | :still_another_way 138 | end 139 | end 140 | end 141 | 142 | def test_heres_still_another_way_to_write_class_methods 143 | assert_equal __(:still_another_way), Dog.another_class_method 144 | end 145 | 146 | # THINK ABOUT IT: 147 | # 148 | # The two major ways to write class methods are: 149 | # class Demo 150 | # def self.method 151 | # end 152 | # 153 | # class << self 154 | # def class_methods 155 | # end 156 | # end 157 | # end 158 | # 159 | # Which do you prefer and why? 160 | # Are there times you might prefer one over the other? 161 | 162 | # ------------------------------------------------------------------ 163 | 164 | def test_heres_an_easy_way_to_call_class_methods_from_instance_methods 165 | fido = Dog.new 166 | assert_equal __(:still_another_way), fido.class.another_class_method 167 | end 168 | 169 | end 170 | -------------------------------------------------------------------------------- /src/about_classes.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/neo') 2 | 3 | class AboutClasses < Neo::Koan 4 | class Dog 5 | end 6 | 7 | def test_instances_of_classes_can_be_created_with_new 8 | fido = Dog.new 9 | assert_equal __(Dog), fido.class 10 | end 11 | 12 | # ------------------------------------------------------------------ 13 | 14 | class Dog2 15 | def set_name(a_name) 16 | @name = a_name 17 | end 18 | end 19 | 20 | def test_instance_variables_can_be_set_by_assigning_to_them 21 | fido = Dog2.new 22 | assert_equal __([]), fido.instance_variables 23 | 24 | fido.set_name("Fido") 25 | assert_equal __(["@name"], [:@name]), fido.instance_variables 26 | end 27 | 28 | def test_instance_variables_cannot_be_accessed_outside_the_class 29 | fido = Dog2.new 30 | fido.set_name("Fido") 31 | 32 | assert_raise(___(NoMethodError)) do 33 | fido.name 34 | end 35 | 36 | assert_raise(___(SyntaxError)) do 37 | eval "fido.@name" 38 | # NOTE: Using eval because the above line is a syntax error. 39 | end 40 | end 41 | 42 | def test_you_can_politely_ask_for_instance_variable_values 43 | fido = Dog2.new 44 | fido.set_name("Fido") 45 | 46 | assert_equal __("Fido"), fido.instance_variable_get("@name") 47 | end 48 | 49 | def test_you_can_rip_the_value_out_using_instance_eval 50 | fido = Dog2.new 51 | fido.set_name("Fido") 52 | 53 | assert_equal __("Fido"), fido.instance_eval("@name") # string version 54 | assert_equal __("Fido"), fido.instance_eval { @name } # block version 55 | end 56 | 57 | # ------------------------------------------------------------------ 58 | 59 | class Dog3 60 | def set_name(a_name) 61 | @name = a_name 62 | end 63 | def name 64 | @name 65 | end 66 | end 67 | 68 | def test_you_can_create_accessor_methods_to_return_instance_variables 69 | fido = Dog3.new 70 | fido.set_name("Fido") 71 | 72 | assert_equal __("Fido"), fido.name 73 | end 74 | 75 | # ------------------------------------------------------------------ 76 | 77 | class Dog4 78 | attr_reader :name 79 | 80 | def set_name(a_name) 81 | @name = a_name 82 | end 83 | end 84 | 85 | 86 | def test_attr_reader_will_automatically_define_an_accessor 87 | fido = Dog4.new 88 | fido.set_name("Fido") 89 | 90 | assert_equal __("Fido"), fido.name 91 | end 92 | 93 | # ------------------------------------------------------------------ 94 | 95 | class Dog5 96 | attr_accessor :name 97 | end 98 | 99 | 100 | def test_attr_accessor_will_automatically_define_both_read_and_write_accessors 101 | fido = Dog5.new 102 | 103 | fido.name = "Fido" 104 | assert_equal __("Fido"), fido.name 105 | end 106 | 107 | # ------------------------------------------------------------------ 108 | 109 | class Dog6 110 | attr_reader :name 111 | def initialize(initial_name) 112 | @name = initial_name 113 | end 114 | end 115 | 116 | def test_initialize_provides_initial_values_for_instance_variables 117 | fido = Dog6.new("Fido") 118 | assert_equal __("Fido"), fido.name 119 | end 120 | 121 | def test_args_to_new_must_match_initialize 122 | assert_raise(___(ArgumentError)) do 123 | Dog6.new 124 | end 125 | # THINK ABOUT IT: 126 | # Why is this so? 127 | end 128 | 129 | def test_different_objects_have_different_instance_variables 130 | fido = Dog6.new("Fido") 131 | rover = Dog6.new("Rover") 132 | 133 | assert_equal __(true), rover.name != fido.name 134 | end 135 | 136 | # ------------------------------------------------------------------ 137 | 138 | class Dog7 139 | attr_reader :name 140 | 141 | def initialize(initial_name) 142 | @name = initial_name 143 | end 144 | 145 | def get_self 146 | self 147 | end 148 | 149 | def to_s 150 | @name 151 | end 152 | 153 | def inspect 154 | "" 155 | end 156 | end 157 | 158 | def test_inside_a_method_self_refers_to_the_containing_object 159 | fido = Dog7.new("Fido") 160 | 161 | fidos_self = fido.get_self 162 | assert_equal __(fido), fidos_self 163 | end 164 | 165 | def test_to_s_provides_a_string_version_of_the_object 166 | fido = Dog7.new("Fido") 167 | assert_equal __("Fido"), fido.to_s 168 | end 169 | 170 | def test_to_s_is_used_in_string_interpolation 171 | fido = Dog7.new("Fido") 172 | assert_equal __("My dog is Fido"), "My dog is #{fido}" 173 | end 174 | 175 | def test_inspect_provides_a_more_complete_string_version 176 | fido = Dog7.new("Fido") 177 | assert_equal __(""), fido.inspect 178 | end 179 | 180 | def test_all_objects_support_to_s_and_inspect 181 | array = [1,2,3] 182 | 183 | assert_equal __("123", "[1, 2, 3]"), array.to_s 184 | assert_equal __("[1, 2, 3]"), array.inspect 185 | 186 | assert_equal __("STRING"), "STRING".to_s 187 | assert_equal __('"STRING"'), "STRING".inspect 188 | end 189 | 190 | end 191 | -------------------------------------------------------------------------------- /src/about_constants.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/neo') 2 | 3 | C = "top level" 4 | 5 | class AboutConstants < Neo::Koan 6 | 7 | C = "nested" 8 | 9 | def test_nested_constants_may_also_be_referenced_with_relative_paths 10 | assert_equal __("nested"), C 11 | end 12 | 13 | def test_top_level_constants_are_referenced_by_double_colons 14 | assert_equal __("top level"), ::C 15 | end 16 | 17 | def test_nested_constants_are_referenced_by_their_complete_path 18 | assert_equal __("nested"), AboutConstants::C 19 | assert_equal __("nested"), ::AboutConstants::C 20 | end 21 | 22 | # ------------------------------------------------------------------ 23 | 24 | class Animal 25 | LEGS = 4 26 | def legs_in_animal 27 | LEGS 28 | end 29 | 30 | class NestedAnimal 31 | def legs_in_nested_animal 32 | LEGS 33 | end 34 | end 35 | end 36 | 37 | def test_nested_classes_inherit_constants_from_enclosing_classes 38 | assert_equal __(4), Animal::NestedAnimal.new.legs_in_nested_animal 39 | end 40 | 41 | # ------------------------------------------------------------------ 42 | 43 | class Reptile < Animal 44 | def legs_in_reptile 45 | LEGS 46 | end 47 | end 48 | 49 | def test_subclasses_inherit_constants_from_parent_classes 50 | assert_equal __(4), Reptile.new.legs_in_reptile 51 | end 52 | 53 | # ------------------------------------------------------------------ 54 | 55 | class MyAnimals 56 | LEGS = 2 57 | 58 | class Bird < Animal 59 | def legs_in_bird 60 | LEGS 61 | end 62 | end 63 | end 64 | 65 | def test_who_wins_with_both_nested_and_inherited_constants 66 | assert_equal __(2), MyAnimals::Bird.new.legs_in_bird 67 | end 68 | 69 | # QUESTION: Which has precedence: The constant in the lexical scope, 70 | # or the constant from the inheritance hierarchy? 71 | 72 | # ------------------------------------------------------------------ 73 | 74 | class MyAnimals::Oyster < Animal 75 | def legs_in_oyster 76 | LEGS 77 | end 78 | end 79 | 80 | def test_who_wins_with_explicit_scoping_on_class_definition 81 | assert_equal __(4), MyAnimals::Oyster.new.legs_in_oyster 82 | end 83 | 84 | # QUESTION: Now which has precedence: The constant in the lexical 85 | # scope, or the constant from the inheritance hierarchy? Why is it 86 | # different than the previous answer? 87 | end 88 | -------------------------------------------------------------------------------- /src/about_control_statements.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/neo') 2 | 3 | class AboutControlStatements < Neo::Koan 4 | 5 | def test_if_then_else_statements 6 | if true 7 | result = :true_value 8 | else 9 | result = :false_value 10 | end 11 | assert_equal __(:true_value), result 12 | end 13 | 14 | def test_if_then_statements 15 | result = :default_value 16 | if true 17 | result = :true_value 18 | end 19 | assert_equal __(:true_value), result 20 | end 21 | 22 | def test_if_statements_return_values 23 | value = if true 24 | :true_value 25 | else 26 | :false_value 27 | end 28 | assert_equal __(:true_value), value 29 | 30 | value = if false 31 | :true_value 32 | else 33 | :false_value 34 | end 35 | assert_equal __(:false_value), value 36 | 37 | # NOTE: Actually, EVERY statement in Ruby will return a value, not 38 | # just if statements. 39 | end 40 | 41 | def test_if_statements_with_no_else_with_false_condition_return_value 42 | value = if false 43 | :true_value 44 | end 45 | assert_equal __(nil), value 46 | end 47 | 48 | def test_condition_operators 49 | assert_equal __(:true_value), (true ? :true_value : :false_value) 50 | assert_equal __(:false_value), (false ? :true_value : :false_value) 51 | end 52 | 53 | def test_if_statement_modifiers 54 | result = :default_value 55 | result = :true_value if true 56 | 57 | assert_equal __(:true_value), result 58 | end 59 | 60 | def test_unless_statement 61 | result = :default_value 62 | unless false # same as saying 'if !false', which evaluates as 'if true' 63 | result = :false_value 64 | end 65 | assert_equal __(:false_value), result 66 | end 67 | 68 | def test_unless_statement_evaluate_true 69 | result = :default_value 70 | unless true # same as saying 'if !true', which evaluates as 'if false' 71 | result = :true_value 72 | end 73 | assert_equal __(:default_value), result 74 | end 75 | 76 | def test_unless_statement_modifier 77 | result = :default_value 78 | result = :false_value unless false 79 | 80 | assert_equal __(:false_value), result 81 | end 82 | 83 | def test_while_statement 84 | i = 1 85 | result = 1 86 | while i <= 10 87 | result = result * i 88 | i += 1 89 | end 90 | assert_equal __(3628800), result 91 | end 92 | 93 | def test_break_statement 94 | i = 1 95 | result = 1 96 | while true 97 | break unless i <= 10 98 | result = result * i 99 | i += 1 100 | end 101 | assert_equal __(3628800), result 102 | end 103 | 104 | def test_break_statement_returns_values 105 | i = 1 106 | result = while i <= 10 107 | break i if i % 2 == 0 108 | i += 1 109 | end 110 | 111 | assert_equal __(2), result 112 | end 113 | 114 | def test_next_statement 115 | i = 0 116 | result = [] 117 | while i < 10 118 | i += 1 119 | next if (i % 2) == 0 120 | result << i 121 | end 122 | assert_equal __([1, 3, 5, 7, 9]), result 123 | end 124 | 125 | def test_for_statement 126 | array = ["fish", "and", "chips"] 127 | result = [] 128 | for item in array 129 | result << item.upcase 130 | end 131 | assert_equal [__("FISH"), __("AND"), __("CHIPS")], result 132 | end 133 | 134 | def test_times_statement 135 | sum = 0 136 | 10.times do 137 | sum += 1 138 | end 139 | assert_equal __(10), sum 140 | end 141 | 142 | end 143 | -------------------------------------------------------------------------------- /src/about_dice_project.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/neo') 2 | 3 | # Implement a DiceSet Class here: 4 | # 5 | # class DiceSet 6 | # code ... 7 | # end 8 | 9 | #-- 10 | class DiceSet 11 | attr_reader :values 12 | def roll(n) 13 | @values = (1..n).map { rand(6) + 1 } 14 | end 15 | end 16 | 17 | #++ 18 | class AboutDiceProject < Neo::Koan 19 | def test_can_create_a_dice_set 20 | dice = DiceSet.new 21 | assert_not_nil dice 22 | end 23 | 24 | def test_rolling_the_dice_returns_a_set_of_integers_between_1_and_6 25 | dice = DiceSet.new 26 | 27 | dice.roll(5) 28 | assert dice.values.is_a?(Array), "should be an array" 29 | assert_equal 5, dice.values.size 30 | dice.values.each do |value| 31 | assert value >= 1 && value <= 6, "value #{value} must be between 1 and 6" 32 | end 33 | end 34 | 35 | def test_dice_values_do_not_change_unless_explicitly_rolled 36 | dice = DiceSet.new 37 | dice.roll(5) 38 | first_time = dice.values 39 | second_time = dice.values 40 | assert_equal first_time, second_time 41 | end 42 | 43 | def test_dice_values_should_change_between_rolls 44 | dice = DiceSet.new 45 | 46 | dice.roll(5) 47 | first_time = dice.values 48 | 49 | dice.roll(5) 50 | second_time = dice.values 51 | 52 | assert_not_equal first_time, second_time, 53 | "Two rolls should not be equal" 54 | 55 | # THINK ABOUT IT: 56 | # 57 | # If the rolls are random, then it is possible (although not 58 | # likely) that two consecutive rolls are equal. What would be a 59 | # better way to test this? 60 | end 61 | 62 | def test_you_can_roll_different_numbers_of_dice 63 | dice = DiceSet.new 64 | 65 | dice.roll(3) 66 | assert_equal 3, dice.values.size 67 | 68 | dice.roll(1) 69 | assert_equal 1, dice.values.size 70 | end 71 | 72 | end 73 | -------------------------------------------------------------------------------- /src/about_exceptions.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/neo') 2 | 3 | class AboutExceptions < Neo::Koan 4 | 5 | class MySpecialError < RuntimeError 6 | end 7 | 8 | def test_exceptions_inherit_from_Exception 9 | assert_equal __(RuntimeError), MySpecialError.ancestors[1] 10 | assert_equal __(StandardError), MySpecialError.ancestors[2] 11 | assert_equal __(Exception), MySpecialError.ancestors[3] 12 | assert_equal __(Object), MySpecialError.ancestors[4] 13 | end 14 | 15 | def test_rescue_clause 16 | result = nil 17 | begin 18 | fail "Oops" 19 | rescue StandardError => ex 20 | result = :exception_handled 21 | end 22 | 23 | assert_equal __(:exception_handled), result 24 | 25 | assert_equal __(true), ex.is_a?(StandardError), "Should be a Standard Error" 26 | assert_equal __(true), ex.is_a?(RuntimeError), "Should be a Runtime Error" 27 | 28 | assert RuntimeError.ancestors.include?(StandardError), # __ 29 | "RuntimeError is a subclass of StandardError" 30 | 31 | assert_equal __("Oops"), ex.message 32 | end 33 | 34 | def test_raising_a_particular_error 35 | result = nil 36 | begin 37 | # 'raise' and 'fail' are synonyms 38 | raise MySpecialError, "My Message" 39 | rescue MySpecialError => ex 40 | result = :exception_handled 41 | end 42 | 43 | assert_equal __(:exception_handled), result 44 | assert_equal __("My Message"), ex.message 45 | end 46 | 47 | def test_ensure_clause 48 | result = nil 49 | begin 50 | fail "Oops" 51 | rescue StandardError 52 | # no code here 53 | ensure 54 | result = :always_run 55 | end 56 | 57 | assert_equal __(:always_run), result 58 | end 59 | 60 | # Sometimes, we must know about the unknown 61 | def test_asserting_an_error_is_raised # __ 62 | # A do-end is a block, a topic to explore more later 63 | assert_raise(___(MySpecialError)) do 64 | raise MySpecialError.new("New instances can be raised directly.") 65 | end 66 | end 67 | 68 | end 69 | -------------------------------------------------------------------------------- /src/about_extra_credit.rb: -------------------------------------------------------------------------------- 1 | # EXTRA CREDIT: 2 | # 3 | # Create a program that will play the Greed Game. 4 | # Rules for the game are in GREED_RULES.TXT. 5 | # 6 | # You already have a DiceSet class and score function you can use. 7 | # Write a player class and a Game class to complete the project. This 8 | # is a free form assignment, so approach it however you desire. 9 | -------------------------------------------------------------------------------- /src/about_hashes.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/neo') 2 | 3 | class AboutHashes < Neo::Koan 4 | def test_creating_hashes 5 | empty_hash = Hash.new 6 | assert_equal __(Hash), empty_hash.class 7 | assert_equal(__({}), empty_hash) 8 | assert_equal __(0), empty_hash.size 9 | end 10 | 11 | def test_hash_literals 12 | hash = { :one => "uno", :two => "dos" } 13 | assert_equal __(2), hash.size 14 | end 15 | 16 | def test_accessing_hashes 17 | hash = { :one => "uno", :two => "dos" } 18 | assert_equal __("uno"), hash[:one] 19 | assert_equal __("dos"), hash[:two] 20 | assert_equal __(nil), hash[:doesnt_exist] 21 | end 22 | 23 | def test_accessing_hashes_with_fetch 24 | hash = { :one => "uno" } 25 | assert_equal __("uno"), hash.fetch(:one) 26 | assert_raise(___(IndexError, KeyError)) do 27 | hash.fetch(:doesnt_exist) 28 | end 29 | 30 | # THINK ABOUT IT: 31 | # 32 | # Why might you want to use #fetch instead of #[] when accessing hash keys? 33 | end 34 | 35 | def test_changing_hashes 36 | hash = { :one => "uno", :two => "dos" } 37 | hash[:one] = "eins" 38 | 39 | expected = { :one => __("eins"), :two => "dos" } 40 | assert_equal __(expected), hash 41 | 42 | # Bonus Question: Why was "expected" broken out into a variable 43 | # rather than used as a literal? 44 | end 45 | 46 | def test_hash_is_unordered 47 | hash1 = { :one => "uno", :two => "dos" } 48 | hash2 = { :two => "dos", :one => "uno" } 49 | 50 | assert_equal __(true), hash1 == hash2 51 | end 52 | 53 | def test_hash_keys 54 | hash = { :one => "uno", :two => "dos" } 55 | assert_equal __(2), hash.keys.size 56 | assert_equal __(true), hash.keys.include?(:one) 57 | assert_equal __(true), hash.keys.include?(:two) 58 | assert_equal __(Array), hash.keys.class 59 | end 60 | 61 | def test_hash_values 62 | hash = { :one => "uno", :two => "dos" } 63 | assert_equal __(2), hash.values.size 64 | assert_equal __(true), hash.values.include?("uno") 65 | assert_equal __(true), hash.values.include?("dos") 66 | assert_equal __(Array), hash.values.class 67 | end 68 | 69 | def test_combining_hashes 70 | hash = { "jim" => 53, "amy" => 20, "dan" => 23 } 71 | new_hash = hash.merge({ "jim" => 54, "jenny" => 26 }) 72 | 73 | assert_equal __(true), hash != new_hash 74 | 75 | expected = { "jim" => __(54), "amy" => 20, "dan" => 23, "jenny" => __(26) } 76 | assert_equal __(true), expected == new_hash 77 | end 78 | 79 | def test_default_value 80 | hash1 = Hash.new 81 | hash1[:one] = 1 82 | 83 | assert_equal __(1), hash1[:one] 84 | assert_equal __(nil), hash1[:two] 85 | 86 | hash2 = Hash.new("dos") 87 | hash2[:one] = 1 88 | 89 | assert_equal __(1), hash2[:one] 90 | assert_equal __("dos"), hash2[:two] 91 | end 92 | 93 | def test_default_value_is_the_same_object 94 | hash = Hash.new([]) 95 | 96 | hash[:one] << "uno" 97 | hash[:two] << "dos" 98 | 99 | assert_equal __(["uno", "dos"]), hash[:one] 100 | assert_equal __(["uno", "dos"]), hash[:two] 101 | assert_equal __(["uno", "dos"]), hash[:three] 102 | 103 | assert_equal __(true), hash[:one].object_id == hash[:two].object_id 104 | end 105 | 106 | def test_default_value_with_block 107 | hash = Hash.new {|hash, key| hash[key] = [] } 108 | 109 | hash[:one] << "uno" 110 | hash[:two] << "dos" 111 | 112 | assert_equal __(["uno"]), hash[:one] 113 | assert_equal __(["dos"]), hash[:two] 114 | assert_equal __([]), hash[:three] 115 | end 116 | 117 | def test_default_value_attribute 118 | hash = Hash.new 119 | 120 | assert_equal __(nil), hash[:some_key] 121 | 122 | hash.default = 'peanut' 123 | 124 | assert_equal __('peanut'), hash[:some_key] 125 | end 126 | end 127 | -------------------------------------------------------------------------------- /src/about_inheritance.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/neo') 2 | 3 | class AboutInheritance < Neo::Koan 4 | class Dog 5 | attr_reader :name 6 | 7 | def initialize(name) 8 | @name = name 9 | end 10 | 11 | def bark 12 | "WOOF" 13 | end 14 | end 15 | 16 | class Chihuahua < Dog 17 | def wag 18 | :happy 19 | end 20 | 21 | def bark 22 | "yip" 23 | end 24 | end 25 | 26 | def test_subclasses_have_the_parent_as_an_ancestor 27 | assert_equal __(true), Chihuahua.ancestors.include?(Dog) 28 | end 29 | 30 | def test_all_classes_ultimately_inherit_from_object 31 | assert_equal __(true), Chihuahua.ancestors.include?(Object) 32 | end 33 | 34 | def test_subclasses_inherit_behavior_from_parent_class 35 | chico = Chihuahua.new("Chico") 36 | assert_equal __("Chico"), chico.name 37 | end 38 | 39 | def test_subclasses_add_new_behavior 40 | chico = Chihuahua.new("Chico") 41 | assert_equal __(:happy), chico.wag 42 | 43 | assert_raise(___(NoMethodError)) do 44 | fido = Dog.new("Fido") 45 | fido.wag 46 | end 47 | end 48 | 49 | def test_subclasses_can_modify_existing_behavior 50 | chico = Chihuahua.new("Chico") 51 | assert_equal __("yip"), chico.bark 52 | 53 | fido = Dog.new("Fido") 54 | assert_equal __("WOOF"), fido.bark 55 | end 56 | 57 | # ------------------------------------------------------------------ 58 | 59 | class BullDog < Dog 60 | def bark 61 | super + ", GROWL" 62 | end 63 | end 64 | 65 | def test_subclasses_can_invoke_parent_behavior_via_super 66 | ralph = BullDog.new("Ralph") 67 | assert_equal __("WOOF, GROWL"), ralph.bark 68 | end 69 | 70 | # ------------------------------------------------------------------ 71 | 72 | class GreatDane < Dog 73 | def growl 74 | super.bark + ", GROWL" 75 | end 76 | end 77 | 78 | def test_super_does_not_work_cross_method 79 | george = GreatDane.new("George") 80 | assert_raise(___(NoMethodError)) do 81 | george.growl 82 | end 83 | end 84 | 85 | end 86 | -------------------------------------------------------------------------------- /src/about_iteration.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/neo') 2 | 3 | class AboutIteration < Neo::Koan 4 | 5 | # -- An Aside ------------------------------------------------------ 6 | # Ruby 1.8 stores names as strings. Ruby 1.9 and later stores names 7 | # as symbols. So we use a version dependent method "as_name" to 8 | # convert to the right format in the koans. We will use "as_name" 9 | # whenever comparing to lists of methods. 10 | 11 | in_ruby_version("1.8") do 12 | def as_name(name) 13 | name.to_s 14 | end 15 | end 16 | 17 | in_ruby_version("1.9", "2", "3") do 18 | def as_name(name) 19 | name.to_sym 20 | end 21 | end 22 | 23 | # Ok, now back to the Koans. 24 | # ------------------------------------------------------------------- 25 | 26 | def test_each_is_a_method_on_arrays 27 | assert_equal __(true), [].methods.include?(as_name(:each)) 28 | end 29 | 30 | def test_iterating_with_each 31 | array = [1, 2, 3] 32 | sum = 0 33 | array.each do |item| 34 | sum += item 35 | end 36 | assert_equal __(6), sum 37 | end 38 | 39 | def test_each_can_use_curly_brace_blocks_too 40 | array = [1, 2, 3] 41 | sum = 0 42 | array.each { |item| sum += item } 43 | assert_equal __(6), sum 44 | end 45 | 46 | def test_break_works_with_each_style_iterations 47 | array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] 48 | sum = 0 49 | array.each do |item| 50 | break if item > 3 51 | sum += item 52 | end 53 | assert_equal __(6), sum 54 | end 55 | 56 | def test_collect_transforms_elements_of_an_array 57 | array = [1, 2, 3] 58 | new_array = array.collect { |item| item + 10 } 59 | assert_equal __([11, 12, 13]), new_array 60 | 61 | # NOTE: 'map' is another name for the 'collect' operation 62 | another_array = array.map { |item| item + 10 } 63 | assert_equal __([11, 12, 13]), another_array 64 | end 65 | 66 | def test_select_selects_certain_items_from_an_array 67 | array = [1, 2, 3, 4, 5, 6] 68 | 69 | even_numbers = array.select { |item| (item % 2) == 0 } 70 | assert_equal __([2, 4, 6]), even_numbers 71 | 72 | # NOTE: 'find_all' is another name for the 'select' operation 73 | more_even_numbers = array.find_all { |item| (item % 2) == 0 } 74 | assert_equal __([2, 4, 6]), more_even_numbers 75 | end 76 | 77 | def test_find_locates_the_first_element_matching_a_criteria 78 | array = ["Jim", "Bill", "Clarence", "Doug", "Eli"] 79 | 80 | assert_equal __("Clarence"), array.find { |item| item.size > 4 } 81 | end 82 | 83 | def test_inject_will_blow_your_mind 84 | result = [2, 3, 4].inject(0) { |sum, item| sum + item } 85 | assert_equal __(9), result 86 | 87 | result2 = [2, 3, 4].inject(1) { |product, item| product * item } 88 | assert_equal __(24), result2 89 | 90 | # Extra Credit: 91 | # Describe in your own words what inject does. 92 | end 93 | 94 | def test_all_iteration_methods_work_on_any_collection_not_just_arrays 95 | # Ranges act like a collection 96 | result = (1..3).map { |item| item + 10 } 97 | assert_equal __([11, 12, 13]), result 98 | 99 | # Files act like a collection of lines 100 | File.open("example_file.txt") do |file| 101 | upcase_lines = file.map { |line| line.strip.upcase } 102 | assert_equal __(["THIS", "IS", "A", "TEST"]), upcase_lines 103 | end 104 | 105 | # NOTE: You can create your own collections that work with each, 106 | # map, select, etc. 107 | end 108 | 109 | # Bonus Question: In the previous koan, we saw the construct: 110 | # 111 | # File.open(filename) do |file| 112 | # # code to read 'file' 113 | # end 114 | # 115 | # Why did we do it that way instead of the following? 116 | # 117 | # file = File.open(filename) 118 | # # code to read 'file' 119 | # 120 | # When you get to the "AboutSandwichCode" koan, recheck your answer. 121 | 122 | end 123 | -------------------------------------------------------------------------------- /src/about_java_interop.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/neo') 2 | 3 | include Java 4 | 5 | # Concepts 6 | # * Pull in a java class 7 | # * calling a method, Camel vs snake 8 | # * Resolving module/class name conflicts 9 | # * Showing what gets returned 10 | # * Ruby Strings VS Java Strings 11 | # * Calling custom java class 12 | # * Calling Ruby from java??? 13 | 14 | class AboutJavaInterop < Neo::Koan 15 | def test_using_a_java_library_class 16 | java_array = java.util.ArrayList.new 17 | assert_equal __(Java::JavaUtil::ArrayList), java_array.class 18 | end 19 | 20 | def test_java_class_can_be_referenced_using_both_ruby_and_java_like_syntax 21 | assert_equal __(true), Java::JavaUtil::ArrayList == java.util.ArrayList 22 | end 23 | 24 | def test_include_class_includes_class_in_module_scope 25 | assert_nil defined?(TreeSet) # __ 26 | include_class "java.util.TreeSet" 27 | assert_equal __("constant"), defined?(TreeSet) 28 | end 29 | 30 | # THINK ABOUT IT: 31 | # 32 | # What if we use: 33 | # 34 | # include_class "java.lang.String" 35 | # 36 | # What would be the value of the String constant after this 37 | # include_class is run? Would it be useful to provide a way of 38 | # aliasing java classes to different names? 39 | 40 | JString = java.lang.String 41 | def test_also_java_class_can_be_given_ruby_aliases 42 | java_string = JString.new("A Java String") 43 | assert_equal __(java.lang.String), java_string.class 44 | assert_equal __(java.lang.String), JString 45 | end 46 | 47 | def test_can_directly_call_java_methods_on_java_objects 48 | java_string = JString.new("A Java String") 49 | assert_equal __("a java string"), java_string.toLowerCase 50 | end 51 | 52 | def test_jruby_provides_snake_case_versions_of_java_methods 53 | java_string = JString.new("A Java String") 54 | assert_equal __("a java string"), java_string.to_lower_case 55 | end 56 | 57 | def test_jruby_provides_question_mark_versions_of_boolean_methods 58 | java_string = JString.new("A Java String") 59 | assert_equal __(true), java_string.endsWith("String") 60 | assert_equal __(true), java_string.ends_with("String") 61 | assert_equal __(true), java_string.ends_with?("String") 62 | end 63 | 64 | def test_java_string_are_not_ruby_strings 65 | ruby_string = "A Java String" 66 | java_string = java.lang.String.new(ruby_string) 67 | assert_equal __(true), java_string.is_a?(java.lang.String) 68 | assert_equal __(false), java_string.is_a?(String) 69 | end 70 | 71 | def test_java_strings_can_be_compared_to_ruby_strings_maybe 72 | ruby_string = "A Java String" 73 | java_string = java.lang.String.new(ruby_string) 74 | assert_equal __(false), ruby_string == java_string 75 | assert_equal __(true), java_string == ruby_string 76 | 77 | # THINK ABOUT IT: 78 | # 79 | # Is there any possible way for this to be more wrong? 80 | # 81 | # SERIOUSLY, THINK ABOUT IT: 82 | # 83 | # Why do you suppose that Ruby and Java strings compare like that? 84 | # 85 | # ADVANCED THINK ABOUT IT: 86 | # 87 | # Is there a way to make Ruby/Java string comparisons commutative? 88 | # How would you do it? 89 | end 90 | 91 | def test_however_most_methods_returning_strings_return_ruby_strings 92 | java_array = java.util.ArrayList.new 93 | assert_equal __("[]"), java_array.toString 94 | assert_equal __(true), java_array.toString.is_a?(String) 95 | assert_equal __(false), java_array.toString.is_a?(java.lang.String) 96 | end 97 | 98 | def test_some_ruby_objects_can_be_coerced_to_java 99 | assert_equal __(Java::JavaLang::String), "ruby string".to_java.class 100 | assert_equal __(Java::JavaLang::Long), 1.to_java.class 101 | assert_equal __(Java::JavaLang::Double), 9.32.to_java.class 102 | assert_equal __(Java::JavaLang::Boolean), false.to_java.class 103 | end 104 | 105 | def test_some_ruby_objects_are_not_coerced_to_what_you_might_expect 106 | assert_equal __(false), [].to_java.class == Java::JavaUtil::ArrayList 107 | assert_equal __(false), {}.to_java.class == Java::JavaUtil::HashMap 108 | assert_equal __(false), Object.new.to_java.class == Java::JavaLang::Object 109 | end 110 | 111 | def test_java_collections_are_enumerable 112 | java_array = java.util.ArrayList.new 113 | java_array << "one" << "two" << "three" 114 | assert_equal __(["ONE", "TWO", "THREE"]), java_array.map { |item| item.upcase } 115 | end 116 | 117 | # ------------------------------------------------------------------ 118 | 119 | # Open the Java ArrayList class and add a new method. 120 | class Java::JavaUtil::ArrayList 121 | def multiply_all 122 | result = 1 123 | each do |item| 124 | result *= item 125 | end 126 | result 127 | end 128 | end 129 | 130 | def test_java_class_are_open_from_ruby 131 | java_array = java.util.ArrayList.new 132 | java_array.add_all([1,2,3,4,5]) 133 | 134 | assert_equal __(120), java_array.multiply_all 135 | end 136 | 137 | end 138 | -------------------------------------------------------------------------------- /src/about_keyword_arguments.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/neo') 2 | 3 | class AboutKeywordArguments < Neo::Koan 4 | 5 | def method_with_keyword_arguments(one: 1, two: 'two') 6 | [one, two] 7 | end 8 | 9 | def test_keyword_arguments 10 | assert_equal __(Array), method_with_keyword_arguments.class 11 | assert_equal __([1, 'two']), method_with_keyword_arguments 12 | assert_equal __(['one', 'two']), method_with_keyword_arguments(one: 'one') 13 | assert_equal __([1, 2]), method_with_keyword_arguments(two: 2) 14 | end 15 | 16 | def method_with_keyword_arguments_with_mandatory_argument(one, two: 2, three: 3) 17 | [one, two, three] 18 | end 19 | 20 | def test_keyword_arguments_with_wrong_number_of_arguments 21 | exception = assert_raise (___(ArgumentError)) do 22 | method_with_keyword_arguments_with_mandatory_argument 23 | end 24 | assert_match(/#{__("wrong number of arguments")}/, exception.message) 25 | end 26 | 27 | def method_with_mandatory_keyword_arguments(one:, two: 'two') 28 | [one, two] 29 | end 30 | 31 | def test_mandatory_keyword_arguments 32 | assert_equal __(['one', 'two']), method_with_mandatory_keyword_arguments(one: 'one') 33 | assert_equal __([1, 2]), method_with_mandatory_keyword_arguments(two: 2, one: 1) 34 | end 35 | 36 | def test_mandatory_keyword_arguments_without_mandatory_argument 37 | exception = assert_raise(___(ArgumentError)) do 38 | method_with_mandatory_keyword_arguments 39 | end 40 | assert_match(/#{__("missing keyword: :one")}/, exception.message) 41 | end 42 | 43 | end 44 | -------------------------------------------------------------------------------- /src/about_message_passing.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/neo') 2 | 3 | class AboutMessagePassing < Neo::Koan 4 | 5 | class MessageCatcher 6 | def caught? 7 | true 8 | end 9 | end 10 | 11 | def test_methods_can_be_called_directly 12 | mc = MessageCatcher.new 13 | 14 | assert mc.caught? # __ 15 | end 16 | 17 | def test_methods_can_be_invoked_by_sending_the_message 18 | mc = MessageCatcher.new 19 | 20 | assert mc.send(:caught?) # __ 21 | end 22 | 23 | def test_methods_can_be_invoked_more_dynamically 24 | mc = MessageCatcher.new 25 | 26 | assert mc.send("caught?") # __ 27 | assert mc.send("caught" + __("?") ) # What do you need to add to the first string? 28 | assert mc.send("CAUGHT?".____(:downcase) ) # What would you need to do to the string? 29 | end 30 | 31 | def test_send_with_underscores_will_also_send_messages 32 | mc = MessageCatcher.new 33 | 34 | assert_equal __(true), mc.__send__(:caught?) 35 | 36 | # THINK ABOUT IT: 37 | # 38 | # Why does Ruby provide both send and __send__ ? 39 | end 40 | 41 | def test_classes_can_be_asked_if_they_know_how_to_respond 42 | mc = MessageCatcher.new 43 | 44 | assert_equal __(true), mc.respond_to?(:caught?) 45 | assert_equal __(false), mc.respond_to?(:does_not_exist) 46 | end 47 | 48 | # ------------------------------------------------------------------ 49 | 50 | class MessageCatcher 51 | def add_a_payload(*args) 52 | args 53 | end 54 | end 55 | 56 | def test_sending_a_message_with_arguments 57 | mc = MessageCatcher.new 58 | 59 | assert_equal __([]), mc.add_a_payload 60 | assert_equal __([]), mc.send(:add_a_payload) 61 | 62 | assert_equal __([3, 4, nil, 6]), mc.add_a_payload(3, 4, nil, 6) 63 | assert_equal __([3, 4, nil, 6]), mc.send(:add_a_payload, 3, 4, nil, 6) 64 | end 65 | 66 | # NOTE: 67 | # 68 | # Both obj.msg and obj.send(:msg) sends the message named :msg to 69 | # the object. We use "send" when the name of the message can vary 70 | # dynamically (e.g. calculated at run time), but by far the most 71 | # common way of sending a message is just to say: obj.msg. 72 | 73 | # ------------------------------------------------------------------ 74 | 75 | class TypicalObject 76 | end 77 | 78 | def test_sending_undefined_messages_to_a_typical_object_results_in_errors 79 | typical = TypicalObject.new 80 | 81 | exception = assert_raise(___(NoMethodError)) do 82 | typical.foobar 83 | end 84 | assert_match(/foobar/, exception.message) # __ 85 | end 86 | 87 | def test_calling_method_missing_causes_the_no_method_error 88 | typical = TypicalObject.new 89 | 90 | exception = assert_raise(___(NoMethodError)) do 91 | typical.method_missing(:foobar) 92 | end 93 | assert_match(/foobar/, exception.message) # __ 94 | 95 | # THINK ABOUT IT: 96 | # 97 | # If the method :method_missing causes the NoMethodError, then 98 | # what would happen if we redefine method_missing? 99 | # 100 | # NOTE: 101 | # 102 | # In Ruby 1.8 the method_missing method is public and can be 103 | # called as shown above. However, in Ruby 1.9 (and later versions) 104 | # the method_missing method is private. We explicitly made it 105 | # public in the testing framework so this example works in both 106 | # versions of Ruby. Just keep in mind you can't call 107 | # method_missing like that after Ruby 1.9 normally. 108 | # 109 | # Thanks. We now return you to your regularly scheduled Ruby 110 | # Koans. 111 | end 112 | 113 | # ------------------------------------------------------------------ 114 | 115 | class AllMessageCatcher 116 | def method_missing(method_name, *args, &block) 117 | "Someone called #{method_name} with <#{args.join(", ")}>" 118 | end 119 | end 120 | 121 | def test_all_messages_are_caught 122 | catcher = AllMessageCatcher.new 123 | 124 | assert_equal __("Someone called foobar with <>"), catcher.foobar 125 | assert_equal __("Someone called foobaz with <1>"), catcher.foobaz(1) 126 | assert_equal __("Someone called sum with <1, 2, 3, 4, 5, 6>"), catcher.sum(1,2,3,4,5,6) 127 | end 128 | 129 | def test_catching_messages_makes_respond_to_lie 130 | catcher = AllMessageCatcher.new 131 | 132 | assert_nothing_raised do # __ 133 | catcher.any_method 134 | end 135 | assert_equal __(false), catcher.respond_to?(:any_method) 136 | end 137 | 138 | # ------------------------------------------------------------------ 139 | 140 | class WellBehavedFooCatcher 141 | def method_missing(method_name, *args, &block) 142 | if method_name.to_s[0,3] == "foo" 143 | "Foo to you too" 144 | else 145 | super(method_name, *args, &block) 146 | end 147 | end 148 | end 149 | 150 | def test_foo_method_are_caught 151 | catcher = WellBehavedFooCatcher.new 152 | 153 | assert_equal __("Foo to you too"), catcher.foo_bar 154 | assert_equal __("Foo to you too"), catcher.foo_baz 155 | end 156 | 157 | def test_non_foo_messages_are_treated_normally 158 | catcher = WellBehavedFooCatcher.new 159 | 160 | assert_raise(___(NoMethodError)) do 161 | catcher.normal_undefined_method 162 | end 163 | end 164 | 165 | # ------------------------------------------------------------------ 166 | 167 | # (note: just reopening class from above) 168 | class WellBehavedFooCatcher 169 | def respond_to?(method_name) 170 | if method_name.to_s[0,3] == "foo" 171 | true 172 | else 173 | super(method_name) 174 | end 175 | end 176 | end 177 | 178 | def test_explicitly_implementing_respond_to_lets_objects_tell_the_truth 179 | catcher = WellBehavedFooCatcher.new 180 | 181 | assert_equal __(true), catcher.respond_to?(:foo_bar) 182 | assert_equal __(false), catcher.respond_to?(:something_else) 183 | end 184 | 185 | end 186 | -------------------------------------------------------------------------------- /src/about_methods.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/neo') 2 | 3 | def my_global_method(a,b) 4 | a + b 5 | end 6 | 7 | class AboutMethods < Neo::Koan 8 | 9 | def test_calling_global_methods 10 | assert_equal __(5), my_global_method(2,3) 11 | end 12 | 13 | def test_calling_global_methods_without_parentheses 14 | result = my_global_method 2, 3 15 | assert_equal __(5), result 16 | end 17 | 18 | # (NOTE: We are Using eval below because the example code is 19 | # considered to be syntactically invalid). 20 | def test_sometimes_missing_parentheses_are_ambiguous 21 | #-- 22 | eval "assert_equal 5, my_global_method(2, 3)" # REMOVE CHECK # __ 23 | if false 24 | #++ 25 | eval "assert_equal 5, my_global_method 2, 3" # ENABLE CHECK # __ 26 | #-- 27 | end 28 | #++ 29 | # 30 | # Ruby doesn't know if you mean: 31 | # 32 | # assert_equal(5, my_global_method(2), 3) 33 | # or 34 | # assert_equal(5, my_global_method(2, 3)) 35 | # 36 | # Rewrite the eval string to continue. 37 | # 38 | end 39 | 40 | # NOTE: wrong number of arguments is not a SYNTAX error, but a 41 | # runtime error. 42 | def test_calling_global_methods_with_wrong_number_of_arguments 43 | exception = assert_raise(___(ArgumentError)) do 44 | my_global_method 45 | end 46 | #-- 47 | pattern = "wrong (number|#) of arguments" 48 | #++ 49 | assert_match(/#{__(pattern)}/, exception.message) 50 | 51 | exception = assert_raise(___(ArgumentError)) do 52 | my_global_method(1,2,3) 53 | end 54 | assert_match(/#{__(pattern)}/, exception.message) 55 | end 56 | 57 | # ------------------------------------------------------------------ 58 | 59 | def method_with_defaults(a, b=:default_value) 60 | [a, b] 61 | end 62 | 63 | def test_calling_with_default_values 64 | assert_equal [1, __(:default_value)], method_with_defaults(1) 65 | assert_equal [1, __(2)], method_with_defaults(1, 2) 66 | end 67 | 68 | # ------------------------------------------------------------------ 69 | 70 | def method_with_var_args(*args) 71 | args 72 | end 73 | 74 | def test_calling_with_variable_arguments 75 | assert_equal __(Array), method_with_var_args.class 76 | assert_equal __([]), method_with_var_args 77 | assert_equal __([:one]), method_with_var_args(:one) 78 | assert_equal __([:one, :two]), method_with_var_args(:one, :two) 79 | end 80 | 81 | # ------------------------------------------------------------------ 82 | 83 | def method_with_explicit_return 84 | :a_non_return_value 85 | return :return_value 86 | :another_non_return_value 87 | end 88 | 89 | def test_method_with_explicit_return 90 | assert_equal __(:return_value), method_with_explicit_return 91 | end 92 | 93 | # ------------------------------------------------------------------ 94 | 95 | def method_without_explicit_return 96 | :a_non_return_value 97 | :return_value 98 | end 99 | 100 | def test_method_without_explicit_return 101 | assert_equal __(:return_value), method_without_explicit_return 102 | end 103 | 104 | # ------------------------------------------------------------------ 105 | 106 | def my_method_in_the_same_class(a, b) 107 | a * b 108 | end 109 | 110 | def test_calling_methods_in_same_class 111 | assert_equal __(12), my_method_in_the_same_class(3,4) 112 | end 113 | 114 | def test_calling_methods_in_same_class_with_explicit_receiver 115 | assert_equal __(12), self.my_method_in_the_same_class(3,4) 116 | end 117 | 118 | # ------------------------------------------------------------------ 119 | 120 | def my_private_method 121 | "a secret" 122 | end 123 | private :my_private_method 124 | 125 | def test_calling_private_methods_without_receiver 126 | assert_equal __("a secret"), my_private_method 127 | end 128 | 129 | if before_ruby_version("2.7") # https://github.com/edgecase/ruby_koans/issues/12 130 | def test_calling_private_methods_with_an_explicit_receiver 131 | exception = assert_raise(___(NoMethodError)) do 132 | self.my_private_method 133 | end 134 | assert_match /#{__("method `my_private_method'")}/, exception.message 135 | end 136 | end 137 | 138 | # ------------------------------------------------------------------ 139 | 140 | class Dog 141 | def name 142 | "Fido" 143 | end 144 | 145 | private 146 | 147 | def tail 148 | "tail" 149 | end 150 | end 151 | 152 | def test_calling_methods_in_other_objects_require_explicit_receiver 153 | rover = Dog.new 154 | assert_equal __("Fido"), rover.name 155 | end 156 | 157 | def test_calling_private_methods_in_other_objects 158 | rover = Dog.new 159 | assert_raise(___(NoMethodError)) do 160 | rover.tail 161 | end 162 | end 163 | end 164 | -------------------------------------------------------------------------------- /src/about_modules.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/neo') 2 | 3 | class AboutModules < Neo::Koan 4 | module Nameable 5 | def set_name(new_name) 6 | @name = new_name 7 | end 8 | 9 | def here 10 | :in_module 11 | end 12 | end 13 | 14 | def test_cant_instantiate_modules 15 | assert_raise(___(NoMethodError)) do 16 | Nameable.new 17 | end 18 | end 19 | 20 | # ------------------------------------------------------------------ 21 | 22 | class Dog 23 | include Nameable 24 | 25 | attr_reader :name 26 | 27 | def initialize 28 | @name = "Fido" 29 | end 30 | 31 | def bark 32 | "WOOF" 33 | end 34 | 35 | def here 36 | :in_object 37 | end 38 | end 39 | 40 | def test_normal_methods_are_available_in_the_object 41 | fido = Dog.new 42 | assert_equal __("WOOF"), fido.bark 43 | end 44 | 45 | def test_module_methods_are_also_available_in_the_object 46 | fido = Dog.new 47 | assert_nothing_raised do # __ 48 | fido.set_name("Rover") 49 | end 50 | end 51 | 52 | def test_module_methods_can_affect_instance_variables_in_the_object 53 | fido = Dog.new 54 | assert_equal __("Fido"), fido.name 55 | fido.set_name("Rover") 56 | assert_equal __("Rover"), fido.name 57 | end 58 | 59 | def test_classes_can_override_module_methods 60 | fido = Dog.new 61 | assert_equal __(:in_object), fido.here 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /src/about_nil.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/neo') 2 | 3 | class AboutNil < Neo::Koan 4 | def test_nil_is_an_object 5 | assert_equal __(true), nil.is_a?(Object), "Unlike NULL in other languages" 6 | end 7 | 8 | def test_you_dont_get_null_pointer_errors_when_calling_methods_on_nil 9 | # What happens when you call a method that doesn't exist. The 10 | # following begin/rescue/end code block captures the exception and 11 | # makes some assertions about it. 12 | begin 13 | nil.some_method_nil_doesnt_know_about 14 | rescue Exception => ex 15 | # What exception has been caught? 16 | assert_equal __(NoMethodError), ex.class 17 | 18 | # What message was attached to the exception? 19 | # (HINT: replace __ with part of the error message.) 20 | assert_match(/#{__("undefined method")}/, ex.message) 21 | end 22 | end 23 | 24 | def test_nil_has_a_few_methods_defined_on_it 25 | assert_equal __(true), nil.nil? 26 | assert_equal __(""), nil.to_s 27 | assert_equal __("nil"), nil.inspect 28 | 29 | # THINK ABOUT IT: 30 | # 31 | # Is it better to use 32 | # obj.nil? 33 | # or 34 | # obj == nil 35 | # Why? 36 | end 37 | 38 | end 39 | -------------------------------------------------------------------------------- /src/about_objects.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/neo') 2 | 3 | class AboutObjects < Neo::Koan 4 | def test_everything_is_an_object 5 | assert_equal __(true), 1.is_a?(Object) 6 | assert_equal __(true), 1.5.is_a?(Object) 7 | assert_equal __(true), "string".is_a?(Object) 8 | assert_equal __(true), nil.is_a?(Object) 9 | assert_equal __(true), Object.is_a?(Object) 10 | end 11 | 12 | def test_objects_can_be_converted_to_strings 13 | assert_equal __("123"), 123.to_s 14 | assert_equal __(""), nil.to_s 15 | end 16 | 17 | def test_objects_can_be_inspected 18 | assert_equal __("123"), 123.inspect 19 | assert_equal __("nil"), nil.inspect 20 | end 21 | 22 | def test_every_object_has_an_id 23 | obj = Object.new 24 | assert_equal __(Fixnum), obj.object_id.class 25 | end 26 | 27 | def test_every_object_has_different_id 28 | obj = Object.new 29 | another_obj = Object.new 30 | assert_equal __(true), obj.object_id != another_obj.object_id 31 | end 32 | 33 | def test_small_integers_have_fixed_ids 34 | assert_equal __(1), 0.object_id 35 | assert_equal __(3), 1.object_id 36 | assert_equal __(5), 2.object_id 37 | assert_equal __(201), 100.object_id 38 | 39 | # THINK ABOUT IT: 40 | # What pattern do the object IDs for small integers follow? 41 | end 42 | 43 | def test_clone_creates_a_different_object 44 | obj = Object.new 45 | copy = obj.clone 46 | 47 | assert_equal __(true), obj != copy 48 | assert_equal __(true), obj.object_id != copy.object_id 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /src/about_open_classes.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/neo') 2 | 3 | class AboutOpenClasses < Neo::Koan 4 | class Dog 5 | def bark 6 | "WOOF" 7 | end 8 | end 9 | 10 | def test_as_defined_dogs_do_bark 11 | fido = Dog.new 12 | assert_equal __("WOOF"), fido.bark 13 | end 14 | 15 | # ------------------------------------------------------------------ 16 | 17 | # Open the existing Dog class and add a new method. 18 | class Dog 19 | def wag 20 | "HAPPY" 21 | end 22 | end 23 | 24 | def test_after_reopening_dogs_can_both_wag_and_bark 25 | fido = Dog.new 26 | assert_equal __("HAPPY"), fido.wag 27 | assert_equal __("WOOF"), fido.bark 28 | end 29 | 30 | # ------------------------------------------------------------------ 31 | 32 | class ::Integer 33 | def answer_to_life_universe_and_everything? 34 | self == 42 35 | end 36 | end 37 | 38 | def test_even_existing_built_in_classes_can_be_reopened 39 | assert_equal __(false), 1.answer_to_life_universe_and_everything? 40 | assert_equal __(true), 42.answer_to_life_universe_and_everything? 41 | end 42 | 43 | # NOTE: To understand why we need the :: before Integer, you need to 44 | # become enlightened about scope. 45 | end 46 | -------------------------------------------------------------------------------- /src/about_pattern_matching.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/neo') 2 | 3 | class AboutPatternMatching < Neo::Koan 4 | 5 | def test_pattern_may_not_match 6 | begin 7 | case [true, false] 8 | in [a, b] if a == b # The condition after pattern is called guard. 9 | :match 10 | end 11 | rescue Exception => ex 12 | # What exception has been caught? 13 | assert_equal __, ex.class 14 | end 15 | end 16 | 17 | def test_we_can_use_else 18 | result = case [true, false] 19 | in [a, b] if a == b 20 | :match 21 | else 22 | :no_match 23 | end 24 | 25 | assert_equal __, result 26 | end 27 | 28 | # ------------------------------------------------------------------ 29 | 30 | def value_pattern(variable) 31 | case variable 32 | in 0 33 | :match_exact_value 34 | in 1..10 35 | :match_in_range 36 | in Integer 37 | :match_with_class 38 | else 39 | :no_match 40 | end 41 | end 42 | 43 | def test_value_pattern 44 | assert_equal __, value_pattern(0) 45 | assert_equal __, value_pattern(5) 46 | assert_equal __, value_pattern(100) 47 | assert_equal __, value_pattern('Not a Number!') 48 | end 49 | 50 | # ------------------------------------------------------------------ 51 | # This pattern will bind variable to the value 52 | 53 | def variable_pattern_with_binding(variable) 54 | case 0 55 | in variable 56 | variable 57 | else 58 | :no_match 59 | end 60 | end 61 | 62 | def test_variable_pattern_with_binding 63 | assert_equal __, variable_pattern_with_binding(1) 64 | end 65 | 66 | # ------------------------------------------------------------------ 67 | 68 | # We can pin the value of the variable with ^ 69 | 70 | def variable_pattern_with_pin(variable) 71 | case 0 72 | in ^variable 73 | variable 74 | else 75 | :no_match 76 | end 77 | end 78 | 79 | def test_variable_pattern_with_pin 80 | assert_equal __, variable_pattern_with_pin(1) 81 | end 82 | 83 | # ------------------------------------------------------------------ 84 | 85 | # We can drop values from pattern 86 | 87 | def pattern_with_dropping(variable) 88 | case variable 89 | in [_, 2] 90 | :match 91 | else 92 | :no_match 93 | end 94 | end 95 | 96 | def test_pattern_with_dropping 97 | assert_equal __, pattern_with_dropping(['I will not be checked', 2]) 98 | assert_equal __, pattern_with_dropping(['I will not be checked', 'But I will!']) 99 | end 100 | 101 | # ------------------------------------------------------------------ 102 | 103 | # We can use logical *or* in patterns 104 | 105 | def alternative_pattern(variable) 106 | case variable 107 | in 0 | false | nil 108 | :match 109 | else 110 | :no_match 111 | end 112 | end 113 | 114 | def test_alternative_pattern 115 | assert_equal __, alternative_pattern(0) 116 | assert_equal __, alternative_pattern(false) 117 | assert_equal __, alternative_pattern(nil) 118 | assert_equal __, alternative_pattern(4) 119 | end 120 | 121 | # ------------------------------------------------------------------ 122 | 123 | # As pattern binds the variable to the value if pattern matches 124 | # pat: pat => var 125 | 126 | def as_pattern 127 | a = 'First I was afraid' 128 | 129 | case 'I was petrified' 130 | in String => a 131 | a 132 | else 133 | :no_match 134 | end 135 | end 136 | 137 | def test_as_pattern 138 | assert_equal __, as_pattern 139 | end 140 | 141 | # ------------------------------------------------------------------ 142 | 143 | # Array pattern works with all objects that have #deconstruct method that returns Array 144 | # It is useful to cut needed parts from Array-ish objects 145 | 146 | class Deconstructible 147 | def initialize(str) 148 | @data = str 149 | end 150 | 151 | def deconstruct 152 | @data&.split('') 153 | end 154 | end 155 | 156 | def array_pattern(deconstructible) 157 | case deconstructible 158 | in 'a', *res, 'd' 159 | res 160 | else 161 | :no_match 162 | end 163 | end 164 | 165 | def test_array_pattern 166 | assert_equal __, array_pattern(Deconstructible.new('abcd')) 167 | assert_equal __, array_pattern(Deconstructible.new('123')) 168 | end 169 | 170 | # ------------------------------------------------------------------ 171 | 172 | # Hash pattern is quite the same as Array pattern, but it expects #deconsturct_keys(keys) method 173 | # It works with symbol keys for now 174 | 175 | class LetterAccountant 176 | def initialize(str) 177 | @data = str 178 | end 179 | 180 | def deconstruct_keys(keys) 181 | # we will count number of occurrences of each key in our data 182 | keys.map { |key| [key, @data.count(key.to_s)] }.to_h 183 | end 184 | end 185 | 186 | def hash_pattern(deconstructible_as_hash) 187 | case deconstructible_as_hash 188 | in {a: a, b: b} 189 | [a, b] 190 | else 191 | :no_match 192 | end 193 | end 194 | 195 | def test_hash_pattern 196 | assert_equal __, hash_pattern(LetterAccountant.new('aaabbc')) 197 | assert_equal __, hash_pattern(LetterAccountant.new('xyz')) 198 | end 199 | 200 | # we can write it even shorter 201 | def hash_pattern_with_sugar(deconstructible_as_hash) 202 | case deconstructible_as_hash 203 | in a:, b: 204 | [a, b] 205 | else 206 | :no_match 207 | end 208 | end 209 | 210 | def test_hash_pattern_with_sugar 211 | assert_equal __, hash_pattern_with_sugar(LetterAccountant.new('aaabbc')) 212 | assert_equal __, hash_pattern_with_sugar(LetterAccountant.new('xyz')) 213 | end 214 | 215 | end -------------------------------------------------------------------------------- /src/about_proxy_object_project.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/neo') 2 | 3 | # Project: Create a Proxy Class 4 | # 5 | # In this assignment, create a proxy class (one is started for you 6 | # below). You should be able to initialize the proxy object with any 7 | # object. Any messages sent to the proxy object should be forwarded 8 | # to the target object. As each message is sent, the proxy should 9 | # record the name of the method sent. 10 | # 11 | # The proxy class is started for you. You will need to add a method 12 | # missing handler and any other supporting methods. The specification 13 | # of the Proxy class is given in the AboutProxyObjectProject koan. 14 | 15 | class Proxy 16 | def initialize(target_object) 17 | @object = target_object 18 | # ADD MORE CODE HERE 19 | #-- 20 | @messages = [] 21 | #++ 22 | end 23 | 24 | # WRITE CODE HERE 25 | #-- 26 | attr_reader :messages 27 | 28 | def method_missing(sym, *args, &block) 29 | @messages << sym 30 | @object.send(sym, *args, &block) 31 | end 32 | 33 | def called?(method) 34 | @messages.include?(method) 35 | end 36 | 37 | def number_of_times_called(method) 38 | @messages.select { |m| m == method }.size 39 | end 40 | #++ 41 | end 42 | 43 | # The proxy object should pass the following Koan: 44 | # 45 | class AboutProxyObjectProject < Neo::Koan 46 | def test_proxy_method_returns_wrapped_object 47 | # NOTE: The Television class is defined below 48 | tv = Proxy.new(Television.new) 49 | 50 | # HINT: Proxy class is defined above, may need tweaking... 51 | 52 | assert tv.instance_of?(Proxy) 53 | end 54 | 55 | def test_tv_methods_still_perform_their_function 56 | tv = Proxy.new(Television.new) 57 | 58 | tv.channel = 10 59 | tv.power 60 | 61 | assert_equal 10, tv.channel 62 | assert tv.on? 63 | end 64 | 65 | def test_proxy_records_messages_sent_to_tv 66 | tv = Proxy.new(Television.new) 67 | 68 | tv.power 69 | tv.channel = 10 70 | 71 | assert_equal [:power, :channel=], tv.messages 72 | end 73 | 74 | def test_proxy_handles_invalid_messages 75 | tv = Proxy.new(Television.new) 76 | 77 | assert_raise(NoMethodError) do 78 | tv.no_such_method 79 | end 80 | end 81 | 82 | def test_proxy_reports_methods_have_been_called 83 | tv = Proxy.new(Television.new) 84 | 85 | tv.power 86 | tv.power 87 | 88 | assert tv.called?(:power) 89 | assert ! tv.called?(:channel) 90 | end 91 | 92 | def test_proxy_counts_method_calls 93 | tv = Proxy.new(Television.new) 94 | 95 | tv.power 96 | tv.channel = 48 97 | tv.power 98 | 99 | assert_equal 2, tv.number_of_times_called(:power) 100 | assert_equal 1, tv.number_of_times_called(:channel=) 101 | assert_equal 0, tv.number_of_times_called(:on?) 102 | end 103 | 104 | def test_proxy_can_record_more_than_just_tv_objects 105 | proxy = Proxy.new("Code Mash 2009") 106 | 107 | proxy.upcase! 108 | result = proxy.split 109 | 110 | assert_equal ["CODE", "MASH", "2009"], result 111 | assert_equal [:upcase!, :split], proxy.messages 112 | end 113 | end 114 | 115 | 116 | # ==================================================================== 117 | # The following code is to support the testing of the Proxy class. No 118 | # changes should be necessary to anything below this comment. 119 | 120 | # Example class using in the proxy testing above. 121 | class Television 122 | attr_accessor :channel 123 | 124 | def power 125 | if @power == :on 126 | @power = :off 127 | else 128 | @power = :on 129 | end 130 | end 131 | 132 | def on? 133 | @power == :on 134 | end 135 | end 136 | 137 | # Tests for the Television class. All of theses tests should pass. 138 | class TelevisionTest < Neo::Koan 139 | def test_it_turns_on 140 | tv = Television.new 141 | 142 | tv.power 143 | assert tv.on? 144 | end 145 | 146 | def test_it_also_turns_off 147 | tv = Television.new 148 | 149 | tv.power 150 | tv.power 151 | 152 | assert ! tv.on? 153 | end 154 | 155 | def test_edge_case_on_off 156 | tv = Television.new 157 | 158 | tv.power 159 | tv.power 160 | tv.power 161 | 162 | assert tv.on? 163 | 164 | tv.power 165 | 166 | assert ! tv.on? 167 | end 168 | 169 | def test_can_set_the_channel 170 | tv = Television.new 171 | 172 | tv.channel = 11 173 | assert_equal 11, tv.channel 174 | end 175 | end 176 | -------------------------------------------------------------------------------- /src/about_regular_expressions.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | require File.expand_path(File.dirname(__FILE__) + '/neo') 3 | 4 | class AboutRegularExpressions < Neo::Koan 5 | def test_a_pattern_is_a_regular_expression 6 | assert_equal __(Regexp), /pattern/.class 7 | end 8 | 9 | def test_a_regexp_can_search_a_string_for_matching_content 10 | assert_equal __("match"), "some matching content"[/match/] 11 | end 12 | 13 | def test_a_failed_match_returns_nil 14 | assert_equal __(nil), "some matching content"[/missing/] 15 | end 16 | 17 | # ------------------------------------------------------------------ 18 | 19 | def test_question_mark_means_optional 20 | assert_equal __("ab"), "abbcccddddeeeee"[/ab?/] 21 | assert_equal __("a"), "abbcccddddeeeee"[/az?/] 22 | end 23 | 24 | def test_plus_means_one_or_more 25 | assert_equal __("bccc"), "abbcccddddeeeee"[/bc+/] 26 | end 27 | 28 | def test_asterisk_means_zero_or_more 29 | assert_equal __("abb"), "abbcccddddeeeee"[/ab*/] 30 | assert_equal __("a"), "abbcccddddeeeee"[/az*/] 31 | assert_equal __(""), "abbcccddddeeeee"[/z*/] 32 | 33 | # THINK ABOUT IT: 34 | # 35 | # When would * fail to match? 36 | end 37 | 38 | # THINK ABOUT IT: 39 | # 40 | # We say that the repetition operators above are "greedy." 41 | # 42 | # Why? 43 | 44 | # ------------------------------------------------------------------ 45 | 46 | def test_the_left_most_match_wins 47 | assert_equal __("a"), "abbccc az"[/az*/] 48 | end 49 | 50 | # ------------------------------------------------------------------ 51 | 52 | def test_character_classes_give_options_for_a_character 53 | animals = ["cat", "bat", "rat", "zat"] 54 | assert_equal __(["cat", "bat", "rat"]), animals.select { |a| a[/[cbr]at/] } 55 | end 56 | 57 | def test_slash_d_is_a_shortcut_for_a_digit_character_class 58 | assert_equal __("42"), "the number is 42"[/[0123456789]+/] 59 | assert_equal __("42"), "the number is 42"[/\d+/] 60 | end 61 | 62 | def test_character_classes_can_include_ranges 63 | assert_equal __("42"), "the number is 42"[/[0-9]+/] 64 | end 65 | 66 | def test_slash_s_is_a_shortcut_for_a_whitespace_character_class 67 | assert_equal __(" \t\n"), "space: \t\n"[/\s+/] 68 | end 69 | 70 | def test_slash_w_is_a_shortcut_for_a_word_character_class 71 | # NOTE: This is more like how a programmer might define a word. 72 | assert_equal __("variable_1"), "variable_1 = 42"[/[a-zA-Z0-9_]+/] 73 | assert_equal __("variable_1"), "variable_1 = 42"[/\w+/] 74 | end 75 | 76 | def test_period_is_a_shortcut_for_any_non_newline_character 77 | assert_equal __("abc"), "abc\n123"[/a.+/] 78 | end 79 | 80 | def test_a_character_class_can_be_negated 81 | assert_equal __("the number is "), "the number is 42"[/[^0-9]+/] 82 | end 83 | 84 | def test_shortcut_character_classes_are_negated_with_capitals 85 | assert_equal __("the number is "), "the number is 42"[/\D+/] 86 | assert_equal __("space:"), "space: \t\n"[/\S+/] 87 | # ... a programmer would most likely do 88 | assert_equal __(" = "), "variable_1 = 42"[/[^a-zA-Z0-9_]+/] 89 | assert_equal __(" = "), "variable_1 = 42"[/\W+/] 90 | end 91 | 92 | # ------------------------------------------------------------------ 93 | 94 | def test_slash_a_anchors_to_the_start_of_the_string 95 | assert_equal __("start"), "start end"[/\Astart/] 96 | assert_equal __(nil), "start end"[/\Aend/] 97 | end 98 | 99 | def test_slash_z_anchors_to_the_end_of_the_string 100 | assert_equal __("end"), "start end"[/end\z/] 101 | assert_equal __(nil), "start end"[/start\z/] 102 | end 103 | 104 | def test_caret_anchors_to_the_start_of_lines 105 | assert_equal __("2"), "num 42\n2 lines"[/^\d+/] 106 | end 107 | 108 | def test_dollar_sign_anchors_to_the_end_of_lines 109 | assert_equal __("42"), "2 lines\nnum 42"[/\d+$/] 110 | end 111 | 112 | def test_slash_b_anchors_to_a_word_boundary 113 | assert_equal __("vines"), "bovine vines"[/\bvine./] 114 | end 115 | 116 | # ------------------------------------------------------------------ 117 | 118 | def test_parentheses_group_contents 119 | assert_equal __("hahaha"), "ahahaha"[/(ha)+/] 120 | end 121 | 122 | # ------------------------------------------------------------------ 123 | 124 | def test_parentheses_also_capture_matched_content_by_number 125 | assert_equal __("Gray"), "Gray, James"[/(\w+), (\w+)/, 1] 126 | assert_equal __("James"), "Gray, James"[/(\w+), (\w+)/, 2] 127 | end 128 | 129 | def test_variables_can_also_be_used_to_access_captures 130 | assert_equal __("Gray, James"), "Name: Gray, James"[/(\w+), (\w+)/] 131 | assert_equal __("Gray"), $1 132 | assert_equal __("James"), $2 133 | end 134 | 135 | # ------------------------------------------------------------------ 136 | 137 | def test_a_vertical_pipe_means_or 138 | grays = /(James|Dana|Summer) Gray/ 139 | assert_equal __("James Gray"), "James Gray"[grays] 140 | assert_equal __("Summer"), "Summer Gray"[grays, 1] 141 | assert_equal __(nil), "Jim Gray"[grays, 1] 142 | end 143 | 144 | # THINK ABOUT IT: 145 | # 146 | # Explain the difference between a character class ([...]) and alternation (|). 147 | 148 | # ------------------------------------------------------------------ 149 | 150 | def test_scan_is_like_find_all 151 | assert_equal __(["one", "two", "three"]), "one two-three".scan(/\w+/) 152 | end 153 | 154 | def test_sub_is_like_find_and_replace 155 | assert_equal __("one t-three"), "one two-three".sub(/(t\w*)/) { $1[0, 1] } 156 | end 157 | 158 | def test_gsub_is_like_find_and_replace_all 159 | assert_equal __("one t-t"), "one two-three".gsub(/(t\w*)/) { $1[0, 1] } 160 | end 161 | end 162 | -------------------------------------------------------------------------------- /src/about_sandwich_code.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/neo') 2 | 3 | class AboutSandwichCode < Neo::Koan 4 | 5 | def count_lines(file_name) 6 | file = open(file_name) 7 | count = 0 8 | while file.gets 9 | count += 1 10 | end 11 | count 12 | ensure 13 | file.close if file 14 | end 15 | 16 | def test_counting_lines 17 | assert_equal __(4), count_lines("example_file.txt") 18 | end 19 | 20 | # ------------------------------------------------------------------ 21 | 22 | def find_line(file_name) 23 | file = open(file_name) 24 | while line = file.gets 25 | return line if line.match(/e/) 26 | end 27 | ensure 28 | file.close if file 29 | end 30 | 31 | def test_finding_lines 32 | assert_equal __("test\n"), find_line("example_file.txt") 33 | end 34 | 35 | # ------------------------------------------------------------------ 36 | # THINK ABOUT IT: 37 | # 38 | # The count_lines and find_line are similar, and yet different. 39 | # They both follow the pattern of "sandwich code". 40 | # 41 | # Sandwich code is code that comes in three parts: (1) the top slice 42 | # of bread, (2) the meat, and (3) the bottom slice of bread. The 43 | # bread part of the sandwich almost always goes together, but 44 | # the meat part changes all the time. 45 | # 46 | # Because the changing part of the sandwich code is in the middle, 47 | # abstracting the top and bottom bread slices to a library can be 48 | # difficult in many languages. 49 | # 50 | # (Aside for C++ programmers: The idiom of capturing allocated 51 | # pointers in a smart pointer constructor is an attempt to deal with 52 | # the problem of sandwich code for resource allocation.) 53 | # 54 | # Consider the following code: 55 | # 56 | 57 | def file_sandwich(file_name) 58 | file = open(file_name) 59 | yield(file) 60 | ensure 61 | file.close if file 62 | end 63 | 64 | # Now we write: 65 | 66 | def count_lines2(file_name) 67 | file_sandwich(file_name) do |file| 68 | count = 0 69 | while file.gets 70 | count += 1 71 | end 72 | count 73 | end 74 | end 75 | 76 | def test_counting_lines2 77 | assert_equal __(4), count_lines2("example_file.txt") 78 | end 79 | 80 | # ------------------------------------------------------------------ 81 | 82 | def find_line2(file_name) 83 | # Rewrite find_line using the file_sandwich library function. 84 | #-- 85 | file_sandwich(file_name) do |file| 86 | file.each do |line| 87 | return line if line =~ /e/ 88 | end 89 | end 90 | #++ 91 | end 92 | 93 | def test_finding_lines2 94 | assert_equal __("test\n"), find_line2("example_file.txt") 95 | end 96 | 97 | # ------------------------------------------------------------------ 98 | 99 | def count_lines3(file_name) 100 | open(file_name) do |file| 101 | count = 0 102 | while file.gets 103 | count += 1 104 | end 105 | count 106 | end 107 | end 108 | 109 | def test_open_handles_the_file_sandwich_when_given_a_block 110 | assert_equal __(4), count_lines3("example_file.txt") 111 | end 112 | 113 | end 114 | -------------------------------------------------------------------------------- /src/about_scope.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/neo') 2 | 3 | class AboutScope < Neo::Koan 4 | module Jims 5 | class Dog 6 | def identify 7 | :jims_dog 8 | end 9 | end 10 | end 11 | 12 | module Joes 13 | class Dog 14 | def identify 15 | :joes_dog 16 | end 17 | end 18 | end 19 | 20 | def test_dog_is_not_available_in_the_current_scope 21 | assert_raise(___(NameError)) do 22 | Dog.new 23 | end 24 | end 25 | 26 | def test_you_can_reference_nested_classes_using_the_scope_operator 27 | fido = Jims::Dog.new 28 | rover = Joes::Dog.new 29 | assert_equal __(:jims_dog), fido.identify 30 | assert_equal __(:joes_dog), rover.identify 31 | 32 | assert_equal __(true), fido.class != rover.class 33 | assert_equal __(true), Jims::Dog != Joes::Dog 34 | end 35 | 36 | # ------------------------------------------------------------------ 37 | 38 | class String 39 | end 40 | 41 | def test_bare_bones_class_names_assume_the_current_scope 42 | assert_equal __(true), AboutScope::String == String 43 | end 44 | 45 | def test_nested_string_is_not_the_same_as_the_system_string 46 | assert_equal __(false), String == "HI".class 47 | end 48 | 49 | def test_use_the_prefix_scope_operator_to_force_the_global_scope 50 | assert_equal __(true), ::String == "HI".class 51 | end 52 | 53 | # ------------------------------------------------------------------ 54 | 55 | PI = 3.1416 56 | 57 | def test_constants_are_defined_with_an_initial_uppercase_letter 58 | assert_equal __(3.1416), PI 59 | end 60 | 61 | # ------------------------------------------------------------------ 62 | 63 | MyString = ::String 64 | 65 | def test_class_names_are_just_constants 66 | assert_equal __(true), MyString == ::String 67 | assert_equal __(true), MyString == "HI".class 68 | end 69 | 70 | def test_constants_can_be_looked_up_explicitly 71 | assert_equal __(true), PI == AboutScope.const_get("PI") 72 | assert_equal __(true), MyString == AboutScope.const_get("MyString") 73 | end 74 | 75 | def test_you_can_get_a_list_of_constants_for_any_class_or_module 76 | assert_equal __(["Dog"], [:Dog]), Jims.constants 77 | assert Object.constants.size > _n_(10) 78 | end 79 | end 80 | -------------------------------------------------------------------------------- /src/about_scoring_project.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/neo') 2 | 3 | # Greed is a dice game where you roll up to five dice to accumulate 4 | # points. The following "score" function will be used to calculate the 5 | # score of a single roll of the dice. 6 | # 7 | # A greed roll is scored as follows: 8 | # 9 | # * A set of three ones is 1000 points 10 | # 11 | # * A set of three numbers (other than ones) is worth 100 times the 12 | # number. (e.g. three fives is 500 points). 13 | # 14 | # * A one (that is not part of a set of three) is worth 100 points. 15 | # 16 | # * A five (that is not part of a set of three) is worth 50 points. 17 | # 18 | # * Everything else is worth 0 points. 19 | # 20 | # 21 | # Examples: 22 | # 23 | # score([1,1,1,5,1]) => 1150 points 24 | # score([2,3,4,6,2]) => 0 points 25 | # score([3,4,5,3,3]) => 350 points 26 | # score([1,5,1,2,4]) => 250 points 27 | # 28 | # More scoring examples are given in the tests below: 29 | # 30 | # Your goal is to write the score method. 31 | 32 | def score(dice) 33 | # You need to write this method 34 | #-- 35 | result = 0 36 | (1..6).each do |face| 37 | count = dice.select { |n| n == face }.size 38 | while count > 0 39 | if count >= 3 40 | result += (face == 1) ? 1000 : 100 * face 41 | count -= 3 42 | elsif face == 5 43 | result += count * 50 44 | count = 0 45 | elsif face == 1 46 | result += count * 100 47 | count = 0 48 | else 49 | count = 0 50 | end 51 | end 52 | end 53 | result 54 | #++ 55 | end 56 | 57 | class AboutScoringProject < Neo::Koan 58 | def test_score_of_an_empty_list_is_zero 59 | assert_equal 0, score([]) 60 | end 61 | 62 | def test_score_of_a_single_roll_of_5_is_50 63 | assert_equal 50, score([5]) 64 | end 65 | 66 | def test_score_of_a_single_roll_of_1_is_100 67 | assert_equal 100, score([1]) 68 | end 69 | 70 | def test_score_of_multiple_1s_and_5s_is_the_sum_of_individual_scores 71 | assert_equal 300, score([1,5,5,1]) 72 | end 73 | 74 | def test_score_of_single_2s_3s_4s_and_6s_are_zero 75 | assert_equal 0, score([2,3,4,6]) 76 | end 77 | 78 | def test_score_of_a_triple_1_is_1000 79 | assert_equal 1000, score([1,1,1]) 80 | end 81 | 82 | def test_score_of_other_triples_is_100x 83 | assert_equal 200, score([2,2,2]) 84 | assert_equal 300, score([3,3,3]) 85 | assert_equal 400, score([4,4,4]) 86 | assert_equal 500, score([5,5,5]) 87 | assert_equal 600, score([6,6,6]) 88 | end 89 | 90 | def test_score_of_mixed_is_sum 91 | assert_equal 250, score([2,5,2,2,3]) 92 | assert_equal 550, score([5,5,5,5]) 93 | assert_equal 1100, score([1,1,1,1]) 94 | assert_equal 1200, score([1,1,1,1,1]) 95 | assert_equal 1150, score([1,1,1,5,1]) 96 | end 97 | 98 | end 99 | -------------------------------------------------------------------------------- /src/about_strings.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/neo') 2 | 3 | class AboutStrings < Neo::Koan 4 | def test_double_quoted_strings_are_strings 5 | string = "Hello, World" 6 | assert_equal __(true), string.is_a?(String) 7 | end 8 | 9 | def test_single_quoted_strings_are_also_strings 10 | string = 'Goodbye, World' 11 | assert_equal __(true), string.is_a?(String) 12 | end 13 | 14 | def test_use_single_quotes_to_create_string_with_double_quotes 15 | string = 'He said, "Go Away."' 16 | assert_equal __('He said, "Go Away."'), string 17 | end 18 | 19 | def test_use_double_quotes_to_create_strings_with_single_quotes 20 | string = "Don't" 21 | assert_equal __("Don't"), string 22 | end 23 | 24 | def test_use_backslash_for_those_hard_cases 25 | a = "He said, \"Don't\"" 26 | b = 'He said, "Don\'t"' 27 | assert_equal __(true), a == b 28 | end 29 | 30 | def test_use_flexible_quoting_to_handle_really_hard_cases 31 | a = %(flexible quotes can handle both ' and " characters) 32 | b = %!flexible quotes can handle both ' and " characters! 33 | c = %{flexible quotes can handle both ' and " characters} 34 | assert_equal __(true), a == b 35 | assert_equal __(true), a == c 36 | end 37 | 38 | def test_flexible_quotes_can_handle_multiple_lines 39 | long_string = %{ 40 | It was the best of times, 41 | It was the worst of times. 42 | } 43 | assert_equal __(54), long_string.length 44 | assert_equal __(3), long_string.lines.count 45 | assert_equal __("\n"), long_string[0,1] 46 | end 47 | 48 | def test_here_documents_can_also_handle_multiple_lines 49 | long_string = < 0, :black => 30, :red => 31, 102 | :green => 32, :yellow => 33, :blue => 34, 103 | :magenta => 35, :cyan => 36, 104 | } 105 | 106 | module_function 107 | 108 | COLORS.each do |color, value| 109 | module_eval "def #{color}(string); colorize(string, #{value}); end" 110 | module_function color 111 | end 112 | 113 | def colorize(string, color_value) 114 | if use_colors? 115 | color(color_value) + string + color(COLORS[:clear]) 116 | else 117 | string 118 | end 119 | end 120 | 121 | def color(color_value) 122 | "\e[#{color_value}m" 123 | end 124 | 125 | def use_colors? 126 | return false if ENV['NO_COLOR'] 127 | if ENV['ANSI_COLOR'].nil? 128 | if using_windows? 129 | using_win32console 130 | else 131 | return true 132 | end 133 | else 134 | ENV['ANSI_COLOR'] =~ /^(t|y)/i 135 | end 136 | end 137 | 138 | def using_windows? 139 | File::ALT_SEPARATOR 140 | end 141 | 142 | def using_win32console 143 | defined? Win32::Console 144 | end 145 | end 146 | 147 | module Assertions 148 | FailedAssertionError = Class.new(StandardError) 149 | 150 | def flunk(msg) 151 | raise FailedAssertionError, msg 152 | end 153 | 154 | def assert(condition, msg=nil) 155 | msg ||= "Failed assertion." 156 | flunk(msg) unless condition 157 | true 158 | end 159 | 160 | def assert_equal(expected, actual, msg=nil) 161 | msg ||= "Expected #{expected.inspect} to equal #{actual.inspect}" 162 | assert(expected == actual, msg) 163 | end 164 | 165 | def assert_not_equal(expected, actual, msg=nil) 166 | msg ||= "Expected #{expected.inspect} to not equal #{actual.inspect}" 167 | assert(expected != actual, msg) 168 | end 169 | 170 | def assert_nil(actual, msg=nil) 171 | msg ||= "Expected #{actual.inspect} to be nil" 172 | assert(nil == actual, msg) 173 | end 174 | 175 | def assert_not_nil(actual, msg=nil) 176 | msg ||= "Expected #{actual.inspect} to not be nil" 177 | assert(nil != actual, msg) 178 | end 179 | 180 | def assert_match(pattern, actual, msg=nil) 181 | msg ||= "Expected #{actual.inspect} to match #{pattern.inspect}" 182 | assert pattern =~ actual, msg 183 | end 184 | 185 | def assert_raise(exception) 186 | begin 187 | yield 188 | rescue Exception => ex 189 | expected = ex.is_a?(exception) 190 | assert(expected, "Exception #{exception.inspect} expected, but #{ex.inspect} was raised") 191 | return ex 192 | end 193 | flunk "Exception #{exception.inspect} expected, but nothing raised" 194 | end 195 | 196 | def assert_nothing_raised 197 | begin 198 | yield 199 | rescue Exception => ex 200 | flunk "Expected nothing to be raised, but exception #{exception.inspect} was raised" 201 | end 202 | end 203 | end 204 | 205 | class Sensei 206 | attr_reader :failure, :failed_test, :pass_count 207 | 208 | FailedAssertionError = Assertions::FailedAssertionError 209 | 210 | def initialize 211 | @pass_count = 0 212 | @failure = nil 213 | @failed_test = nil 214 | @observations = [] 215 | end 216 | 217 | PROGRESS_FILE_NAME = '.path_progress' 218 | 219 | def add_progress(prog) 220 | @_contents = nil 221 | exists = File.exist?(PROGRESS_FILE_NAME) 222 | File.open(PROGRESS_FILE_NAME,'a+') do |f| 223 | f.print "#{',' if exists}#{prog}" 224 | end 225 | end 226 | 227 | def progress 228 | if @_contents.nil? 229 | if File.exist?(PROGRESS_FILE_NAME) 230 | File.open(PROGRESS_FILE_NAME,'r') do |f| 231 | @_contents = f.read.to_s.gsub(/\s/,'').split(',') 232 | end 233 | else 234 | @_contents = [] 235 | end 236 | end 237 | @_contents 238 | end 239 | 240 | def observe(step) 241 | if step.passed? 242 | @pass_count += 1 243 | if @pass_count > progress.last.to_i 244 | @observations << Color.green("#{step.koan_file}##{step.name} has expanded your awareness.") 245 | end 246 | else 247 | @failed_test = step 248 | @failure = step.failure 249 | add_progress(@pass_count) 250 | @observations << Color.red("#{step.koan_file}##{step.name} has damaged your karma.") 251 | throw :neo_exit 252 | end 253 | end 254 | 255 | def failed? 256 | ! @failure.nil? 257 | end 258 | 259 | def assert_failed? 260 | failure.is_a?(FailedAssertionError) 261 | end 262 | 263 | def instruct 264 | if failed? 265 | @observations.each{|c| puts c } 266 | encourage 267 | guide_through_error 268 | a_zenlike_statement 269 | show_progress 270 | else 271 | end_screen 272 | end 273 | end 274 | 275 | def show_progress 276 | bar_width = 50 277 | total_tests = Neo::Koan.total_tests 278 | scale = bar_width.to_f/total_tests 279 | print Color.green("your path thus far [") 280 | happy_steps = (pass_count*scale).to_i 281 | happy_steps = 1 if happy_steps == 0 && pass_count > 0 282 | print Color.green('.'*happy_steps) 283 | if failed? 284 | print Color.red('X') 285 | print Color.cyan('_'*(bar_width-1-happy_steps)) 286 | end 287 | print Color.green(']') 288 | print " #{pass_count}/#{total_tests} (#{pass_count*100/total_tests}%)" 289 | puts 290 | end 291 | 292 | def end_screen 293 | if Neo.simple_output 294 | boring_end_screen 295 | else 296 | artistic_end_screen 297 | end 298 | end 299 | 300 | def boring_end_screen 301 | puts "Mountains are again merely mountains" 302 | end 303 | 304 | def artistic_end_screen 305 | "JRuby 1.9.x Koans" 306 | ruby_version = "(in #{'J' if defined?(JRUBY_VERSION)}Ruby #{defined?(JRUBY_VERSION) ? JRUBY_VERSION : RUBY_VERSION})" 307 | ruby_version = ruby_version.side_padding(54) 308 | completed = <<-ENDTEXT 309 | ,, , ,, 310 | : ::::, :::, 311 | , ,,: :::::::::::::,, :::: : , 312 | , ,,, ,:::::::::::::::::::, ,: ,: ,, 313 | :, ::, , , :, ,::::::::::::::::::, ::: ,:::: 314 | : : ::, ,:::::::: ::, ,:::: 315 | , ,::::: :,:::::::,::::, 316 | ,: , ,:,,: ::::::::::::: 317 | ::,: ,,:::, ,::::::::::::, 318 | ,:::, :,,::: ::::::::::::, 319 | ,::: :::::::, Mountains are again merely mountains ,:::::::::::: 320 | :::,,,:::::: :::::::::::: 321 | ,:::::::::::, ::::::::::::, 322 | :::::::::::, ,:::::::::::: 323 | ::::::::::::: ,:::::::::::: 324 | :::::::::::: Ruby Koans :::::::::::: 325 | ::::::::::::#{ ruby_version },:::::::::::: 326 | :::::::::::, , ::::::::::: 327 | ,:::::::::::::, brought to you by ,,:::::::::::: 328 | :::::::::::::: ,:::::::::::: 329 | ::::::::::::::, ,::::::::::::: 330 | ::::::::::::, Neo Software Artisans , :::::::::::: 331 | :,::::::::: :::: ::::::::::::: 332 | ,::::::::::: ,: ,,:::::::::::::, 333 | :::::::::::: ,::::::::::::::, 334 | :::::::::::::::::, :::::::::::::::: 335 | :::::::::::::::::::, :::::::::::::::: 336 | ::::::::::::::::::::::, ,::::,:, , ::::,::: 337 | :::::::::::::::::::::::, ::,: ::,::, ,,: :::: 338 | ,:::::::::::::::::::: ::,, , ,, ,:::: 339 | ,:::::::::::::::: ::,, , ,:::, 340 | ,:::: , ,, 341 | ,,, 342 | ENDTEXT 343 | puts completed 344 | end 345 | 346 | def encourage 347 | puts 348 | puts "The Master says:" 349 | puts Color.cyan(" You have not yet reached enlightenment.") 350 | if ((recents = progress.last(5)) && recents.size == 5 && recents.uniq.size == 1) 351 | puts Color.cyan(" I sense frustration. Do not be afraid to ask for help.") 352 | elsif progress.last(2).size == 2 && progress.last(2).uniq.size == 1 353 | puts Color.cyan(" Do not lose hope.") 354 | elsif progress.last.to_i > 0 355 | puts Color.cyan(" You are progressing. Excellent. #{progress.last} completed.") 356 | end 357 | end 358 | 359 | def guide_through_error 360 | puts 361 | puts "The answers you seek..." 362 | puts Color.red(indent(failure.message).join) 363 | puts 364 | puts "Please meditate on the following code:" 365 | puts embolden_first_line_only(indent(find_interesting_lines(failure.backtrace))) 366 | puts 367 | end 368 | 369 | def embolden_first_line_only(text) 370 | first_line = true 371 | text.collect { |t| 372 | if first_line 373 | first_line = false 374 | Color.red(t) 375 | else 376 | Color.cyan(t) 377 | end 378 | } 379 | end 380 | 381 | def indent(text) 382 | text = text.split(/\n/) if text.is_a?(String) 383 | text.collect{|t| " #{t}"} 384 | end 385 | 386 | def find_interesting_lines(backtrace) 387 | backtrace.reject { |line| 388 | line =~ /neo\.rb/ 389 | } 390 | end 391 | 392 | # Hat's tip to Ara T. Howard for the zen statements from his 393 | # metakoans Ruby Quiz (http://rubyquiz.com/quiz67.html) 394 | def a_zenlike_statement 395 | if !failed? 396 | zen_statement = "Mountains are again merely mountains" 397 | else 398 | zen_statement = case (@pass_count % 10) 399 | when 0 400 | "mountains are merely mountains" 401 | when 1, 2 402 | "learn the rules so you know how to break them properly" 403 | when 3, 4 404 | "remember that silence is sometimes the best answer" 405 | when 5, 6 406 | "sleep is the best meditation" 407 | when 7, 8 408 | "when you lose, don't lose the lesson" 409 | else 410 | "things are not what they appear to be: nor are they otherwise" 411 | end 412 | end 413 | puts Color.green(zen_statement) 414 | end 415 | end 416 | 417 | class Koan 418 | include Assertions 419 | 420 | attr_reader :name, :failure, :koan_count, :step_count, :koan_file 421 | 422 | def initialize(name, koan_file=nil, koan_count=0, step_count=0) 423 | @name = name 424 | @failure = nil 425 | @koan_count = koan_count 426 | @step_count = step_count 427 | @koan_file = koan_file 428 | end 429 | 430 | def passed? 431 | @failure.nil? 432 | end 433 | 434 | def failed(failure) 435 | @failure = failure 436 | end 437 | 438 | def setup 439 | end 440 | 441 | def teardown 442 | end 443 | 444 | def meditate 445 | setup 446 | begin 447 | send(name) 448 | rescue StandardError, Neo::Sensei::FailedAssertionError => ex 449 | failed(ex) 450 | ensure 451 | begin 452 | teardown 453 | rescue StandardError, Neo::Sensei::FailedAssertionError => ex 454 | failed(ex) if passed? 455 | end 456 | end 457 | self 458 | end 459 | 460 | # Class methods for the Neo test suite. 461 | class << self 462 | def inherited(subclass) 463 | subclasses << subclass 464 | end 465 | 466 | def method_added(name) 467 | testmethods << name if !tests_disabled? && Koan.test_pattern =~ name.to_s 468 | end 469 | 470 | def end_of_enlightenment 471 | @tests_disabled = true 472 | end 473 | 474 | def command_line(args) 475 | args.each do |arg| 476 | case arg 477 | when /^-n\/(.*)\/$/ 478 | @test_pattern = Regexp.new($1) 479 | when /^-n(.*)$/ 480 | @test_pattern = Regexp.new(Regexp.quote($1)) 481 | else 482 | if File.exist?(arg) 483 | load(arg) 484 | else 485 | fail "Unknown command line argument '#{arg}'" 486 | end 487 | end 488 | end 489 | end 490 | 491 | # Lazy initialize list of subclasses 492 | def subclasses 493 | @subclasses ||= [] 494 | end 495 | 496 | # Lazy initialize list of test methods. 497 | def testmethods 498 | @test_methods ||= [] 499 | end 500 | 501 | def tests_disabled? 502 | @tests_disabled ||= false 503 | end 504 | 505 | def test_pattern 506 | @test_pattern ||= /^test_/ 507 | end 508 | 509 | def total_tests 510 | self.subclasses.inject(0){|total, k| total + k.testmethods.size } 511 | end 512 | end 513 | end 514 | 515 | class ThePath 516 | def walk 517 | sensei = Neo::Sensei.new 518 | each_step do |step| 519 | sensei.observe(step.meditate) 520 | end 521 | sensei.instruct 522 | end 523 | 524 | def each_step 525 | catch(:neo_exit) { 526 | step_count = 0 527 | Neo::Koan.subclasses.each_with_index do |koan,koan_index| 528 | koan.testmethods.each do |method_name| 529 | step = koan.new(method_name, koan.to_s, koan_index+1, step_count+=1) 530 | yield step 531 | end 532 | end 533 | } 534 | end 535 | end 536 | end 537 | 538 | END { 539 | Neo::Koan.command_line(ARGV) 540 | Neo::ThePath.new.walk 541 | } 542 | -------------------------------------------------------------------------------- /src/path_to_enlightenment.rb: -------------------------------------------------------------------------------- 1 | # The path to Ruby Enlightenment starts with the following: 2 | 3 | $LOAD_PATH << File.dirname(__FILE__) 4 | 5 | require 'about_asserts' 6 | require 'about_true_and_false' 7 | require 'about_strings' 8 | require 'about_symbols' 9 | require 'about_arrays' 10 | require 'about_array_assignment' 11 | require 'about_objects' 12 | require 'about_nil' 13 | require 'about_hashes' 14 | require 'about_methods' 15 | in_ruby_version("2", "3") do 16 | require 'about_keyword_arguments' 17 | end 18 | require 'about_constants' 19 | require 'about_regular_expressions' 20 | require 'about_control_statements' 21 | require 'about_triangle_project' 22 | require 'about_exceptions' 23 | require 'about_triangle_project_2' 24 | require 'about_iteration' 25 | require 'about_blocks' 26 | require 'about_sandwich_code' 27 | require 'about_scoring_project' 28 | require 'about_classes' 29 | require 'about_open_classes' 30 | require 'about_dice_project' 31 | require 'about_inheritance' 32 | require 'about_modules' 33 | require 'about_scope' 34 | require 'about_class_methods' 35 | require 'about_message_passing' 36 | require 'about_proxy_object_project' 37 | require 'about_to_str' 38 | in_ruby_version("jruby") do 39 | require 'about_java_interop' 40 | end 41 | in_ruby_version("2.7", "3") do 42 | require 'about_pattern_matching' 43 | end 44 | require 'about_extra_credit' 45 | -------------------------------------------------------------------------------- /src/triangle.rb: -------------------------------------------------------------------------------- 1 | # Triangle Project Code. 2 | 3 | # Triangle analyzes the lengths of the sides of a triangle 4 | # (represented by a, b and c) and returns the type of triangle. 5 | # 6 | # It returns: 7 | # :equilateral if all sides are equal 8 | # :isosceles if exactly 2 sides are equal 9 | # :scalene if no sides are equal 10 | # 11 | # The tests for this method can be found in 12 | # about_triangle_project.rb 13 | # and 14 | # about_triangle_project_2.rb 15 | # 16 | def triangle(a, b, c) 17 | # WRITE THIS CODE 18 | #-- 19 | a, b, c = [a, b, c].sort 20 | fail TriangleError if (a+b) <= c 21 | sides = [a, b, c].uniq 22 | [nil, :equilateral, :isosceles, :scalene][sides.size] 23 | #++ 24 | end 25 | 26 | # Error class used in part 2. No need to change this code. 27 | class TriangleError < StandardError 28 | end 29 | -------------------------------------------------------------------------------- /tests/check_test.rb: -------------------------------------------------------------------------------- 1 | require_relative "test_helper" 2 | 3 | class CheckTest < Minitest::Test 4 | def with_captured_stdout 5 | original_stdout = $stdout 6 | $stdout = StringIO.new 7 | yield 8 | $stdout.string 9 | ensure 10 | $stdout = original_stdout 11 | end 12 | 13 | def test_check_asserts 14 | output = with_captured_stdout do 15 | Rake::Task['check:asserts'].invoke 16 | end 17 | assert_match(/OK/, output) 18 | end 19 | 20 | def test_check_abouts 21 | output = with_captured_stdout do 22 | Rake::Task['check:abouts'].invoke 23 | end 24 | assert_match(/OK/, output) 25 | end 26 | end -------------------------------------------------------------------------------- /tests/test_helper.rb: -------------------------------------------------------------------------------- 1 | require "minitest/autorun" 2 | require "rake" 3 | 4 | Rake.application.load_rakefile --------------------------------------------------------------------------------