diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..f30bc80f3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +dist +.project_env.rc +.path_progress +.rvmrc +.ruby-version +*.rbc +koans/* +*~ diff --git a/DEPLOYING b/DEPLOYING new file mode 100644 index 000000000..3b91dc39f --- /dev/null +++ b/DEPLOYING @@ -0,0 +1,12 @@ += Deploying a new Ruby Koans ZIP file + +The "Download" button on the rubykoans.com web-site points to the +download/rubykoans.zip file in the github repository. So to update the +download target on the web-site, just rebuild the .zip file, commit +and push the changes. + + rake package + git add download + git push + +That's it. \ No newline at end of file diff --git a/MIT-LICENSE b/MIT-LICENSE deleted file mode 100644 index b243e8cf6..000000000 --- a/MIT-LICENSE +++ /dev/null @@ -1,20 +0,0 @@ -Copyright (c) 2009 EdgeCase - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/README.rdoc b/README.rdoc index 5fafe07dc..590854799 100644 --- a/README.rdoc +++ b/README.rdoc @@ -1,123 +1,175 @@ -= EdgeCase Ruby Koans += Neo Ruby Koans -The Ruby Koans walk you along the path to enlightenment in order to learn Ruby. -The goal is to learn the Ruby language, syntax, structure, and some common -functions and libraries. We also teach you culture. Testing is not just something we -pay lip service to, but something we live. It is essential in your quest to learn -and do great things in the language. +The Ruby Koans walk you along the path to enlightenment in order to learn Ruby. +The goal is to learn the Ruby language, syntax, structure, and some common +functions and libraries. We also teach you culture by basing the koans on tests. +Testing is not just something we pay lip service to, but something we +live. Testing is essential in your quest to learn and do great things in Ruby. == The Structure -The koans are broken out into areas by file, hashes are covered in about_hashes.rb, -modules are introduced in about_modules.rb, etc. They are presented in order in the -path_to_enlightenment.rb file. +The koans are broken out into areas by file, hashes are covered in +about_hashes.rb+, +modules are introduced in +about_modules.rb+, etc. They are presented in +order in the +path_to_enlightenment.rb+ file. -Each koan builds up your knowledge of Ruby and builds upon itself. It will stop at -the first place you need to correct. +Each koan builds up your knowledge of Ruby and builds upon itself. It will stop at +the first place you need to correct. -Some koans simply need to have the correct answer substituted for an incorrect one. -Some, however, require you to supply your own answer. If you see the method +__+ (a -double underscore) listed, it is a hint to you to supply your own code in order to -make it work correctly. +Some koans simply need to have the correct answer substituted for an incorrect one. +Some, however, require you to supply your own answer. If you see the method +__+ (a +double underscore) listed, it is a hint to you to supply your own code in order to +make it work correctly. == Installing Ruby -If you do not have Ruby setup, please visit http://ruby-lang.org/en/downloads/ for -operating specific instructions. In order to run this you need ruby and rake -installed. To check the installations simply type: +If you do not have Ruby setup, please visit http://ruby-lang.org/en/downloads/ for +operating specific instructions. In order to run the koans you need +ruby+ and ++rake+ installed. To check your installations simply type: *nix platforms from any terminal window: [~] $ ruby --version [~] $ rake --version -Windows from the command prompt (cmd.exe) +Windows from the command prompt (+cmd.exe+) - c:\ruby --version + c:\ruby --version c:\rake --version - -Any response for Ruby with a version number greater than 1.8 is fine (should be -around 1.8.6 or more). Any version of rake will do. + +If you don't have +rake+ installed, just run gem install rake + +Any response for Ruby with a version number greater than 1.8 is fine (should be +around 1.8.6 or more). Any version of +rake+ will do. + +== Generating the Koans + +A fresh checkout will not include the koans, you will need to generate +them. + + [ruby_koans] $ rake gen # generates the koans directory + +If you need to regenerate the koans, thus wiping your current `koans`, + + [ruby_koans] $ rake regen # regenerates the koans directory, wiping the original == The Path To Enlightenment -You can run the tests through rake or by calling the file itself (rake is the +You can run the tests through +rake+ or by calling the file itself (+rake+ is the recommended way to run them as we might build more functionality into this task). -*nix platforms, from the koans directory +*nix platforms, from the +ruby_koans+ directory [ruby_koans] $ rake # runs the default target :walk_the_path [ruby_koans] $ ruby path_to_enlightenment.rb # simply call the file directly - + Windows is the same thing c:\ruby_koans\rake # runs the default target :walk_the_path c:\ruby_koans\ruby path_to_enlightenment.rb # simply call the file directly - + === Red, Green, Refactor -In test-driven development the mantra has always been, red, green, refactor. Write a -failing test and run it (red), make the test pass (green), then refactor it (that is -look at the code and see if you can make it any better. In this case you will need -to run the koan and see it fail (refactor), make the test pass (green), then take a -moment and reflect upon the test to see what it is teaching you. +In test-driven development the mantra has always been red, green, refactor. +Write a failing test and run it (red), make the test pass (green), +then look at the code and consider if you can make it any better (refactor). + +While walking the path to Ruby enlightenment you will need to run the koan and +see it fail (red), make the test pass (green), then take a moment +and reflect upon the test to see what it is teaching you and improve the code to +better communicate its intent (refactor). -The very first time you run it you will see the following output: +The very first time you run the koans you will see the following output: [ ruby_koans ] $ rake (in /Users/person/dev/ruby_koans) - cd koans - - Thinking AboutAsserts - test_assert_truth has damaged your karma. - - You have not yet reached enlightenment ... + /usr/bin/ruby1.8 path_to_enlightenment.rb + + AboutAsserts#test_assert_truth has damaged your karma. + + The Master says: + You have not yet reached enlightenment. + + The answers you seek... is not true. - + Please meditate on the following code: - ./about_basics.rb:10:in `test_assert_truth' - path_to_enlightenment.rb:27 + ./about_asserts.rb:10:in `test_assert_truth' + path_to_enlightenment.rb:38:in `each_with_index' + path_to_enlightenment.rb:38 mountains are merely mountains - -You have come to your first stage. If you notice it is telling you where to look for + your path thus far [X_________________________________________________] 0/280 + +You have come to your first stage. Notice it is telling you where to look for the first solution: Please meditate on the following code: - ./about_basics.rb:10:in `test_assert_truth' - path_to_enlightenment.rb:27 - -We then open up the about_basics.rb file and look at the first test: + ./about_asserts.rb:10:in `test_assert_truth' + path_to_enlightenment.rb:38:in `each_with_index' + path_to_enlightenment.rb:38 + +Open the +about_asserts.rb+ file and look at the first test: # We shall contemplate truth by testing reality, via asserts. def test_assert_truth assert false # This should be true end -We then change the +false+ to +true+ and run the test again. After you are +Change the +false+ to +true+ and re-run the test. After you are done, think about what you are learning. In this case, ignore everything except -the method name (+test_assert_truth+) and the parts inside the method (everything -before the +end+). - -In this case the goal is for you to see that if you pass a value to the +assert+ -method, it will either ensure it is +true+ and continue on, or fail if in fact -the statement is +false+. - +the method name (+test_assert_truth+) and the parts inside the method (everything +before the +end+). + +In this case the goal is for you to see that if you pass a value to the +assert+ +method, it will either ensure it is +true+ and continue on, or fail if +the statement is +false+. + +=== Running the Koans automatically + +This section is optional. + +Normally the path to enlightenment looks like this: + + cd ruby_koans + rake + # edit + rake + # edit + rake + # etc + +If you prefer, you can keep the koans running in the background so that after you +make a change in your editor, the koans will immediately run again. This will +hopefully keep your focus on learning Ruby instead of on the command line. + +Install the Ruby gem (library) called +watchr+ and then ask it to +"watch" the koans for changes: + + cd ruby_koans + rake + # decide to run rake automatically from now on as you edit + gem install watchr + watchr ./koans/koans.watchr + == Inspiration -A special thanks to Mike Clark and Ara Howard for inspiring this project. Mike Clark -wrote an excellent blog post about learning Ruby through unit testing. This sparked -an idea that has taken a bit to solidify, that of bringing new rubyists into the -community through testing. Ara Howard then gave us the idea for the Koans in his -ruby quiz entry an Meta Koans (a must for any rubyist wanting to improve their skills). +A special thanks to Mike Clark and Ara Howard for inspiring this +project. Mike Clark wrote an excellent blog post about learning Ruby +through unit testing. This sparked an idea that has taken a bit to +solidify, that of bringing new rubyists into the community through +testing. Ara Howard then gave us the idea for the Koans in his ruby +quiz entry on Meta Koans (a must for any rubyist wanting to improve +their skills). Also, "The Little Lisper" taught us all the value of +the short questions/simple answers style of learning. Mike Clark's post :: http://www.clarkware.com/cgi/blosxom/2005/03/18 Meta Koans :: http://rubyquiz.com/quiz67.html +The Little Lisper :: http://www.amazon.com/Little-LISPer-Third-Daniel-Friedman/dp/0023397632 == Other Resources The Ruby Language :: http://ruby-lang.org -Try Ruby in your browser :: http://tryruby.hobix.com/ +Try Ruby in your browser :: http://tryruby.org Dave Thomas' introduction to Ruby Programming Ruby (the Pick Axe) :: http://pragprog.com/titles/ruby/programming-ruby @@ -125,8 +177,15 @@ Brian Marick's fantastic guide for beginners Everyday Scripting with Ruby :: = Other stuff -Author:: Jim Weirich -Author:: Joe O'Brien -Requires:: Ruby 1.8.x or later and Rake (any version) +Author :: Jim Weirich +Author :: Joe O'Brien +Issue Tracker :: http://www.pivotaltracker.com/projects/48111 +Requires :: Ruby 1.8.x or later and Rake (any recent version) + += License +http://i.creativecommons.org/l/by-nc-sa/3.0/88x31.png +RubyKoans is released under a Creative Commons, +Attribution-NonCommercial-ShareAlike, Version 3.0 +(http://creativecommons.org/licenses/by-nc-sa/3.0/) License. diff --git a/Rakefile b/Rakefile old mode 100644 new mode 100755 index 0c07899f0..45f5df42c --- a/Rakefile +++ b/Rakefile @@ -1,14 +1,159 @@ -require 'rubygems' -require 'rake/rdoctask' +#!/usr/bin/env ruby +# -*- ruby -*- + +require 'rake/clean' + +SRC_DIR = 'src' +PROB_DIR = 'koans' +DOWNLOAD_DIR = 'download' + +SRC_FILES = FileList["#{SRC_DIR}/*"] +KOAN_FILES = SRC_FILES.pathmap("#{PROB_DIR}/%f") + +ZIP_FILE = "#{DOWNLOAD_DIR}/rubykoans.zip" + +CLEAN.include("**/*.rbc") + +module Koans + extend Rake::DSL if defined?(Rake::DSL) + + # Remove solution info from source + # __(a,b) => __ + # _n_(number) => __ + # # __ => + def Koans.remove_solution(line) + line = line.gsub(/\b____\([^\)]+\)/, "____") + line = line.gsub(/\b___\([^\)]+\)/, "___") + line = line.gsub(/\b__\([^\)]+\)/, "__") + line = line.gsub(/\b_n_\([^\)]+\)/, "_n_") + line = line.gsub(%r(/\#\{__\}/), "/__/") + line = line.gsub(/\s*#\s*__\s*$/, '') + line + end + + def Koans.make_koan_file(infile, outfile) + if infile =~ /neo/ + cp infile, outfile + else + open(infile) do |ins| + open(outfile, "w") do |outs| + state = :copy + ins.each do |line| + state = :skip if line =~ /^ *#--/ + case state + when :copy + outs.puts remove_solution(line) + else + # do nothing + end + state = :copy if line =~ /^ *#\+\+/ + end + end + end + end + end +end + +module RubyImpls + # Calculate the list of relevant Ruby implementations. + def self.find_ruby_impls + rubys = `rvm list`.gsub(/=>/,'').split(/\n/).map { |x| x.strip }.reject { |x| x.empty? || x =~ /^rvm/ }.sort + expected.map { |impl| + last = rubys.grep(Regexp.new(Regexp.quote(impl))).last + last ? last.split.first : nil + }.compact + end + + # Return a (cached) list of relevant Ruby implementations. + def self.list + @list ||= find_ruby_impls + end + + # List of expected ruby implementations. + def self.expected + %w(ruby-1.8.7 ruby-1.9.2 jruby ree) + end +end task :default => :walk_the_path task :walk_the_path do - cd 'koans' + cd PROB_DIR ruby 'path_to_enlightenment.rb' end -Rake::RDocTask.new do |rd| - rd.main = "README.rdoc" - rd.rdoc_files.include("README.rdoc", "koans/*.rb") +directory DOWNLOAD_DIR +directory PROB_DIR + +desc "(re)Build zip file" +task :zip => [:clobber_zip, :package] + +task :clobber_zip do + rm ZIP_FILE +end + +file ZIP_FILE => KOAN_FILES + [DOWNLOAD_DIR] do + sh "zip #{ZIP_FILE} #{PROB_DIR}/*" +end + +desc "Create packaged files for distribution" +task :package => [ZIP_FILE] + +desc "Upload the package files to the web server" +task :upload => [ZIP_FILE] do + sh "scp #{ZIP_FILE} linode:sites/onestepback.org/download" +end + +desc "Generate the Koans from the source files from scratch." +task :regen => [:clobber_koans, :gen] + +desc "Generate the Koans from the changed source files." +task :gen => KOAN_FILES + [PROB_DIR + "/README.rdoc"] +task :clobber_koans do + rm_r PROB_DIR +end + +file PROB_DIR + "/README.rdoc" => "README.rdoc" do |t| + cp "README.rdoc", t.name +end + +SRC_FILES.each do |koan_src| + file koan_src.pathmap("#{PROB_DIR}/%f") => [PROB_DIR, koan_src] do |t| + Koans.make_koan_file koan_src, t.name + end +end + +task :run do + puts 'koans' + Dir.chdir("#{SRC_DIR}") do + puts "in #{Dir.pwd}" + sh "ruby path_to_enlightenment.rb" + end +end + + +desc "Pre-checkin tests (=> run_all)" +task :cruise => :run_all + +desc "Run the completed koans againts a list of relevant Ruby Implementations" +task :run_all do + results = [] + RubyImpls.list.each do |impl| + puts "=" * 40 + puts "On Ruby #{impl}" + sh ". rvm #{impl}; rake run" + results << [impl, "RAN"] + puts + end + puts "=" * 40 + puts "Summary:" + puts + results.each do |impl, res| + puts "#{impl} => RAN" + end + puts + RubyImpls.expected.each do |requested_impl| + impl_pattern = Regexp.new(Regexp.quote(requested_impl)) + puts "No Results for #{requested_impl}" unless results.detect { |x| x.first =~ impl_pattern } + end end diff --git a/download/rubykoans.zip b/download/rubykoans.zip new file mode 100644 index 000000000..60b497b0f Binary files /dev/null and b/download/rubykoans.zip differ diff --git a/keynote/RubyKoans.key b/keynote/RubyKoans.key new file mode 100644 index 000000000..a9f00ac95 Binary files /dev/null and b/keynote/RubyKoans.key differ diff --git a/koans/about_array_assignment.rb b/koans/about_array_assignment.rb deleted file mode 100644 index 37b1068c5..000000000 --- a/koans/about_array_assignment.rb +++ /dev/null @@ -1,38 +0,0 @@ -require 'edgecase' - -class AboutArrayAssignment < EdgeCase::Koan - def test_non_parallel_assignment - names = ["John", "Smith"] - assert_equal __, names - end - - def test_parallel_assignments - first_name, last_name = ["John", "Smith"] - assert_equal __, first_name - assert_equal __, last_name - end - - def test_parallel_assignments_with_extra_values - first_name, last_name = ["John", "Smith", "III"] - assert_equal __, first_name - assert_equal __, last_name - end - - def test_parallel_assignments_with_extra_variables - first_name, last_name = ["Cher"] - assert_equal __, first_name - assert_equal __, last_name - end - - def test_parallel_assignements_with_subarrays - first_name, last_name = [["Willie", "Rae"], "Johnson"] - assert_equal __, first_name - assert_equal __, last_name - end - - def test_parallel_assignment_with_one_variable - first_name, = ["John", "Smith"] - assert_equal __, first_name - end - -end diff --git a/koans/about_arrays.rb b/koans/about_arrays.rb deleted file mode 100644 index 5fd016af5..000000000 --- a/koans/about_arrays.rb +++ /dev/null @@ -1,101 +0,0 @@ -require 'edgecase' - -class AboutArrays < EdgeCase::Koan - def test_creating_arrays - empty_array = Array.new - assert_equal Array, empty_array.class - assert_equal __, empty_array.size - end - - def test_array_literals - array = Array.new - assert_equal [], array - - array[0] = 1 - assert_equal [1], array - - array[1] = 2 - assert_equal [1, __], array - - array << 333 - assert_equal __, array - end - - def test_accessing_array_elements - array = [:peanut, :butter, :and, :jelly] - - assert_equal __, array[0] - assert_equal __, array.first - assert_equal __, array[3] - assert_equal __, array.last - assert_equal __, array[-1] - assert_equal __, array[-3] - end - - def test_slicing_arrays - array = [:peanut, :butter, :and, :jelly] - - assert_equal __, array[0,1] - assert_equal __, array[0,2] - assert_equal __, array[2,2] - assert_equal __, array[2,20] - assert_equal __, array[4,0] - assert_equal __, array[5,0] - end - - def test_arrays_and_ranges - assert_equal Range, (1..5).class - assert_not_equal [1,2,3,4,5], (1..5) - assert_equal [1,2,3,4,5], (1..5).to_a - assert_equal __, (1...5).to_a - end - - def test_slicing_with_ranges - array = [:peanut, :butter, :and, :jelly] - - assert_equal __, array[0..2] - assert_equal __, array[0...2] - assert_equal __, array[2..-1] - end - - def test_pushing_and_popping_arrays - array = [1,2] - array.push(:last) - - assert_equal __, array - - popped_value = array.pop - assert_equal __, popped_value - assert_equal __, array - end - - def test_shifting_arrays - array = [1,2] - array.unshift(:first) - - assert_equal __, array - - shifted_value = array.shift - assert_equal __, shifted_value - assert_equal __, array - end - - def test_parallel_assignments - first_name, last_name = ["John", "Smith"] - assert_equal __, first_name - assert_equal __, last_name - end - - def test_parallel_assignments_with_extra_values - first_name, last_name = ["John", "Smith", "III"] - assert_equal __, first_name - assert_equal __, last_name - end - - def test_parallel_assignments_with_extra_variables - first_name, last_name = ["Cher"] - assert_equal __, first_name - assert_equal __, last_name - end - -end diff --git a/koans/about_exceptions.rb b/koans/about_exceptions.rb deleted file mode 100644 index b2843b118..000000000 --- a/koans/about_exceptions.rb +++ /dev/null @@ -1,60 +0,0 @@ -require 'edgecase' - -class AboutExceptions < EdgeCase::Koan - - class MySpecialError < RuntimeError - end - - def test_exceptions_inherit_from_Exception - assert MySpecialError.ancestors.include?(RuntimeError) - assert MySpecialError.ancestors.include?(StandardError) - assert MySpecialError.ancestors.include?(Exception) - assert MySpecialError.ancestors.include?(Object) - end - - def test_rescue_clause - result = nil - begin - fail "Oops" - rescue StandardError => ex - result = :exception_handled - end - - assert_equal __, result - - assert ex.is_a?(StandardError), "Failure message." - assert ex.is_a?(RuntimeError), "Failure message." - - assert RuntimeError.ancestors.include?(StandardError), - "RuntimeError is a subclass of StandardError" - - assert_equal __, ex.message - end - - def test_raising_a_particular_error - result = nil - begin - # 'raise' and 'fail' are synonyms - raise MySpecialError, "My Message" - rescue MySpecialError => ex - result = :exception_handled - end - - assert_equal __(:exception_handled), result - assert_equal __, ex.message - end - - def test_ensure_clause - result = nil - begin - fail "Oops" - rescue StandardError => ex - # no code here - ensure - result = :always_run - end - - assert_equal __, result - end - -end diff --git a/koans/about_hashes.rb b/koans/about_hashes.rb deleted file mode 100644 index 6208f3879..000000000 --- a/koans/about_hashes.rb +++ /dev/null @@ -1,56 +0,0 @@ -require 'edgecase' - -class AboutHashes < EdgeCase::Koan - def test_creating_hashes - empty_hash = Hash.new - assert_equal Hash, empty_hash.class - assert_equal({}, empty_hash) - assert_equal __, empty_hash.size - end - - def test_hash_literals - hash = { :one => "uno", :two => "dos" } - assert_equal __, hash.size - end - - def test_accessing_hashes - hash = { :one => "uno", :two => "dos" } - assert_equal __, hash[:one] - assert_equal __, hash[:two] - assert_equal __, hash[:doesnt_exist] - end - - def test_changing_hashes - hash = { :one => "uno", :two => "dos" } - hash[:one] = "eins" - - expected = { :one => __, :two => "dos" } - assert_equal expected, hash - - # Bonus Question: Why was "expected" broken out into a variable - # rather than used as a literal? - end - - def test_hash_is_unordered - hash1 = { :one => "uno", :two => "dos" } - hash2 = { :two => "dos", :one => "uno" } - - assert_equal hash1, hash2 - end - - def test_hash_keys_and_values - hash = { :one => "uno", :two => "dos" } - assert_equal __, hash.keys - assert_equal __, hash.values - end - - def test_combining_hashes - hash = { "jim" => 53, "amy" => 20, "dan" => 23 } - new_hash = hash.merge({ "jim" => 54, "jenny" => 26 }) - - assert_not_equal hash, new_hash - - expected = { "jim" => __, "amy" => 20, "dan" => 23, "jenny" => __ } - assert_equal expected, new_hash - end -end diff --git a/koans/about_iteration.rb b/koans/about_iteration.rb deleted file mode 100644 index 01808dcdb..000000000 --- a/koans/about_iteration.rb +++ /dev/null @@ -1,93 +0,0 @@ -require 'edgecase' - -class AboutIteration < EdgeCase::Koan - - def test_each_is_a_method_on_arrays - [].methods.include?("each") - end - - def test_iterating_with_each - array = [1, 2, 3] - sum = 0 - array.each do |item| - sum += item - end - assert_equal 6, sum - end - - def test_each_can_use_curly_brace_blocks_too - array = [1, 2, 3] - sum = 0 - array.each { |item| - sum += item - } - assert_equal __, sum - end - - def test_break_works_with_each_style_iterations - array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] - sum = 0 - array.each { |item| - break if item > 3 - sum += item - } - assert_equal __, sum - end - - def test_collect_transforms_elements_of_an_array - array = [1, 2, 3] - new_array = array.collect { |item| item + 10 } - assert_equal __, new_array - - # NOTE: 'map' is another name for the 'collect' operation - another_array = array.map { |item| item + 10 } - assert_equal __, another_array - end - - def test_select_selects_certain_items_from_an_array - array = [1, 2, 3, 4, 5, 6] - - even_numbers = array.select { |item| (item % 2) == 0 } - assert_equal __, even_numbers - - # NOTE: 'find_all' is another name for the 'select' operation - more_even_numbers = array.find_all { |item| (item % 2) == 0 } - assert_equal __, more_even_numbers - end - - def test_find_locates_the_first_element_matching_a_criteria - array = ["Jim", "Bill", "Clarence", "Doug", "Eli"] - - assert_equal __, array.find { |item| item.size > 4 } - end - - def test_inject_will_blow_your_mind - result = [2, 3, 4].inject(0) { |sum, item| sum + item } - assert_equal __, result - - result2 = [2, 3, 4].inject(1) { |sum, item| sum * item } - assert_equal __, result2 - - # Extra Credit: - # Describe in your own words what inject does. - end - - def test_all_iteration_methods_work_on_any_collection_not_just_arrays - # Ranges act like a collection - result = (1..3).map { |item| item + 10 } - assert_equal __, result - - # Files act like a collection of lines - file = File.open("example_file.txt") - upcase_lines = file.map { |line| line.strip.upcase } - assert_equal __, upcase_lines - - # NOTE: You can create your own collections that work with each, - # map, select, etc. - ensure - # Arg, this is ugly. - # We will figure out how to fix this later. - file.close if file - end - -end diff --git a/koans/about_nil.rb b/koans/about_nil.rb deleted file mode 100644 index b007cc0c2..000000000 --- a/koans/about_nil.rb +++ /dev/null @@ -1,46 +0,0 @@ -require 'edgecase' - -class AboutNil < EdgeCase::Koan - def test_nil_is_an_object - assert nil.is_a?(Object), "Unlike NULL in other languages" - end - - def test_you_dont_get_null_pointer_errors_when_calling_methods_on_nil - # - # What is the Exception that is thrown when you call a method that - # does not exist? - # - # Hint: launch irb and try the code in the block below. - # - # Don't be confused by the code below yet. It's using blocks - # which are explained later on in about_blocks.rb. For now, - # think about it like running nil.some_method_nil_doesnt_know_about - # in a sandbox and catching the error class into the exception - # variable. - # - exception = assert_raise(___) do - nil.some_method_nil_doesnt_know_about - end - - # - # What is the error message itself? What substring or pattern could - # you test against in order to have a good idea what the string is? - # - assert_match /__/, exception.message - end - - def test_nil_has_a_few_methods_defined_on_it - assert_equal __, nil.nil? - assert_equal __, nil.to_s - assert_equal __, nil.inspect - - # THINK ABOUT IT: - # - # Is it better to use - # obj.nil? - # or - # obj == nil - # Why? - end - -end diff --git a/koans/about_triangle_project_2.rb b/koans/about_triangle_project_2.rb deleted file mode 100644 index f9e472815..000000000 --- a/koans/about_triangle_project_2.rb +++ /dev/null @@ -1,15 +0,0 @@ -require 'edgecase' - -# You need to write the triangle method in the file 'triangle.rb' -require 'triangle.rb' - -class AboutTriangleAssignment < EdgeCase::Koan - # The first assignment did not talk about how to handle errors. - # Let's handle that part now. - def test_illegal_triangles_throw_exceptions - assert_raise(TriangleError) do triangle(0, 0, 0) end - assert_raise(TriangleError) do triangle(3, 4, -5) end - assert_raise(TriangleError) do triangle(2, 4, 2) end - end -end - diff --git a/koans/about_true_and_false.rb b/koans/about_true_and_false.rb deleted file mode 100644 index 51922516e..000000000 --- a/koans/about_true_and_false.rb +++ /dev/null @@ -1,33 +0,0 @@ -require 'edgecase' - -class AboutTrueAndFalse < EdgeCase::Koan - def truth_value(condition) - if condition - :true_stuff - else - :false_stuff - end - end - - def test_true_is_treated_as_true - assert_equal __, truth_value(true) - end - - def test_false_is_treated_as_false - assert_equal __, truth_value(false) - end - - def test_nil_is_treated_as_false_too - assert_equal __, truth_value(nil) - end - - def test_everything_else_is_treated_as_true - assert_equal __, truth_value(1) - assert_equal __, truth_value(0) - assert_equal __, truth_value([]) - assert_equal __, truth_value({}) - assert_equal __, truth_value("Strings") - assert_equal __, truth_value("") - end - -end diff --git a/koans/array_test.rb b/koans/array_test.rb deleted file mode 100644 index 83ec07b46..000000000 --- a/koans/array_test.rb +++ /dev/null @@ -1,47 +0,0 @@ -require 'test_helper' - -class ArrayTest < EdgeCase::TestCase - - def test_basic_arrays - food = [:peanut, :button, :and, :jelly] - assert_equal __, food[0] - assert_equal __, food.size - end - - def test_array_access - food = [:peanut, :button, :and, :jelly] - assert_equal __, food.first - assert_equal __, food.last - assert_equal __, food[0] - assert_equal __, food[2] - assert_equal __, food[(food.size() - 1)] - end - - def test_arrays_with_other_objects - food = [:peanut, :button, :and, :jelly, 1, nil] - assert_equal __, food.size - assert_equal __, food.last - assert_equal __, food[5] - end - - def test_adding_to_an_array_with_shovel_shovel - food = [:peanut, :button, :and, :jelly] - food << 'sandwich' - assert_equal __, food.size - assert_equal __, food.first - end - - def test_adding_to_an_array_with_push - food = [:peanut, :button, :and, :jelly] - food.push('sandwich') - assert_equal __, food.last - end - - def test_adding_to_an_array_with_unshift - food = [:peanut, :button, :and, :jelly] - food.unshift('a') - assert_equal __, food.first - end - -end - diff --git a/koans/code_mash.rb b/koans/code_mash.rb deleted file mode 100644 index fe089a5ef..000000000 --- a/koans/code_mash.rb +++ /dev/null @@ -1 +0,0 @@ -require 'edgecase' \ No newline at end of file diff --git a/koans/edgecase.rb b/koans/edgecase.rb deleted file mode 100644 index 7ab429e9d..000000000 --- a/koans/edgecase.rb +++ /dev/null @@ -1,208 +0,0 @@ -#!/usr/bin/env ruby -# -*- ruby -*- - -require 'test/unit/assertions' - -class FillMeInError < StandardError -end - -def __(value="FILL ME IN") - value -end - -def ___(value=FillMeInError) - value -end - -module EdgeCase - class Sensei - attr_reader :failure, :failed_test - - AssertionError = Test::Unit::AssertionFailedError - - def initialize - @pass_count = 0 - @failure = nil - @failed_test = nil - end - - def accumulate(test) - if test.passed? - @pass_count += 1 - puts " #{test.name} has expanded your awareness." - else - puts " #{test.name} has damaged your karma." - @failed_test = test - @failure = test.failure - throw :edgecase_exit - end - end - - def failed? - ! @failure.nil? - end - - def assert_failed? - failure.is_a?(AssertionError) - end - - def report - if failed? - puts - puts "You have not yet reached enlightenment ..." - puts failure.message - puts - puts "Please meditate on the following code:" - if assert_failed? - puts find_interesting_lines(failure.backtrace) - else - puts failure.backtrace - end - puts - end - say_something_zenlike - end - - def find_interesting_lines(backtrace) - backtrace.reject { |line| - line =~ /test\/unit\/|edgecase\.rb/ - } - end - - # Hat's tip to Ara T. Howard for the zen statements from his - # metakoans Ruby Quiz (http://rubyquiz.com/quiz67.html) - def say_something_zenlike - puts - if !failed? - puts "Mountains are again merely mountains" - else - case (@pass_count % 10) - when 0 - puts "mountains are merely mountains" - when 1, 2 - puts "learn the rules so you know how to break them properly" - when 3, 4 - puts "remember that silence is sometimes the best answer" - when 5, 6 - puts "sleep is the best meditation" - when 7, 8 - puts "when you lose, don't lose the lesson" - else - puts "things are not what they appear to be: nor are they otherwise" - end - end - end - end - - class Koan - include Test::Unit::Assertions - - attr_reader :name, :failure - - def initialize(name) - @name = name - @failure = nil - end - - def passed? - @failure.nil? - end - - def failed(failure) - @failure = failure - end - - def setup - end - - def teardown - end - - # Class methods for the EdgeCase test suite. - class << self - def inherited(subclass) - subclasses << subclass - end - - def method_added(name) - testmethods << name unless tests_disabled? - end - - def run_tests(accumulator) - puts - puts "Thinking #{self}" - testmethods.each do |m| - self.run_test(m, accumulator) if Koan.test_pattern =~ m.to_s - end - end - - def run_test(method, accumulator) - test = self.new(method) - test.setup - begin - test.send(method) - rescue StandardError => ex - test.failed(ex) - ensure - begin - test.teardown - rescue StandardError => ex - test.failed(ex) if test.passed? - end - end - accumulator.accumulate(test) - end - - def end_of_enlightenment - @tests_disabled = true - end - - def command_line(args) - args.each do |arg| - case arg - when /^-n\/(.*)\/$/ - @test_pattern = Regexp.new($1) - when /^-n(.*)$/ - @test_pattern = Regexp.new(Regexp.quote($1)) - else - if File.exist?(arg) - load(arg) - else - fail "Unknown command line argument '#{arg}'" - end - end - end - end - - # Lazy initialize list of subclasses - def subclasses - @subclasses ||= [] - end - - # Lazy initialize list of test methods. - def testmethods - @test_methods ||= [] - end - - def tests_disabled? - @tests_disabled ||= false - end - - def test_pattern - @test_pattern ||= /^test_/ - end - - end - end -end - -END { - EdgeCase::Koan.command_line(ARGV) - zen_master = EdgeCase::Sensei.new - catch(:edgecase_exit) { - EdgeCase::Koan.subclasses.each do |sc| - sc.run_tests(zen_master) - end - } - zen_master.report -} diff --git a/koans/first_test.rb b/koans/first_test.rb deleted file mode 100644 index 708baf17a..000000000 --- a/koans/first_test.rb +++ /dev/null @@ -1,11 +0,0 @@ -require 'test/unit' - -class TestSomething < Test::Unit::TestCase - def test_assert - assert true - assert_equal 1, 1 - assert_equal 1, 1.0 - end -end - - diff --git a/koans/test_helper.rb b/koans/test_helper.rb deleted file mode 100644 index 9accf96d8..000000000 --- a/koans/test_helper.rb +++ /dev/null @@ -1,7 +0,0 @@ -require 'test/unit' - -def __ - "FILL ME IN" -end - -EdgeCase = Test::Unit diff --git a/rakelib/checks.rake b/rakelib/checks.rake new file mode 100644 index 000000000..c1b20d7de --- /dev/null +++ b/rakelib/checks.rake @@ -0,0 +1,33 @@ +namespace "check" do + + desc "Check that the require files match the about_* files" + task :abouts do + about_files = Dir['src/about_*.rb'].size + about_requires = `grep require src/path_to_enlightenment.rb | wc -l`.to_i + puts "Checking path_to_enlightenment completeness" + puts "# of about files: #{about_files}" + puts "# of about requires: #{about_requires}" + if about_files > about_requires + puts "*** There seems to be requires missing in the path to enlightenment" + else + puts "OK" + end + puts + end + + desc "Check that asserts have __ replacements" + task :asserts do + puts "Checking for asserts missing the replacement text:" + begin + sh "egrep -n 'assert( |_)' src/about_*.rb | egrep -v '__|_n_|project|about_assert' | egrep -v ' *#'" + puts + puts "Examine the above lines for missing __ replacements" + rescue RuntimeError => ex + puts "OK" + end + puts + end +end + +desc "Run some simple consistency checks" +task :check => ["check:abouts", "check:asserts"] diff --git a/rakelib/run.rake b/rakelib/run.rake new file mode 100644 index 000000000..a21917833 --- /dev/null +++ b/rakelib/run.rake @@ -0,0 +1,8 @@ +RUBIES = ENV['KOAN_RUBIES'] || %w(ruby-1.8.7-p299,ruby-1.9.2-p0,jruby-1.5.2,jruby-head) + +task :runall do + chdir('src') do + ENV['SIMPLE_KOAN_OUTPUT'] = 'true' + sh "rvm #{RUBIES} path_to_enlightenment.rb" + end +end diff --git a/koans/GREED_RULES.txt b/src/GREED_RULES.txt similarity index 89% rename from koans/GREED_RULES.txt rename to src/GREED_RULES.txt index f120604d5..58b5a9cb6 100644 --- a/koans/GREED_RULES.txt +++ b/src/GREED_RULES.txt @@ -1,12 +1,12 @@ = Playing Greed -Greed is a dice game played amoung 2 or more players, using 5 +Greed is a dice game played among 2 or more players, using 5 six-sided dice. == Playing Greed Each player takes a turn consisting of one or more rolls of the dice. -On the first roll of the game, a player rolls all six dice which are +On the first roll of the game, a player rolls all five dice which are scored according to the following: Three 1's => 1000 points @@ -37,8 +37,8 @@ final example. After a player rolls and the score is calculated, the scoring dice are removed and the player has the option of rolling again using only the -non-scoring dice. If there all no non-scoring dice), then the player -may roll all 5 dice in the next roll. +non-scoring dice. If all of the thrown dice are scoring, then the +player may roll all 5 dice in the next roll. The player may continue to roll as long as each roll scores points. If a roll has zero points, then the player loses not only their turn, but diff --git a/src/Rakefile b/src/Rakefile new file mode 100644 index 000000000..1a2c7f26d --- /dev/null +++ b/src/Rakefile @@ -0,0 +1,12 @@ +#!/usr/bin/env ruby +# -*- ruby -*- + +require 'rake/clean' +require 'rake/testtask' + +task :default => :test + +task :test do + ruby 'path_to_enlightenment.rb' +end + diff --git a/src/about_array_assignment.rb b/src/about_array_assignment.rb new file mode 100644 index 000000000..a08f512fd --- /dev/null +++ b/src/about_array_assignment.rb @@ -0,0 +1,51 @@ +require File.expand_path(File.dirname(__FILE__) + '/neo') + +class AboutArrayAssignment < Neo::Koan + def test_non_parallel_assignment + names = ["John", "Smith"] + assert_equal __(["John", "Smith"]), names + end + + def test_parallel_assignments + first_name, last_name = ["John", "Smith"] + assert_equal __("John"), first_name + assert_equal __("Smith"), last_name + end + + def test_parallel_assignments_with_extra_values + first_name, last_name = ["John", "Smith", "III"] + assert_equal __("John"), first_name + assert_equal __("Smith"), last_name + end + + def test_parallel_assignments_with_splat_operator + first_name, *last_name = ["John", "Smith", "III"] + assert_equal __("John"), first_name + assert_equal __(["Smith","III"]), last_name + end + + def test_parallel_assignments_with_too_few_variables + first_name, last_name = ["Cher"] + assert_equal __("Cher"), first_name + assert_equal __(nil), last_name + end + + def test_parallel_assignments_with_subarrays + first_name, last_name = [["Willie", "Rae"], "Johnson"] + assert_equal __(["Willie", "Rae"]), first_name + assert_equal __("Johnson"), last_name + end + + def test_parallel_assignment_with_one_variable + first_name, = ["John", "Smith"] + assert_equal __("John"), first_name + end + + def test_swapping_with_parallel_assignment + first_name = "Roy" + last_name = "Rob" + first_name, last_name = last_name, first_name + assert_equal __('Rob'), first_name + assert_equal __('Roy'), last_name + end +end diff --git a/src/about_arrays.rb b/src/about_arrays.rb new file mode 100644 index 000000000..a415538ae --- /dev/null +++ b/src/about_arrays.rb @@ -0,0 +1,84 @@ +require File.expand_path(File.dirname(__FILE__) + '/neo') + +class AboutArrays < Neo::Koan + def test_creating_arrays + empty_array = Array.new + assert_equal __(Array), empty_array.class + assert_equal __(0), empty_array.size + end + + def test_array_literals + array = Array.new + assert_equal [], array # __ + + array[0] = 1 + assert_equal [1], array # __ + + array[1] = 2 + assert_equal [1, __(2)], array + + array << 333 + assert_equal __([1, 2, 333]), array + end + + def test_accessing_array_elements + array = [:peanut, :butter, :and, :jelly] + + assert_equal __(:peanut), array[0] + assert_equal __(:peanut), array.first + assert_equal __(:jelly), array[3] + assert_equal __(:jelly), array.last + assert_equal __(:jelly), array[-1] + assert_equal __(:butter), array[-3] + end + + def test_slicing_arrays + array = [:peanut, :butter, :and, :jelly] + + assert_equal __([:peanut]), array[0,1] + assert_equal __([:peanut, :butter]), array[0,2] + assert_equal __([:and, :jelly]), array[2,2] + assert_equal __([:and, :jelly]), array[2,20] + assert_equal __([]), array[4,0] + assert_equal __([]), array[4,100] + assert_equal __(nil), array[5,0] + end + + def test_arrays_and_ranges + assert_equal __(Range), (1..5).class + assert_not_equal [1,2,3,4,5], (1..5) # __ + assert_equal __([1,2,3,4,5]), (1..5).to_a + assert_equal __([1,2,3,4]), (1...5).to_a + end + + def test_slicing_with_ranges + array = [:peanut, :butter, :and, :jelly] + + assert_equal __([:peanut, :butter, :and]), array[0..2] + assert_equal __([:peanut, :butter]), array[0...2] + assert_equal __([:and, :jelly]), array[2..-1] + end + + def test_pushing_and_popping_arrays + array = [1,2] + array.push(:last) + + assert_equal __([1, 2, :last]), array + + popped_value = array.pop + assert_equal __(:last), popped_value + assert_equal __([1, 2]), array + end + + def test_shifting_arrays + array = [1,2] + array.unshift(:first) + + assert_equal __([:first, 1, 2]), array + + shifted_value = array.shift + assert_equal __(:first), shifted_value + assert_equal __([1, 2]), array + end + +end diff --git a/koans/about_basics.rb b/src/about_asserts.rb similarity index 67% rename from koans/about_basics.rb rename to src/about_asserts.rb index db0bfe373..88c310005 100644 --- a/koans/about_basics.rb +++ b/src/about_asserts.rb @@ -1,25 +1,39 @@ #!/usr/bin/env ruby # -*- ruby -*- -require 'edgecase' +require File.expand_path(File.dirname(__FILE__) + '/neo') -class AboutAsserts < EdgeCase::Koan +class AboutAsserts < Neo::Koan # We shall contemplate truth by testing reality, via asserts. def test_assert_truth + #-- + assert true # This should be true + if false + #++ assert false # This should be true + #-- + end + #++ end # Enlightenment may be more easily achieved with appropriate # messages. def test_assert_with_message + #-- + assert true, "This should be true -- Please fix this" + if false + #++ assert false, "This should be true -- Please fix this" + #-- + end + #++ end # To understand reality, we must compare our expectations against # reality. def test_assert_equality - expected_value = 3 + expected_value = __(2) actual_value = 1 + 1 assert expected_value == actual_value @@ -27,7 +41,7 @@ def test_assert_equality # Some ways of asserting equality are better than others. def test_a_better_way_of_asserting_equality - expected_value = 3 + expected_value = __(2) actual_value = 1 + 1 assert_equal expected_value, actual_value @@ -35,6 +49,6 @@ def test_a_better_way_of_asserting_equality # Sometimes we will ask you to fill in the values def test_fill_in_values - assert_equal __, 1 + 1 + assert_equal __(2), 1 + 1 end end diff --git a/koans/about_blocks.rb b/src/about_blocks.rb similarity index 65% rename from koans/about_blocks.rb rename to src/about_blocks.rb index df3a745d2..fa06eab65 100644 --- a/koans/about_blocks.rb +++ b/src/about_blocks.rb @@ -1,6 +1,6 @@ -require 'edgecase' +require File.expand_path(File.dirname(__FILE__) + '/neo') -class AboutBlocks < EdgeCase::Koan +class AboutBlocks < Neo::Koan def method_with_block result = yield result @@ -8,12 +8,12 @@ def method_with_block def test_methods_can_take_blocks yielded_result = method_with_block { 1 + 2 } - assert_equal __, yielded_result + assert_equal __(3), yielded_result end def test_blocks_can_be_defined_with_do_end_too yielded_result = method_with_block do 1 + 2 end - assert_equal __, yielded_result + assert_equal __(3), yielded_result end # ------------------------------------------------------------------ @@ -23,8 +23,8 @@ def method_with_block_arguments end def test_blocks_can_take_arguments - result = method_with_block_arguments do |argument| - assert_equal __, argument + method_with_block_arguments do |argument| + assert_equal __("Jim"), argument end end @@ -40,7 +40,7 @@ def many_yields def test_methods_can_call_yield_many_times result = [] many_yields { |item| result << item } - assert_equal __, result + assert_equal __([:peanut, :butter, :and, :jelly]), result end # ------------------------------------------------------------------ @@ -54,43 +54,43 @@ def yield_tester end def test_methods_can_see_if_they_have_been_called_with_a_block - assert_equal __, yield_tester { :with_block } - assert_equal __, yield_tester + assert_equal __(:with_block), yield_tester { :with_block } + assert_equal __(:no_block), yield_tester end # ------------------------------------------------------------------ - def test_block_can_effect_variables_in_the_code_where_they_are_created + def test_block_can_affect_variables_in_the_code_where_they_are_created value = :initial_value method_with_block { value = :modified_in_a_block } - assert_equal __, value + assert_equal __(:modified_in_a_block), value end def test_blocks_can_be_assigned_to_variables_and_called_explicitly add_one = lambda { |n| n + 1 } - assert_equal __, add_one.call(10) + assert_equal __(11), add_one.call(10) - # Alternative calling sequence - assert_equal __, add_one[10] + # Alternative calling syntax + assert_equal __(11), add_one[10] end def test_stand_alone_blocks_can_be_passed_to_methods_expecting_blocks make_upper = lambda { |n| n.upcase } result = method_with_block_arguments(&make_upper) - assert_equal __, result + assert_equal __("JIM"), result end # ------------------------------------------------------------------ - def method_with_explict_block(&block) + def method_with_explicit_block(&block) block.call(10) end def test_methods_can_take_an_explicit_block_argument - assert_equal __, method_with_explict_block { |n| n * 2 } + assert_equal __(20), method_with_explicit_block { |n| n * 2 } add_one = lambda { |n| n + 1 } - assert_equal __, method_with_explict_block(&add_one) + assert_equal __(11), method_with_explicit_block(&add_one) end end diff --git a/koans/about_class_methods.rb b/src/about_class_methods.rb similarity index 70% rename from koans/about_class_methods.rb rename to src/about_class_methods.rb index c3184032d..a352d2df9 100644 --- a/koans/about_class_methods.rb +++ b/src/about_class_methods.rb @@ -1,29 +1,29 @@ -require 'edgecase' +require File.expand_path(File.dirname(__FILE__) + '/neo') -class AboutClassMethods < EdgeCase::Koan +class AboutClassMethods < Neo::Koan class Dog end def test_objects_are_objects fido = Dog.new - assert_equal __, fido.is_a?(Object) + assert_equal __(true), fido.is_a?(Object) end - def test_classes_are_objects_too - assert_equal __, Dog.is_a?(Class) + def test_classes_are_classes + assert_equal __(true), Dog.is_a?(Class) end def test_classes_are_objects_too - assert_equal __, Dog.is_a?(Object) + assert_equal __(true), Dog.is_a?(Object) end def test_objects_have_methods fido = Dog.new - assert_equal __, fido.methods.size + assert fido.methods.size > _n_(30) end def test_classes_have_methods - assert_equal __, Dog.methods.size + assert Dog.methods.size > _n_(40) end def test_you_can_define_methods_on_individual_objects @@ -31,41 +31,41 @@ def test_you_can_define_methods_on_individual_objects def fido.wag :fidos_wag end - assert_equal __, fido.wag + assert_equal __(:fidos_wag), fido.wag end - def test_other_objects_are_affected_by_these_singleton_methods + def test_other_objects_are_not_affected_by_these_singleton_methods fido = Dog.new rover = Dog.new def fido.wag :fidos_wag end - assert_raise(___) do + assert_raise(___(NoMethodError)) do rover.wag end end # ------------------------------------------------------------------ - - def Dog.wag - :class_level_wag - end - class Dog + class Dog2 def wag :instance_level_wag end end + def Dog2.wag + :class_level_wag + end + def test_since_classes_are_objects_you_can_define_singleton_methods_on_them_too - assert_equal __, Dog.a_class_method + assert_equal __(:class_level_wag), Dog2.wag end def test_class_methods_are_independent_of_instance_methods - fido = Dog.new - assert_equal __, fido.wag - assert_equal __, Dog.wag + fido = Dog2.new + assert_equal __(:instance_level_wag), fido.wag + assert_equal __(:class_level_wag), Dog2.wag end # ------------------------------------------------------------------ @@ -81,8 +81,8 @@ def Dog.name def test_classes_and_instances_do_not_share_instance_variables fido = Dog.new fido.name = "Fido" - assert_equal __, fido.name - assert_equal __, Dog.name + assert_equal __("Fido"), fido.name + assert_equal __(nil), Dog.name end # ------------------------------------------------------------------ @@ -94,18 +94,17 @@ def Dog.a_class_method end def test_you_can_define_class_methods_inside_the_class - assert_equal __, Dog.a_class_method + assert_equal __(:dogs_class_method), Dog.a_class_method end - # ------------------------------------------------------------------ LastExpressionInClassStatement = class Dog 21 end - + def test_class_statements_return_the_value_of_their_last_expression - assert_equal __, LastExpressionInClassStatement + assert_equal __(21), LastExpressionInClassStatement end # ------------------------------------------------------------------ @@ -115,7 +114,7 @@ def test_class_statements_return_the_value_of_their_last_expression end def test_self_while_inside_class_is_class_object_not_instance - assert_equal __, Dog == SelfInsideOfClassStatement + assert_equal __(true), Dog == SelfInsideOfClassStatement end # ------------------------------------------------------------------ @@ -127,7 +126,7 @@ def self.class_method2 end def test_you_can_use_self_instead_of_an_explicit_reference_to_dog - assert_equal __, Dog.class_method2 + assert_equal __(:another_way_to_write_class_methods), Dog.class_method2 end # ------------------------------------------------------------------ @@ -141,7 +140,7 @@ def another_class_method end def test_heres_still_another_way_to_write_class_methods - assert_equal __, Dog.another_class_method + assert_equal __(:still_another_way), Dog.another_class_method end # THINK ABOUT IT: @@ -164,7 +163,7 @@ def test_heres_still_another_way_to_write_class_methods def test_heres_an_easy_way_to_call_class_methods_from_instance_methods fido = Dog.new - assert_equal __, fido.class.another_class_method + assert_equal __(:still_another_way), fido.class.another_class_method end end diff --git a/koans/about_classes.rb b/src/about_classes.rb similarity index 70% rename from koans/about_classes.rb rename to src/about_classes.rb index d576e4bbb..48c805430 100644 --- a/koans/about_classes.rb +++ b/src/about_classes.rb @@ -1,12 +1,12 @@ -require 'edgecase' +require File.expand_path(File.dirname(__FILE__) + '/neo') -class AboutClasses < EdgeCase::Koan +class AboutClasses < Neo::Koan class Dog end def test_instances_of_classes_can_be_created_with_new fido = Dog.new - assert_equal __, fido.class + assert_equal __(Dog), fido.class end # ------------------------------------------------------------------ @@ -19,21 +19,21 @@ def set_name(a_name) def test_instance_variables_can_be_set_by_assigning_to_them fido = Dog2.new - assert_equal __, fido.instance_variables + assert_equal __([]), fido.instance_variables fido.set_name("Fido") - assert_equal __, fido.instance_variables + assert_equal __(["@name"], [:@name]), fido.instance_variables end def test_instance_variables_cannot_be_accessed_outside_the_class fido = Dog2.new fido.set_name("Fido") - assert_raise(___) do + assert_raise(___(NoMethodError)) do fido.name end - assert_raise(___) do + assert_raise(___(SyntaxError)) do eval "fido.@name" # NOTE: Using eval because the above line is a syntax error. end @@ -43,15 +43,15 @@ def test_you_can_politely_ask_for_instance_variable_values fido = Dog2.new fido.set_name("Fido") - assert_equal __, fido.instance_variable_get("@name") + assert_equal __("Fido"), fido.instance_variable_get("@name") end def test_you_can_rip_the_value_out_using_instance_eval fido = Dog2.new fido.set_name("Fido") - assert_equal __, fido.instance_eval("@name") # string version - assert_equal __, fido.instance_eval { @name } # block version + assert_equal __("Fido"), fido.instance_eval("@name") # string version + assert_equal __("Fido"), fido.instance_eval { @name } # block version end # ------------------------------------------------------------------ @@ -69,7 +69,7 @@ def test_you_can_create_accessor_methods_to_return_instance_variables fido = Dog3.new fido.set_name("Fido") - assert_equal __, fido.name + assert_equal __("Fido"), fido.name end # ------------------------------------------------------------------ @@ -87,9 +87,9 @@ def test_attr_reader_will_automatically_define_an_accessor fido = Dog4.new fido.set_name("Fido") - assert_equal __, fido.name + assert_equal __("Fido"), fido.name end - + # ------------------------------------------------------------------ class Dog5 @@ -101,7 +101,7 @@ def test_attr_accessor_will_automatically_define_both_read_and_write_accessors fido = Dog5.new fido.name = "Fido" - assert_equal __, fido.name + assert_equal __("Fido"), fido.name end # ------------------------------------------------------------------ @@ -115,22 +115,22 @@ def initialize(initial_name) def test_initialize_provides_initial_values_for_instance_variables fido = Dog6.new("Fido") - assert_equal __, fido.name + assert_equal __("Fido"), fido.name end def test_args_to_new_must_match_initialize - assert_raise(___) do + assert_raise(___(ArgumentError)) do Dog6.new end # THINK ABOUT IT: # Why is this so? end - - def test_different_objects_have_difference_instance_variables + + def test_different_objects_have_different_instance_variables fido = Dog6.new("Fido") rover = Dog6.new("Rover") - assert_not_equal rover.name, fido.name + assert_equal __(true), rover.name != fido.name end # ------------------------------------------------------------------ @@ -147,7 +147,7 @@ def get_self end def to_s - __ + @name end def inspect @@ -159,32 +159,32 @@ def test_inside_a_method_self_refers_to_the_containing_object fido = Dog7.new("Fido") fidos_self = fido.get_self - assert_equal __, fidos_self + assert_equal __(fido), fidos_self end def test_to_s_provides_a_string_version_of_the_object fido = Dog7.new("Fido") - assert_equal "Fido", fido.to_s + assert_equal __("Fido"), fido.to_s end def test_to_s_is_used_in_string_interpolation fido = Dog7.new("Fido") - assert_equal "My dog is Fido", "My dog is #{fido}" + assert_equal __("My dog is Fido"), "My dog is #{fido}" end def test_inspect_provides_a_more_complete_string_version fido = Dog7.new("Fido") - assert_equal __, fido.inspect + assert_equal __(""), fido.inspect end def test_all_objects_support_to_s_and_inspect array = [1,2,3] - assert_equal __, array.to_s - assert_equal __, array.inspect + assert_equal __("123", "[1, 2, 3]"), array.to_s + assert_equal __("[1, 2, 3]"), array.inspect - assert_equal __, "STRING".to_s - assert_equal __, "STRING".inspect + assert_equal __("STRING"), "STRING".to_s + assert_equal __('"STRING"'), "STRING".inspect end - + end diff --git a/src/about_constants.rb b/src/about_constants.rb new file mode 100644 index 000000000..49d2b5af5 --- /dev/null +++ b/src/about_constants.rb @@ -0,0 +1,87 @@ +require File.expand_path(File.dirname(__FILE__) + '/neo') + +C = "top level" + +class AboutConstants < Neo::Koan + + C = "nested" + + def test_nested_constants_may_also_be_referenced_with_relative_paths + assert_equal __("nested"), C + end + + def test_top_level_constants_are_referenced_by_double_colons + assert_equal __("top level"), ::C + end + + def test_nested_constants_are_referenced_by_their_complete_path + assert_equal __("nested"), AboutConstants::C + assert_equal __("nested"), ::AboutConstants::C + end + + # ------------------------------------------------------------------ + + class Animal + LEGS = 4 + def legs_in_animal + LEGS + end + + class NestedAnimal + def legs_in_nested_animal + LEGS + end + end + end + + def test_nested_classes_inherit_constants_from_enclosing_classes + assert_equal __(4), Animal::NestedAnimal.new.legs_in_nested_animal + end + + # ------------------------------------------------------------------ + + class Reptile < Animal + def legs_in_reptile + LEGS + end + end + + def test_subclasses_inherit_constants_from_parent_classes + assert_equal __(4), Reptile.new.legs_in_reptile + end + + # ------------------------------------------------------------------ + + class MyAnimals + LEGS = 2 + + class Bird < Animal + def legs_in_bird + LEGS + end + end + end + + def test_who_wins_with_both_nested_and_inherited_constants + assert_equal __(2), MyAnimals::Bird.new.legs_in_bird + end + + # QUESTION: Which has precedence: The constant in the lexical scope, + # or the constant from the inheritance hierarchy? + + # ------------------------------------------------------------------ + + class MyAnimals::Oyster < Animal + def legs_in_oyster + LEGS + end + end + + def test_who_wins_with_explicit_scoping_on_class_definition + assert_equal __(4), MyAnimals::Oyster.new.legs_in_oyster + end + + # QUESTION: Now which has precedence: The constant in the lexical + # scope, or the constant from the inheritance hierarchy? Why is it + # different than the previous answer? +end diff --git a/koans/about_control_statements.rb b/src/about_control_statements.rb similarity index 53% rename from koans/about_control_statements.rb rename to src/about_control_statements.rb index d2b75ad08..6fedd700e 100644 --- a/koans/about_control_statements.rb +++ b/src/about_control_statements.rb @@ -1,6 +1,6 @@ -require 'edgecase' +require File.expand_path(File.dirname(__FILE__) + '/neo') -class AboutControlStatements < EdgeCase::Koan +class AboutControlStatements < Neo::Koan def test_if_then_else_statements if true @@ -8,15 +8,15 @@ def test_if_then_else_statements else result = :false_value end - assert_equal __, result + assert_equal __(:true_value), result end - def test_if_then_else_statements + def test_if_then_statements result = :default_value if true result = :true_value end - assert_equal __, result + assert_equal __(:true_value), result end def test_if_statements_return_values @@ -25,14 +25,14 @@ def test_if_statements_return_values else :false_value end - assert_equal __, value + assert_equal __(:true_value), value value = if false :true_value else :false_value end - assert_equal __, value + assert_equal __(:false_value), value # NOTE: Actually, EVERY statement in Ruby will return a value, not # just if statements. @@ -42,34 +42,42 @@ def test_if_statements_with_no_else_with_false_condition_return_value value = if false :true_value end - assert_equal __, value + assert_equal __(nil), value end def test_condition_operators - assert_equal __, (true ? :true_value : :false_value) - assert_equal __, (false ? :true_value : :false_value) + assert_equal __(:true_value), (true ? :true_value : :false_value) + assert_equal __(:false_value), (false ? :true_value : :false_value) end def test_if_statement_modifiers result = :default_value result = :true_value if true - assert_equal __, result + assert_equal __(:true_value), result end def test_unless_statement result = :default_value - unless false + unless false # same as saying 'if !false', which evaluates as 'if true' result = :false_value end - assert_equal __, result + assert_equal __(:false_value), result + end + + def test_unless_statement_evaluate_true + result = :default_value + unless true # same as saying 'if !true', which evaluates as 'if false' + result = :true_value + end + assert_equal __(:default_value), result end def test_unless_statement_modifier result = :default_value result = :false_value unless false - assert_equal __, result + assert_equal __(:false_value), result end def test_while_statement @@ -79,7 +87,7 @@ def test_while_statement result = result * i i += 1 end - assert_equal __, result + assert_equal __(3628800), result end def test_break_statement @@ -90,7 +98,17 @@ def test_break_statement result = result * i i += 1 end - assert_equal __, result + assert_equal __(3628800), result + end + + def test_break_statement_returns_values + i = 1 + result = while i <= 10 + break i if i % 2 == 0 + i += 1 + end + + assert_equal __(2), result end def test_next_statement @@ -99,9 +117,9 @@ def test_next_statement while i < 10 i += 1 next if (i % 2) == 0 - result << i + result << i end - assert_equal __, result + assert_equal __([1, 3, 5, 7, 9]), result end def test_for_statement @@ -110,7 +128,15 @@ def test_for_statement for item in array result << item.upcase end - assert_equal [__, __, __], result + assert_equal [__("FISH"), __("AND"), __("CHIPS")], result + end + + def test_times_statement + sum = 0 + 10.times do + sum += 1 + end + assert_equal __(10), sum end end diff --git a/koans/about_dice_project.rb b/src/about_dice_project.rb similarity index 86% rename from koans/about_dice_project.rb rename to src/about_dice_project.rb index c1fccb14c..b4581476d 100644 --- a/koans/about_dice_project.rb +++ b/src/about_dice_project.rb @@ -1,5 +1,12 @@ -require 'edgecase' +require File.expand_path(File.dirname(__FILE__) + '/neo') +# Implement a DiceSet Class here: +# +# class DiceSet +# code ... +# end + +#-- class DiceSet attr_reader :values def roll(n) @@ -7,7 +14,8 @@ def roll(n) end end -class AboutDiceSet < EdgeCase::Koan +#++ +class AboutDiceProject < Neo::Koan def test_can_create_a_dice_set dice = DiceSet.new assert_not_nil dice @@ -48,7 +56,7 @@ def test_dice_values_should_change_between_rolls # # If the rolls are random, then it is possible (although not # likely) that two consecutive rolls are equal. What would be a - # better way to test this. + # better way to test this? end def test_you_can_roll_different_numbers_of_dice diff --git a/src/about_exceptions.rb b/src/about_exceptions.rb new file mode 100644 index 000000000..b4ad63da2 --- /dev/null +++ b/src/about_exceptions.rb @@ -0,0 +1,68 @@ +require File.expand_path(File.dirname(__FILE__) + '/neo') + +class AboutExceptions < Neo::Koan + + class MySpecialError < RuntimeError + end + + def test_exceptions_inherit_from_Exception + assert_equal __(RuntimeError), MySpecialError.ancestors[1] + assert_equal __(StandardError), MySpecialError.ancestors[2] + assert_equal __(Exception), MySpecialError.ancestors[3] + assert_equal __(Object), MySpecialError.ancestors[4] + end + + def test_rescue_clause + result = nil + begin + fail "Oops" + rescue StandardError => ex + result = :exception_handled + end + + assert_equal __(:exception_handled), result + + assert_equal __(true), ex.is_a?(StandardError), "Should be a Standard Error" + assert_equal __(true), ex.is_a?(RuntimeError), "Should be a Runtime Error" + + assert RuntimeError.ancestors.include?(StandardError), # __ + "RuntimeError is a subclass of StandardError" + + assert_equal __("Oops"), ex.message + end + + def test_raising_a_particular_error + result = nil + begin + # 'raise' and 'fail' are synonyms + raise MySpecialError, "My Message" + rescue MySpecialError => ex + result = :exception_handled + end + + assert_equal __(:exception_handled), result + assert_equal __("My Message"), ex.message + end + + def test_ensure_clause + result = nil + begin + fail "Oops" + rescue StandardError + # no code here + ensure + result = :always_run + end + + assert_equal __(:always_run), result + end + + # Sometimes, we must know about the unknown + def test_asserting_an_error_is_raised # __ + # A do-end is a block, a topic to explore more later + assert_raise(___(MySpecialError)) do + raise MySpecialError.new("New instances can be raised directly.") + end + end + +end diff --git a/koans/about_extra_credit.rb b/src/about_extra_credit.rb similarity index 100% rename from koans/about_extra_credit.rb rename to src/about_extra_credit.rb diff --git a/src/about_hashes.rb b/src/about_hashes.rb new file mode 100644 index 000000000..ef5862170 --- /dev/null +++ b/src/about_hashes.rb @@ -0,0 +1,116 @@ +require File.expand_path(File.dirname(__FILE__) + '/neo') + +class AboutHashes < Neo::Koan + def test_creating_hashes + empty_hash = Hash.new + assert_equal __(Hash), empty_hash.class + assert_equal(__({}), empty_hash) + assert_equal __(0), empty_hash.size + end + + def test_hash_literals + hash = { :one => "uno", :two => "dos" } + assert_equal __(2), hash.size + end + + def test_accessing_hashes + hash = { :one => "uno", :two => "dos" } + assert_equal __("uno"), hash[:one] + assert_equal __("dos"), hash[:two] + assert_equal __(nil), hash[:doesnt_exist] + end + + def test_accessing_hashes_with_fetch + hash = { :one => "uno" } + assert_equal __("uno"), hash.fetch(:one) + assert_raise(___(IndexError, KeyError)) do + hash.fetch(:doesnt_exist) + end + + # THINK ABOUT IT: + # + # Why might you want to use #fetch instead of #[] when accessing hash keys? + end + + def test_changing_hashes + hash = { :one => "uno", :two => "dos" } + hash[:one] = "eins" + + expected = { :one => __("eins"), :two => "dos" } + assert_equal __(expected), hash + + # Bonus Question: Why was "expected" broken out into a variable + # rather than used as a literal? + end + + def test_hash_is_unordered + hash1 = { :one => "uno", :two => "dos" } + hash2 = { :two => "dos", :one => "uno" } + + assert_equal __(true), hash1 == hash2 + end + + def test_hash_keys + hash = { :one => "uno", :two => "dos" } + assert_equal __(2), hash.keys.size + assert_equal __(true), hash.keys.include?(:one) + assert_equal __(true), hash.keys.include?(:two) + assert_equal __(Array), hash.keys.class + end + + def test_hash_values + hash = { :one => "uno", :two => "dos" } + assert_equal __(2), hash.values.size + assert_equal __(true), hash.values.include?("uno") + assert_equal __(true), hash.values.include?("dos") + assert_equal __(Array), hash.values.class + end + + def test_combining_hashes + hash = { "jim" => 53, "amy" => 20, "dan" => 23 } + new_hash = hash.merge({ "jim" => 54, "jenny" => 26 }) + + assert_equal __(true), hash != new_hash + + expected = { "jim" => __(54), "amy" => 20, "dan" => 23, "jenny" => __(26) } + assert_equal __(true), expected == new_hash + end + + def test_default_value + hash1 = Hash.new + hash1[:one] = 1 + + assert_equal __(1), hash1[:one] + assert_equal __(nil), hash1[:two] + + hash2 = Hash.new("dos") + hash2[:one] = 1 + + assert_equal __(1), hash2[:one] + assert_equal __("dos"), hash2[:two] + end + + def test_default_value_is_the_same_object + hash = Hash.new([]) + + hash[:one] << "uno" + hash[:two] << "dos" + + assert_equal __(["uno", "dos"]), hash[:one] + assert_equal __(["uno", "dos"]), hash[:two] + assert_equal __(["uno", "dos"]), hash[:three] + + assert_equal __(true), hash[:one].object_id == hash[:two].object_id + end + + def test_default_value_with_block + hash = Hash.new {|hash, key| hash[key] = [] } + + hash[:one] << "uno" + hash[:two] << "dos" + + assert_equal __(["uno"]), hash[:one] + assert_equal __(["dos"]), hash[:two] + assert_equal __([]), hash[:three] + end +end diff --git a/koans/about_inheritance.rb b/src/about_inheritance.rb similarity index 66% rename from koans/about_inheritance.rb rename to src/about_inheritance.rb index 7bc3a1f99..3a119c078 100644 --- a/koans/about_inheritance.rb +++ b/src/about_inheritance.rb @@ -1,6 +1,6 @@ -require 'edgecase' +require File.expand_path(File.dirname(__FILE__) + '/neo') -class AboutInheritance < EdgeCase::Koan +class AboutInheritance < Neo::Koan class Dog attr_reader :name @@ -24,23 +24,23 @@ def bark end def test_subclasses_have_the_parent_as_an_ancestor - assert_equal __, Chihuahua.ancestors.include?(Dog) + assert_equal __(true), Chihuahua.ancestors.include?(Dog) end def test_all_classes_ultimately_inherit_from_object - assert_equal __, Chihuahua.ancestors.include?(Object) + assert_equal __(true), Chihuahua.ancestors.include?(Object) end - def test_subcases_inherit_behavior_from_parent_class + def test_subclasses_inherit_behavior_from_parent_class chico = Chihuahua.new("Chico") - assert_equal __, chico.name + assert_equal __("Chico"), chico.name end def test_subclasses_add_new_behavior chico = Chihuahua.new("Chico") - assert_equal __, chico.wag + assert_equal __(:happy), chico.wag - assert_raise(___) do + assert_raise(___(NoMethodError)) do fido = Dog.new("Fido") fido.wag end @@ -48,10 +48,10 @@ def test_subclasses_add_new_behavior def test_subclasses_can_modify_existing_behavior chico = Chihuahua.new("Chico") - assert_equal __, chico.bark + assert_equal __("yip"), chico.bark fido = Dog.new("Fido") - assert_equal __, fido.bark + assert_equal __("WOOF"), fido.bark end # ------------------------------------------------------------------ @@ -60,20 +60,11 @@ class BullDog < Dog def bark super + ", GROWL" end - - def growl - super.bark + ", GROWL" - end end def test_subclasses_can_invoke_parent_behavior_via_super ralph = BullDog.new("Ralph") - assert_equal __, ralph.bark - end - - def test_super_does_not_work_cross_method - ralph = BullDog.new("Ralph") - + assert_equal __("WOOF, GROWL"), ralph.bark end # ------------------------------------------------------------------ @@ -86,7 +77,7 @@ def growl def test_super_does_not_work_cross_method george = GreatDane.new("George") - assert_raise(___) do + assert_raise(___(NoMethodError)) do george.growl end end diff --git a/src/about_iteration.rb b/src/about_iteration.rb new file mode 100644 index 000000000..2a595dd84 --- /dev/null +++ b/src/about_iteration.rb @@ -0,0 +1,122 @@ +require File.expand_path(File.dirname(__FILE__) + '/neo') + +class AboutIteration < Neo::Koan + + # -- An Aside ------------------------------------------------------ + # Ruby 1.8 stores names as strings. Ruby 1.9 and later stores names + # as symbols. So we use a version dependent method "as_name" to + # convert to the right format in the koans. We will use "as_name" + # whenever comparing to lists of methods. + + in_ruby_version("1.8") do + def as_name(name) + name.to_s + end + end + + in_ruby_version("1.9", "2") do + def as_name(name) + name.to_sym + end + end + + # Ok, now back to the Koans. + # ------------------------------------------------------------------- + + def test_each_is_a_method_on_arrays + assert_equal __(true), [].methods.include?(as_name(:each)) + end + + def test_iterating_with_each + array = [1, 2, 3] + sum = 0 + array.each do |item| + sum += item + end + assert_equal __(6), sum + end + + def test_each_can_use_curly_brace_blocks_too + array = [1, 2, 3] + sum = 0 + array.each { |item| sum += item } + assert_equal __(6), sum + end + + def test_break_works_with_each_style_iterations + array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + sum = 0 + array.each do |item| + break if item > 3 + sum += item + end + assert_equal __(6), sum + end + + def test_collect_transforms_elements_of_an_array + array = [1, 2, 3] + new_array = array.collect { |item| item + 10 } + assert_equal __([11, 12, 13]), new_array + + # NOTE: 'map' is another name for the 'collect' operation + another_array = array.map { |item| item + 10 } + assert_equal __([11, 12, 13]), another_array + end + + def test_select_selects_certain_items_from_an_array + array = [1, 2, 3, 4, 5, 6] + + even_numbers = array.select { |item| (item % 2) == 0 } + assert_equal __([2, 4, 6]), even_numbers + + # NOTE: 'find_all' is another name for the 'select' operation + more_even_numbers = array.find_all { |item| (item % 2) == 0 } + assert_equal __([2, 4, 6]), more_even_numbers + end + + def test_find_locates_the_first_element_matching_a_criteria + array = ["Jim", "Bill", "Clarence", "Doug", "Eli"] + + assert_equal __("Clarence"), array.find { |item| item.size > 4 } + end + + def test_inject_will_blow_your_mind + result = [2, 3, 4].inject(0) { |sum, item| sum + item } + assert_equal __(9), result + + result2 = [2, 3, 4].inject(1) { |product, item| product * item } + assert_equal __(24), result2 + + # Extra Credit: + # Describe in your own words what inject does. + end + + def test_all_iteration_methods_work_on_any_collection_not_just_arrays + # Ranges act like a collection + result = (1..3).map { |item| item + 10 } + assert_equal __([11, 12, 13]), result + + # Files act like a collection of lines + File.open("example_file.txt") do |file| + upcase_lines = file.map { |line| line.strip.upcase } + assert_equal __(["THIS", "IS", "A", "TEST"]), upcase_lines + end + + # NOTE: You can create your own collections that work with each, + # map, select, etc. + end + + # Bonus Question: In the previous koan, we saw the construct: + # + # File.open(filename) do |file| + # # code to read 'file' + # end + # + # Why did we do it that way instead of the following? + # + # file = File.open(filename) + # # code to read 'file' + # + # When you get to the "AboutSandwichCode" koan, recheck your answer. + +end diff --git a/src/about_java_interop.rb b/src/about_java_interop.rb new file mode 100644 index 000000000..83c2a21eb --- /dev/null +++ b/src/about_java_interop.rb @@ -0,0 +1,137 @@ +require File.expand_path(File.dirname(__FILE__) + '/neo') + +include Java + +# Concepts +# * Pull in a java class +# * calling a method, Camel vs snake +# * Resolving module/class name conflicts +# * Showing what gets returned +# * Ruby Strings VS Java Strings +# * Calling custom java class +# * Calling Ruby from java??? + +class AboutJavaInterop < Neo::Koan + def test_using_a_java_library_class + java_array = java.util.ArrayList.new + assert_equal __(Java::JavaUtil::ArrayList), java_array.class + end + + def test_java_class_can_be_referenced_using_both_ruby_and_java_like_syntax + assert_equal __(true), Java::JavaUtil::ArrayList == java.util.ArrayList + end + + def test_include_class_includes_class_in_module_scope + assert_nil defined?(TreeSet) # __ + include_class "java.util.TreeSet" + assert_equal __("constant"), defined?(TreeSet) + end + + # THINK ABOUT IT: + # + # What if we use: + # + # include_class "java.lang.String" + # + # What would be the value of the String constant after this + # include_class is run? Would it be useful to provide a way of + # aliasing java classes to different names? + + JString = java.lang.String + def test_also_java_class_can_be_given_ruby_aliases + java_string = JString.new("A Java String") + assert_equal __(java.lang.String), java_string.class + assert_equal __(java.lang.String), JString + end + + def test_can_directly_call_java_methods_on_java_objects + java_string = JString.new("A Java String") + assert_equal __("a java string"), java_string.toLowerCase + end + + def test_jruby_provides_snake_case_versions_of_java_methods + java_string = JString.new("A Java String") + assert_equal __("a java string"), java_string.to_lower_case + end + + def test_jruby_provides_question_mark_versions_of_boolean_methods + java_string = JString.new("A Java String") + assert_equal __(true), java_string.endsWith("String") + assert_equal __(true), java_string.ends_with("String") + assert_equal __(true), java_string.ends_with?("String") + end + + def test_java_string_are_not_ruby_strings + ruby_string = "A Java String" + java_string = java.lang.String.new(ruby_string) + assert_equal __(true), java_string.is_a?(java.lang.String) + assert_equal __(false), java_string.is_a?(String) + end + + def test_java_strings_can_be_compared_to_ruby_strings_maybe + ruby_string = "A Java String" + java_string = java.lang.String.new(ruby_string) + assert_equal __(false), ruby_string == java_string + assert_equal __(true), java_string == ruby_string + + # THINK ABOUT IT: + # + # Is there any possible way for this to be more wrong? + # + # SERIOUSLY, THINK ABOUT IT: + # + # Why do you suppose that Ruby and Java strings compare like that? + # + # ADVANCED THINK ABOUT IT: + # + # Is there a way to make Ruby/Java string comparisons commutative? + # How would you do it? + end + + def test_however_most_methods_returning_strings_return_ruby_strings + java_array = java.util.ArrayList.new + assert_equal __("[]"), java_array.toString + assert_equal __(true), java_array.toString.is_a?(String) + assert_equal __(false), java_array.toString.is_a?(java.lang.String) + end + + def test_some_ruby_objects_can_be_coerced_to_java + assert_equal __(Java::JavaLang::String), "ruby string".to_java.class + assert_equal __(Java::JavaLang::Long), 1.to_java.class + assert_equal __(Java::JavaLang::Double), 9.32.to_java.class + assert_equal __(Java::JavaLang::Boolean), false.to_java.class + end + + def test_some_ruby_objects_are_not_coerced_to_what_you_might_expect + assert_equal __(false), [].to_java.class == Java::JavaUtil::ArrayList + assert_equal __(false), {}.to_java.class == Java::JavaUtil::HashMap + assert_equal __(false), Object.new.to_java.class == Java::JavaLang::Object + end + + def test_java_collections_are_enumerable + java_array = java.util.ArrayList.new + java_array << "one" << "two" << "three" + assert_equal __(["ONE", "TWO", "THREE"]), java_array.map { |item| item.upcase } + end + + # ------------------------------------------------------------------ + + # Open the Java ArrayList class and add a new method. + class Java::JavaUtil::ArrayList + def multiply_all + result = 1 + each do |item| + result *= item + end + result + end + end + + def test_java_class_are_open_from_ruby + java_array = java.util.ArrayList.new + java_array.add_all([1,2,3,4,5]) + + assert_equal __(120), java_array.multiply_all + end + +end diff --git a/src/about_keyword_arguments.rb b/src/about_keyword_arguments.rb new file mode 100644 index 000000000..1addb3979 --- /dev/null +++ b/src/about_keyword_arguments.rb @@ -0,0 +1,31 @@ +require File.expand_path(File.dirname(__FILE__) + '/neo') + +class AboutKeywordArguments < Neo::Koan + + def method_with_keyword_arguments(one: 1, two: 'two') + [one, two] + end + + def test_keyword_arguments + assert_equal __(Array), method_with_keyword_arguments.class + assert_equal __([1, 'two']), method_with_keyword_arguments + assert_equal __(['one', 'two']), method_with_keyword_arguments(one: 'one') + assert_equal __([1, 2]), method_with_keyword_arguments(two: 2) + end + + def method_with_keyword_arguments_with_mandatory_argument(one, two: 2, three: 3) + [one, two, three] + end + + def test_keyword_arguments_with_wrong_number_of_arguments + exception = assert_raise (___(ArgumentError)) do + method_with_keyword_arguments_with_mandatory_argument + end + assert_match(/#{__("wrong number of arguments")}/, exception.message) + end + + # THINK ABOUT IT: + # + # Keyword arguments always have a default value, making them optional to the caller + +end diff --git a/koans/about_message_passing.rb b/src/about_message_passing.rb similarity index 52% rename from koans/about_message_passing.rb rename to src/about_message_passing.rb index d396c8034..84f4787f9 100644 --- a/koans/about_message_passing.rb +++ b/src/about_message_passing.rb @@ -1,37 +1,37 @@ -require 'edgecase' +require File.expand_path(File.dirname(__FILE__) + '/neo') + +class AboutMessagePassing < Neo::Koan -class AboutMessagePassing < EdgeCase::Koan - class MessageCatcher def caught? true end end - + def test_methods_can_be_called_directly mc = MessageCatcher.new - - assert mc.caught? + + assert mc.caught? # __ end - + def test_methods_can_be_invoked_by_sending_the_message mc = MessageCatcher.new - - assert mc.send(:caught?) + + assert mc.send(:caught?) # __ end - + def test_methods_can_be_invoked_more_dynamically mc = MessageCatcher.new - - assert mc.send("caught?") - assert mc.send("caught" + __ ) # What do you need to add to the first string? - assert mc.send("CAUGHT?".__ ) # What would you need to do to the string? + + assert mc.send("caught?") # __ + assert mc.send("caught" + __("?") ) # What do you need to add to the first string? + assert mc.send("CAUGHT?".____(:downcase) ) # What would you need to do to the string? end def test_send_with_underscores_will_also_send_messages mc = MessageCatcher.new - assert_equal __, mc.__send__(:caught?) + assert_equal __(true), mc.__send__(:caught?) # THINK ABOUT IT: # @@ -40,30 +40,36 @@ def test_send_with_underscores_will_also_send_messages def test_classes_can_be_asked_if_they_know_how_to_respond mc = MessageCatcher.new - - assert_equal __, mc.respond_to?(:caught?) - assert_equal __, mc.respond_to?(:does_not_exist) + + assert_equal __(true), mc.respond_to?(:caught?) + assert_equal __(false), mc.respond_to?(:does_not_exist) end - + # ------------------------------------------------------------------ class MessageCatcher def add_a_payload(*args) - return :empty unless args args end end - + def test_sending_a_message_with_arguments mc = MessageCatcher.new - - assert_equal __, mc.add_a_payload - assert_equal __, mc.send(:add_a_payload) - assert_equal __, mc.add_a_payload(3, 4, nil, 6) - assert_equal __, mc.send(:add_a_payload, 3, 4, nil, 6) + assert_equal __([]), mc.add_a_payload + assert_equal __([]), mc.send(:add_a_payload) + + assert_equal __([3, 4, nil, 6]), mc.add_a_payload(3, 4, nil, 6) + assert_equal __([3, 4, nil, 6]), mc.send(:add_a_payload, 3, 4, nil, 6) end + # NOTE: + # + # Both obj.msg and obj.send(:msg) sends the message named :msg to + # the object. We use "send" when the name of the message can vary + # dynamically (e.g. calculated at run time), but by far the most + # common way of sending a message is just to say: obj.msg. + # ------------------------------------------------------------------ class TypicalObject @@ -72,49 +78,61 @@ class TypicalObject def test_sending_undefined_messages_to_a_typical_object_results_in_errors typical = TypicalObject.new - assert_raise(___) do + exception = assert_raise(___(NoMethodError)) do typical.foobar end - assert_match(/foobar/, exception.message) + assert_match(/foobar/, exception.message) # __ end def test_calling_method_missing_causes_the_no_method_error typical = TypicalObject.new - exception = assert_raise(___) do + exception = assert_raise(___(NoMethodError)) do typical.method_missing(:foobar) end - assert_match(/foobar/, exception.message) + assert_match(/foobar/, exception.message) # __ # THINK ABOUT IT: # # If the method :method_missing causes the NoMethodError, then # what would happen if we redefine method_missing? + # + # NOTE: + # + # In Ruby 1.8 the method_missing method is public and can be + # called as shown above. However, in Ruby 1.9 (and later versions) + # the method_missing method is private. We explicitly made it + # public in the testing framework so this example works in both + # versions of Ruby. Just keep in mind you can't call + # method_missing like that after Ruby 1.9 normally. + # + # Thanks. We now return you to your regularly scheduled Ruby + # Koans. end # ------------------------------------------------------------------ class AllMessageCatcher def method_missing(method_name, *args, &block) - "Someone called #{method_name} with (#{args.join(", ")})" + "Someone called #{method_name} with <#{args.join(", ")}>" end end def test_all_messages_are_caught catcher = AllMessageCatcher.new - assert_equal __, catcher.foobar - assert_equal __, catcher.foobaz(1) - assert_equal __, catcher.sum(1,2,3,4,5,6) + assert_equal __("Someone called foobar with <>"), catcher.foobar + assert_equal __("Someone called foobaz with <1>"), catcher.foobaz(1) + assert_equal __("Someone called sum with <1, 2, 3, 4, 5, 6>"), catcher.sum(1,2,3,4,5,6) end def test_catching_messages_makes_respond_to_lie catcher = AllMessageCatcher.new - assert_nothing_raised(NoMethodError) do + assert_nothing_raised do # __ catcher.any_method end - assert_equal __, catcher.respond_to?(:any_method) + assert_equal __(false), catcher.respond_to?(:any_method) end # ------------------------------------------------------------------ @@ -132,14 +150,14 @@ def method_missing(method_name, *args, &block) def test_foo_method_are_caught catcher = WellBehavedFooCatcher.new - assert_equal __, catcher.foo_bar - assert_equal __, catcher.foo_baz + assert_equal __("Foo to you too"), catcher.foo_bar + assert_equal __("Foo to you too"), catcher.foo_baz end def test_non_foo_messages_are_treated_normally catcher = WellBehavedFooCatcher.new - assert_raise(___) do + assert_raise(___(NoMethodError)) do catcher.normal_undefined_method end end @@ -160,8 +178,8 @@ def respond_to?(method_name) def test_explicitly_implementing_respond_to_lets_objects_tell_the_truth catcher = WellBehavedFooCatcher.new - assert_equal __, catcher.respond_to?(:foo_bar) - assert_equal __, catcher.respond_to?(:something_else) + assert_equal __(true), catcher.respond_to?(:foo_bar) + assert_equal __(false), catcher.respond_to?(:something_else) end end diff --git a/koans/about_methods.rb b/src/about_methods.rb similarity index 56% rename from koans/about_methods.rb rename to src/about_methods.rb index 187ad27dd..bd6d8b3cb 100644 --- a/koans/about_methods.rb +++ b/src/about_methods.rb @@ -1,24 +1,31 @@ -require 'edgecase' +require File.expand_path(File.dirname(__FILE__) + '/neo') def my_global_method(a,b) a + b end - -class AboutMethods < EdgeCase::Koan + +class AboutMethods < Neo::Koan def test_calling_global_methods - assert_equal __, my_global_method(2,3) + assert_equal __(5), my_global_method(2,3) end - def test_calling_global_methods_without_parenthesis + def test_calling_global_methods_without_parentheses result = my_global_method 2, 3 - assert_equal __, result + assert_equal __(5), result end # (NOTE: We are Using eval below because the example code is # considered to be syntactically invalid). - def test_sometimes_missing_parenthesis_are_ambiguous - eval "assert_equal 5, my_global_method 2, 3" + def test_sometimes_missing_parentheses_are_ambiguous + #-- + eval "assert_equal 5, my_global_method(2, 3)" # REMOVE CHECK # __ + if false + #++ + eval "assert_equal 5, my_global_method 2, 3" # ENABLE CHECK # __ + #-- + end + #++ # # Ruby doesn't know if you mean: # @@ -29,19 +36,22 @@ def test_sometimes_missing_parenthesis_are_ambiguous # Rewrite the eval string to continue. # end - - # NOTE: wrong number of argument is not a SYNTAX error, but a + + # NOTE: wrong number of arguments is not a SYNTAX error, but a # runtime error. def test_calling_global_methods_with_wrong_number_of_arguments - exception = assert_raise(___) do + exception = assert_raise(___(ArgumentError)) do my_global_method end - assert_equal __, exception.message + #-- + pattern = "wrong (number|#) of arguments" + #++ + assert_match(/#{__(pattern)}/, exception.message) - exception = assert_raise(___) do + exception = assert_raise(___(ArgumentError)) do my_global_method(1,2,3) end - assert_equal __, exception.message + assert_match(/#{__(pattern)}/, exception.message) end # ------------------------------------------------------------------ @@ -51,8 +61,8 @@ def method_with_defaults(a, b=:default_value) end def test_calling_with_default_values - assert_equal [1, __], method_with_defaults(1) - assert_equal [1, __], method_with_defaults(1, 2) + assert_equal [1, __(:default_value)], method_with_defaults(1) + assert_equal [1, __(2)], method_with_defaults(1, 2) end # ------------------------------------------------------------------ @@ -62,9 +72,10 @@ def method_with_var_args(*args) end def test_calling_with_variable_arguments - assert_equal __, method_with_var_args - assert_equal __, method_with_var_args(:one) - assert_equal __, method_with_var_args(:one, :two) + assert_equal __(Array), method_with_var_args.class + assert_equal __([]), method_with_var_args + assert_equal __([:one]), method_with_var_args(:one) + assert_equal __([:one, :two]), method_with_var_args(:one, :two) end # ------------------------------------------------------------------ @@ -72,11 +83,11 @@ def test_calling_with_variable_arguments def method_with_explicit_return :a_non_return_value return :return_value - :anoher_non_return_value + :another_non_return_value end def test_method_with_explicit_return - assert_equal __, method_with_explicit_return + assert_equal __(:return_value), method_with_explicit_return end # ------------------------------------------------------------------ @@ -87,21 +98,21 @@ def method_without_explicit_return end def test_method_without_explicit_return - assert_equal __, method_without_explicit_return + assert_equal __(:return_value), method_without_explicit_return end # ------------------------------------------------------------------ - def my_same_class_method(a, b) + def my_method_in_the_same_class(a, b) a * b end def test_calling_methods_in_same_class - assert_equal __, my_same_class_method(3,4) + assert_equal __(12), my_method_in_the_same_class(3,4) end def test_calling_methods_in_same_class_with_explicit_receiver - assert_equal __, self.my_same_class_method(3,4) + assert_equal __(12), self.my_method_in_the_same_class(3,4) end # ------------------------------------------------------------------ @@ -112,14 +123,14 @@ def my_private_method private :my_private_method def test_calling_private_methods_without_receiver - assert_equal __, my_private_method + assert_equal __("a secret"), my_private_method end def test_calling_private_methods_with_an_explicit_receiver - exception = assert_raise(___) do + exception = assert_raise(___(NoMethodError)) do self.my_private_method end - assert_match /__/, exception.message + assert_match /#{__("method `my_private_method'")}/, exception.message end # ------------------------------------------------------------------ @@ -135,15 +146,15 @@ def tail "tail" end end - + def test_calling_methods_in_other_objects_require_explicit_receiver rover = Dog.new - assert_equal __, rover.name + assert_equal __("Fido"), rover.name end def test_calling_private_methods_in_other_objects rover = Dog.new - assert_raise(___) do + assert_raise(___(NoMethodError)) do rover.tail end end diff --git a/koans/about_modules.rb b/src/about_modules.rb similarity index 64% rename from koans/about_modules.rb rename to src/about_modules.rb index c18c81e30..246831c41 100644 --- a/koans/about_modules.rb +++ b/src/about_modules.rb @@ -1,6 +1,6 @@ -require 'edgecase' +require File.expand_path(File.dirname(__FILE__) + '/neo') -class AboutModules < EdgeCase::Koan +class AboutModules < Neo::Koan module Nameable def set_name(new_name) @name = new_name @@ -12,7 +12,7 @@ def here end def test_cant_instantiate_modules - assert_raise(___) do + assert_raise(___(NoMethodError)) do Nameable.new end end @@ -39,25 +39,25 @@ def here def test_normal_methods_are_available_in_the_object fido = Dog.new - assert_equal __, fido.bark + assert_equal __("WOOF"), fido.bark end - def test_module_methods_are_also_availble_in_the_object + def test_module_methods_are_also_available_in_the_object fido = Dog.new - assert_nothing_raised(Exception) do - fido.set_name("Rover") + assert_nothing_raised do # __ + fido.set_name("Rover") end end def test_module_methods_can_affect_instance_variables_in_the_object fido = Dog.new - assert_equal __, fido.name + assert_equal __("Fido"), fido.name fido.set_name("Rover") - assert_equal __, fido.name + assert_equal __("Rover"), fido.name end def test_classes_can_override_module_methods fido = Dog.new - assert_equal __, fido.here + assert_equal __(:in_object), fido.here end end diff --git a/src/about_nil.rb b/src/about_nil.rb new file mode 100644 index 000000000..0c084d727 --- /dev/null +++ b/src/about_nil.rb @@ -0,0 +1,38 @@ +require File.expand_path(File.dirname(__FILE__) + '/neo') + +class AboutNil < Neo::Koan + def test_nil_is_an_object + assert_equal __(true), nil.is_a?(Object), "Unlike NULL in other languages" + end + + def test_you_dont_get_null_pointer_errors_when_calling_methods_on_nil + # What happens when you call a method that doesn't exist. The + # following begin/rescue/end code block captures the exception and + # makes some assertions about it. + begin + nil.some_method_nil_doesnt_know_about + rescue Exception => ex + # What exception has been caught? + assert_equal __(NoMethodError), ex.class + + # What message was attached to the exception? + # (HINT: replace __ with part of the error message.) + assert_match(/#{__("undefined method")}/, ex.message) + end + end + + def test_nil_has_a_few_methods_defined_on_it + assert_equal __(true), nil.nil? + assert_equal __(""), nil.to_s + assert_equal __("nil"), nil.inspect + + # THINK ABOUT IT: + # + # Is it better to use + # obj.nil? + # or + # obj == nil + # Why? + end + +end diff --git a/src/about_objects.rb b/src/about_objects.rb new file mode 100644 index 000000000..0d752583b --- /dev/null +++ b/src/about_objects.rb @@ -0,0 +1,50 @@ +require File.expand_path(File.dirname(__FILE__) + '/neo') + +class AboutObjects < Neo::Koan + def test_everything_is_an_object + assert_equal __(true), 1.is_a?(Object) + assert_equal __(true), 1.5.is_a?(Object) + assert_equal __(true), "string".is_a?(Object) + assert_equal __(true), nil.is_a?(Object) + assert_equal __(true), Object.is_a?(Object) + end + + def test_objects_can_be_converted_to_strings + assert_equal __("123"), 123.to_s + assert_equal __(""), nil.to_s + end + + def test_objects_can_be_inspected + assert_equal __("123"), 123.inspect + assert_equal __("nil"), nil.inspect + end + + def test_every_object_has_an_id + obj = Object.new + assert_equal __(Fixnum), obj.object_id.class + end + + def test_every_object_has_different_id + obj = Object.new + another_obj = Object.new + assert_equal __(true), obj.object_id != another_obj.object_id + end + + def test_small_integers_have_fixed_ids + assert_equal __(1), 0.object_id + assert_equal __(3), 1.object_id + assert_equal __(5), 2.object_id + assert_equal __(201), 100.object_id + + # THINK ABOUT IT: + # What pattern do the object IDs for small integers follow? + end + + def test_clone_creates_a_different_object + obj = Object.new + copy = obj.clone + + assert_equal __(true), obj != copy + assert_equal __(true), obj.object_id != copy.object_id + end +end diff --git a/koans/about_open_classes.rb b/src/about_open_classes.rb similarity index 67% rename from koans/about_open_classes.rb rename to src/about_open_classes.rb index 233cef507..0372f50a5 100644 --- a/koans/about_open_classes.rb +++ b/src/about_open_classes.rb @@ -1,6 +1,6 @@ -require 'edgecase' +require File.expand_path(File.dirname(__FILE__) + '/neo') -class AboutOpenClasses < EdgeCase::Koan +class AboutOpenClasses < Neo::Koan class Dog def bark "WOOF" @@ -9,7 +9,7 @@ def bark def test_as_defined_dogs_do_bark fido = Dog.new - assert_equal __, fido.bark + assert_equal __("WOOF"), fido.bark end # ------------------------------------------------------------------ @@ -23,8 +23,8 @@ def wag def test_after_reopening_dogs_can_both_wag_and_bark fido = Dog.new - assert_equal __, fido.wag - assert_equal __, fido.bark + assert_equal __("HAPPY"), fido.wag + assert_equal __("WOOF"), fido.bark end # ------------------------------------------------------------------ @@ -36,10 +36,10 @@ def even? end def test_even_existing_built_in_classes_can_be_reopened - assert_equal __, 1.even? - assert_equal __, 2.even? + assert_equal __(false), 1.even? + assert_equal __(true), 2.even? end # NOTE: To understand why we need the :: before Integer, you need to - # become enlightened about scope. + # become enlightened about scope. end diff --git a/koans/about_proxy_object_project.rb b/src/about_proxy_object_project.rb similarity index 82% rename from koans/about_proxy_object_project.rb rename to src/about_proxy_object_project.rb index dad63d7a2..3f969f61f 100644 --- a/koans/about_proxy_object_project.rb +++ b/src/about_proxy_object_project.rb @@ -1,4 +1,4 @@ -require 'edgecase' +require File.expand_path(File.dirname(__FILE__) + '/neo') # Project: Create a Proxy Class # @@ -6,7 +6,7 @@ # below). You should be able to initialize the proxy object with any # object. Any messages sent to the proxy object should be forwarded # to the target object. As each message is sent, the proxy should -# record the name of the method send. +# record the name of the method sent. # # The proxy class is started for you. You will need to add a method # missing handler and any other supporting methods. The specification @@ -15,59 +15,83 @@ class Proxy def initialize(target_object) @object = target_object + # ADD MORE CODE HERE + #-- + @messages = [] + #++ end + + # WRITE CODE HERE + #-- + attr_reader :messages + + def method_missing(sym, *args, &block) + @messages << sym + @object.send(sym, *args, &block) + end + + def called?(method) + @messages.include?(method) + end + + def number_of_times_called(method) + @messages.select { |m| m == method }.size + end + #++ end # The proxy object should pass the following Koan: # -class AboutProxyObjectProject < EdgeCase::Koan +class AboutProxyObjectProject < Neo::Koan def test_proxy_method_returns_wrapped_object # NOTE: The Television class is defined below tv = Proxy.new(Television.new) - + + # HINT: Proxy class is defined above, may need tweaking... + assert tv.instance_of?(Proxy) end - + def test_tv_methods_still_perform_their_function tv = Proxy.new(Television.new) - + tv.channel = 10 tv.power - + assert_equal 10, tv.channel assert tv.on? end def test_proxy_records_messages_sent_to_tv tv = Proxy.new(Television.new) - + tv.power tv.channel = 10 - + assert_equal [:power, :channel=], tv.messages end - + def test_proxy_handles_invalid_messages tv = Proxy.new(Television.new) - + assert_raise(NoMethodError) do tv.no_such_method end end - + def test_proxy_reports_methods_have_been_called tv = Proxy.new(Television.new) - + tv.power tv.power - + assert tv.called?(:power) assert ! tv.called?(:channel) end - + def test_proxy_counts_method_calls tv = Proxy.new(Television.new) - + tv.power tv.channel = 48 tv.power @@ -96,7 +120,7 @@ def test_proxy_can_record_more_than_just_tv_objects # Example class using in the proxy testing above. class Television attr_accessor :channel - + def power if @power == :on @power = :off @@ -104,41 +128,41 @@ def power @power = :on end end - + def on? @power == :on end end # Tests for the Television class. All of theses tests should pass. -class TelevisionTest < EdgeCase::Koan +class TelevisionTest < Neo::Koan def test_it_turns_on tv = Television.new - + tv.power assert tv.on? end - + def test_it_also_turns_off tv = Television.new - + tv.power tv.power - + assert ! tv.on? end - + def test_edge_case_on_off tv = Television.new - + tv.power tv.power tv.power - + assert tv.on? - + tv.power - + assert ! tv.on? end diff --git a/src/about_regular_expressions.rb b/src/about_regular_expressions.rb new file mode 100644 index 000000000..76bd56640 --- /dev/null +++ b/src/about_regular_expressions.rb @@ -0,0 +1,161 @@ +# -*- coding: utf-8 -*- +require File.expand_path(File.dirname(__FILE__) + '/neo') + +class AboutRegularExpressions < Neo::Koan + def test_a_pattern_is_a_regular_expression + assert_equal __(Regexp), /pattern/.class + end + + def test_a_regexp_can_search_a_string_for_matching_content + assert_equal __("match"), "some matching content"[/match/] + end + + def test_a_failed_match_returns_nil + assert_equal __(nil), "some matching content"[/missing/] + end + + # ------------------------------------------------------------------ + + def test_question_mark_means_optional + assert_equal __("ab"), "abbcccddddeeeee"[/ab?/] + assert_equal __("a"), "abbcccddddeeeee"[/az?/] + end + + def test_plus_means_one_or_more + assert_equal __("bccc"), "abbcccddddeeeee"[/bc+/] + end + + def test_asterisk_means_zero_or_more + assert_equal __("abb"), "abbcccddddeeeee"[/ab*/] + assert_equal __("a"), "abbcccddddeeeee"[/az*/] + assert_equal __(""), "abbcccddddeeeee"[/z*/] + + # THINK ABOUT IT: + # + # When would * fail to match? + end + + # THINK ABOUT IT: + # + # We say that the repetition operators above are "greedy." + # + # Why? + + # ------------------------------------------------------------------ + + def test_the_left_most_match_wins + assert_equal __("a"), "abbccc az"[/az*/] + end + + # ------------------------------------------------------------------ + + def test_character_classes_give_options_for_a_character + animals = ["cat", "bat", "rat", "zat"] + assert_equal __(["cat", "bat", "rat"]), animals.select { |a| a[/[cbr]at/] } + end + + def test_slash_d_is_a_shortcut_for_a_digit_character_class + assert_equal __("42"), "the number is 42"[/[0123456789]+/] + assert_equal __("42"), "the number is 42"[/\d+/] + end + + def test_character_classes_can_include_ranges + assert_equal __("42"), "the number is 42"[/[0-9]+/] + end + + def test_slash_s_is_a_shortcut_for_a_whitespace_character_class + assert_equal __(" \t\n"), "space: \t\n"[/\s+/] + end + + def test_slash_w_is_a_shortcut_for_a_word_character_class + # NOTE: This is more like how a programmer might define a word. + assert_equal __("variable_1"), "variable_1 = 42"[/[a-zA-Z0-9_]+/] + assert_equal __("variable_1"), "variable_1 = 42"[/\w+/] + end + + def test_period_is_a_shortcut_for_any_non_newline_character + assert_equal __("abc"), "abc\n123"[/a.+/] + end + + def test_a_character_class_can_be_negated + assert_equal __("the number is "), "the number is 42"[/[^0-9]+/] + end + + def test_shortcut_character_classes_are_negated_with_capitals + assert_equal __("the number is "), "the number is 42"[/\D+/] + assert_equal __("space:"), "space: \t\n"[/\S+/] + # ... a programmer would most likely do + assert_equal __(" = "), "variable_1 = 42"[/[^a-zA-Z0-9_]+/] + assert_equal __(" = "), "variable_1 = 42"[/\W+/] + end + + # ------------------------------------------------------------------ + + def test_slash_a_anchors_to_the_start_of_the_string + assert_equal __("start"), "start end"[/\Astart/] + assert_equal __(nil), "start end"[/\Aend/] + end + + def test_slash_z_anchors_to_the_end_of_the_string + assert_equal __("end"), "start end"[/end\z/] + assert_equal __(nil), "start end"[/start\z/] + end + + def test_caret_anchors_to_the_start_of_lines + assert_equal __("2"), "num 42\n2 lines"[/^\d+/] + end + + def test_dollar_sign_anchors_to_the_end_of_lines + assert_equal __("42"), "2 lines\nnum 42"[/\d+$/] + end + + def test_slash_b_anchors_to_a_word_boundary + assert_equal __("vines"), "bovine vines"[/\bvine./] + end + + # ------------------------------------------------------------------ + + def test_parentheses_group_contents + assert_equal __("hahaha"), "ahahaha"[/(ha)+/] + end + + # ------------------------------------------------------------------ + + def test_parentheses_also_capture_matched_content_by_number + assert_equal __("Gray"), "Gray, James"[/(\w+), (\w+)/, 1] + assert_equal __("James"), "Gray, James"[/(\w+), (\w+)/, 2] + end + + def test_variables_can_also_be_used_to_access_captures + assert_equal __("Gray, James"), "Name: Gray, James"[/(\w+), (\w+)/] + assert_equal __("Gray"), $1 + assert_equal __("James"), $2 + end + + # ------------------------------------------------------------------ + + def test_a_vertical_pipe_means_or + grays = /(James|Dana|Summer) Gray/ + assert_equal __("James Gray"), "James Gray"[grays] + assert_equal __("Summer"), "Summer Gray"[grays, 1] + assert_equal __(nil), "Jim Gray"[grays, 1] + end + + # THINK ABOUT IT: + # + # Explain the difference between a character class ([...]) and alternation (|). + + # ------------------------------------------------------------------ + + def test_scan_is_like_find_all + assert_equal __(["one", "two", "three"]), "one two-three".scan(/\w+/) + end + + def test_sub_is_like_find_and_replace + assert_equal __("one t-three"), "one two-three".sub(/(t\w*)/) { $1[0, 1] } + end + + def test_gsub_is_like_find_and_replace_all + assert_equal __("one t-t"), "one two-three".gsub(/(t\w*)/) { $1[0, 1] } + end +end diff --git a/koans/about_sandwich_code.rb b/src/about_sandwich_code.rb similarity index 75% rename from koans/about_sandwich_code.rb rename to src/about_sandwich_code.rb index 37f0c5fee..1314ec834 100644 --- a/koans/about_sandwich_code.rb +++ b/src/about_sandwich_code.rb @@ -1,11 +1,11 @@ -require 'edgecase' +require File.expand_path(File.dirname(__FILE__) + '/neo') -class AboutUsingBlocks < EdgeCase::Koan +class AboutSandwichCode < Neo::Koan def count_lines(file_name) file = open(file_name) count = 0 - while line = file.gets + while file.gets count += 1 end count @@ -14,7 +14,7 @@ def count_lines(file_name) end def test_counting_lines - assert_equal __, count_lines("example_file.txt") + assert_equal __(4), count_lines("example_file.txt") end # ------------------------------------------------------------------ @@ -29,7 +29,7 @@ def find_line(file_name) end def test_finding_lines - assert_equal __, find_line("example_file.txt") + assert_equal __("test\n"), find_line("example_file.txt") end # ------------------------------------------------------------------ @@ -40,7 +40,7 @@ def test_finding_lines # # Sandwich code is code that comes in three parts: (1) the top slice # of bread, (2) the meat, and (3) the bottom slice of bread. The - # the bread part of the sandwich almost always goes together, but + # bread part of the sandwich almost always goes together, but # the meat part changes all the time. # # Because the changing part of the sandwich code is in the middle, @@ -66,7 +66,7 @@ def file_sandwich(file_name) def count_lines2(file_name) file_sandwich(file_name) do |file| count = 0 - while line = file.gets + while file.gets count += 1 end count @@ -74,25 +74,32 @@ def count_lines2(file_name) end def test_counting_lines2 - assert_equal __, count_lines2("example_file.txt") + assert_equal __(4), count_lines2("example_file.txt") end # ------------------------------------------------------------------ def find_line2(file_name) # Rewrite find_line using the file_sandwich library function. + #-- + file_sandwich(file_name) do |file| + file.each do |line| + return line if line =~ /e/ + end + end + #++ end def test_finding_lines2 - assert_equal __, find_line2("example_file.txt") + assert_equal __("test\n"), find_line2("example_file.txt") end - + # ------------------------------------------------------------------ def count_lines3(file_name) open(file_name) do |file| count = 0 - while line = file.gets + while file.gets count += 1 end count @@ -100,7 +107,7 @@ def count_lines3(file_name) end def test_open_handles_the_file_sandwich_when_given_a_block - assert_equal __, count_lines3("example_file.txt") + assert_equal __(4), count_lines3("example_file.txt") end end diff --git a/koans/about_scope.rb b/src/about_scope.rb similarity index 56% rename from koans/about_scope.rb rename to src/about_scope.rb index 968c360cf..4760a9aee 100644 --- a/koans/about_scope.rb +++ b/src/about_scope.rb @@ -1,6 +1,6 @@ -require 'edgecase' +require File.expand_path(File.dirname(__FILE__) + '/neo') -class AboutScope < EdgeCase::Koan +class AboutScope < Neo::Koan module Jims class Dog def identify @@ -18,19 +18,19 @@ def identify end def test_dog_is_not_available_in_the_current_scope - assert_raise(___) do - fido = Dog.new + assert_raise(___(NameError)) do + Dog.new end end def test_you_can_reference_nested_classes_using_the_scope_operator fido = Jims::Dog.new rover = Joes::Dog.new - assert_equal __, fido.identify - assert_equal __, rover.identify - - assert_not_equal fido.class, rover.class - assert_not_equal Jims::Dog, Joes::Dog + assert_equal __(:jims_dog), fido.identify + assert_equal __(:joes_dog), rover.identify + + assert_equal __(true), fido.class != rover.class + assert_equal __(true), Jims::Dog != Joes::Dog end # ------------------------------------------------------------------ @@ -39,15 +39,15 @@ class String end def test_bare_bones_class_names_assume_the_current_scope - assert_equal __, AboutScope::String == String + assert_equal __(true), AboutScope::String == String end - + def test_nested_string_is_not_the_same_as_the_system_string - assert_equal __, String == "HI".class + assert_equal __(false), String == "HI".class end def test_use_the_prefix_scope_operator_to_force_the_global_scope - assert_equal __, ::String == "HI".class + assert_equal __(true), ::String == "HI".class end # ------------------------------------------------------------------ @@ -55,7 +55,7 @@ def test_use_the_prefix_scope_operator_to_force_the_global_scope PI = 3.1416 def test_constants_are_defined_with_an_initial_uppercase_letter - assert_equal __, PI + assert_equal __(3.1416), PI end # ------------------------------------------------------------------ @@ -63,17 +63,17 @@ def test_constants_are_defined_with_an_initial_uppercase_letter MyString = ::String def test_class_names_are_just_constants - assert_equal __, MyString == ::String - assert_equal __, MyString == "HI".class + assert_equal __(true), MyString == ::String + assert_equal __(true), MyString == "HI".class end def test_constants_can_be_looked_up_explicitly - assert_equal __, PI == AboutScope.const_get("PI") - assert_equal __, MyString == AboutScope.const_get("MyString") + assert_equal __(true), PI == AboutScope.const_get("PI") + assert_equal __(true), MyString == AboutScope.const_get("MyString") end def test_you_can_get_a_list_of_constants_for_any_class_or_module - assert_equal __, Jims.constants - assert_equal __, Object.constants.size + assert_equal __(["Dog"], [:Dog]), Jims.constants + assert Object.constants.size > _n_(10) end end diff --git a/koans/about_scoring_project.rb b/src/about_scoring_project.rb similarity index 63% rename from koans/about_scoring_project.rb rename to src/about_scoring_project.rb index 69aeae010..4031eb44c 100644 --- a/koans/about_scoring_project.rb +++ b/src/about_scoring_project.rb @@ -1,13 +1,13 @@ -require 'edgecase' +require File.expand_path(File.dirname(__FILE__) + '/neo') # Greed is a dice game where you roll up to five dice to accumulate -# points. The following "score" function will be used calculate the +# points. The following "score" function will be used to calculate the # score of a single roll of the dice. # # A greed roll is scored as follows: # # * A set of three ones is 1000 points -# +# # * A set of three numbers (other than ones) is worth 100 times the # number. (e.g. three fives is 500 points). # @@ -25,15 +25,36 @@ # score([3,4,5,3,3]) => 350 points # score([1,5,1,2,4]) => 250 points # -# More scoing examples are given in the tests below: +# More scoring examples are given in the tests below: # # Your goal is to write the score method. def score(dice) # You need to write this method + #-- + result = 0 + (1..6).each do |face| + count = dice.select { |n| n == face }.size + while count > 0 + if count >= 3 + result += (face == 1) ? 1000 : 100 * face + count -= 3 + elsif face == 5 + result += count * 50 + count = 0 + elsif face == 1 + result += count * 100 + count = 0 + else + count = 0 + end + end + end + result + #++ end -class AboutScoringAssignment < EdgeCase::Koan +class AboutScoringProject < Neo::Koan def test_score_of_an_empty_list_is_zero assert_equal 0, score([]) end @@ -46,8 +67,8 @@ def test_score_of_a_single_roll_of_1_is_100 assert_equal 100, score([1]) end - def test_score_of_mulitple_1s_and_5s_is_the_sum - assert_equal 200, score([1,5,5,1]) + def test_score_of_multiple_1s_and_5s_is_the_sum_of_individual_scores + assert_equal 300, score([1,5,5,1]) end def test_score_of_single_2s_3s_4s_and_6s_are_zero @@ -69,6 +90,9 @@ def test_score_of_other_triples_is_100x def test_score_of_mixed_is_sum assert_equal 250, score([2,5,2,2,3]) assert_equal 550, score([5,5,5,5]) + assert_equal 1100, score([1,1,1,1]) + assert_equal 1200, score([1,1,1,1,1]) + assert_equal 1150, score([1,1,1,5,1]) end end diff --git a/koans/about_strings.rb b/src/about_strings.rb similarity index 57% rename from koans/about_strings.rb rename to src/about_strings.rb index db3289aad..c74bef2ad 100644 --- a/koans/about_strings.rb +++ b/src/about_strings.rb @@ -1,38 +1,38 @@ -require 'edgecase' +require File.expand_path(File.dirname(__FILE__) + '/neo') -class AboutStrings < EdgeCase::Koan +class AboutStrings < Neo::Koan def test_double_quoted_strings_are_strings string = "Hello, World" - assert_equal __, string.is_a?(String) + assert_equal __(true), string.is_a?(String) end def test_single_quoted_strings_are_also_strings string = 'Goodbye, World' - assert_equal __, string.is_a?(String) + assert_equal __(true), string.is_a?(String) end def test_use_single_quotes_to_create_string_with_double_quotes string = 'He said, "Go Away."' - assert_equal __, string + assert_equal __('He said, "Go Away."'), string end def test_use_double_quotes_to_create_strings_with_single_quotes string = "Don't" - assert_equal __, string + assert_equal __("Don't"), string end def test_use_backslash_for_those_hard_cases a = "He said, \"Don't\"" b = 'He said, "Don\'t"' - assert_equal __, a == b + assert_equal __(true), a == b end def test_use_flexible_quoting_to_handle_really_hard_cases a = %(flexible quotes can handle both ' and " characters) b = %!flexible quotes can handle both ' and " characters! c = %{flexible quotes can handle both ' and " characters} - assert_equal __, a == b - assert_equal __, a == c + assert_equal __(true), a == b + assert_equal __(true), a == c end def test_flexible_quotes_can_handle_multiple_lines @@ -40,7 +40,9 @@ def test_flexible_quotes_can_handle_multiple_lines It was the best of times, It was the worst of times. } - assert_equal __, long_string.size + assert_equal __(54), long_string.length + assert_equal __(3), long_string.lines.count + assert_equal __("\n"), long_string[0,1] end def test_here_documents_can_also_handle_multiple_lines @@ -48,27 +50,29 @@ def test_here_documents_can_also_handle_multiple_lines It was the best of times, It was the worst of times. EOS - assert_equal __, long_string.size + assert_equal __(53), long_string.length + assert_equal __(2), long_string.lines.count + assert_equal __("I"), long_string[0,1] end def test_plus_will_concatenate_two_strings string = "Hello, " + "World" - assert_equal __, string + assert_equal __("Hello, World"), string end def test_plus_concatenation_will_leave_the_original_strings_unmodified hi = "Hello, " there = "World" string = hi + there - assert_equal __, hi - assert_equal __, there + assert_equal __("Hello, "), hi + assert_equal __("World"), there end def test_plus_equals_will_concatenate_to_the_end_of_a_string hi = "Hello, " there = "World" hi += there - assert_equal __, hi + assert_equal __("Hello, World"), hi end def test_plus_equals_also_will_leave_the_original_string_unmodified @@ -76,15 +80,15 @@ def test_plus_equals_also_will_leave_the_original_string_unmodified hi = original_string there = "World" hi += there - assert_equal __, original_string + assert_equal __("Hello, "), original_string end def test_the_shovel_operator_will_also_append_content_to_a_string hi = "Hello, " there = "World" hi << there - assert_equal __, hi - assert_equal __, there + assert_equal __("Hello, World"), hi + assert_equal __("World"), there end def test_the_shovel_operator_modifies_the_original_string @@ -92,7 +96,7 @@ def test_the_shovel_operator_modifies_the_original_string hi = original_string there = "World" hi << there - assert_equal __, original_string + assert_equal __("Hello, World"), original_string # THINK ABOUT IT: # @@ -102,75 +106,92 @@ def test_the_shovel_operator_modifies_the_original_string def test_double_quoted_string_interpret_escape_characters string = "\n" - assert_equal __, string.size + assert_equal __(1), string.size end def test_single_quoted_string_do_not_interpret_escape_characters string = '\n' - assert_equal __, string.size + assert_equal __(2), string.size end def test_single_quotes_sometimes_interpret_escape_characters string = '\\\'' - assert_equal __, string.size - assert_equal __, string + assert_equal __(2), string.size + assert_equal __("\\'"), string end def test_double_quoted_strings_interpolate_variables value = 123 string = "The value is #{value}" - assert_equal __, string + assert_equal __("The value is 123"), string end def test_single_quoted_strings_do_not_interpolate value = 123 string = 'The value is #{value}' - assert_equal __, string + assert_equal __('The value is #{value}'), string end - def test_any_ruby_expression_my_be_interpolated + def test_any_ruby_expression_may_be_interpolated string = "The square root of 5 is #{Math.sqrt(5)}" - assert_equal __, string + assert_equal __("The square root of 5 is 2.23606797749979"), string end def test_you_can_get_a_substring_from_a_string string = "Bacon, lettuce and tomato" - assert_equal __, string[7,3] - assert_equal __, string[7..9] + assert_equal __("let"), string[7,3] + assert_equal __("let"), string[7..9] end def test_you_can_get_a_single_character_from_a_string string = "Bacon, lettuce and tomato" - assert_equal __, string[1] + assert_equal __(97, 'a'), string[1] # Surprised? end - def test_single_characters_are_represented_by_integers - assert_equal __, ?a - assert_equal __, ?a == 97 + in_ruby_version("1.8") do + def test_in_older_ruby_single_characters_are_represented_by_integers + assert_equal __(97, 'a'), ?a + assert_equal __(true, false), ?a == 97 - assert_equal __, ?b == (?a + 1) + assert_equal __(true), ?b == (?a + 1) + end + end + + in_ruby_version("1.9", "2") do + def test_in_modern_ruby_single_characters_are_represented_by_strings + assert_equal __('a'), ?a + assert_equal __(false), ?a == 97 + end end def test_strings_can_be_split string = "Sausage Egg Cheese" words = string.split - assert_equal [__, __, __], words + assert_equal [__("Sausage"), __("Egg"), __("Cheese")], words end def test_strings_can_be_split_with_different_patterns string = "the:rain:in:spain" words = string.split(/:/) - assert_equal [__, __, __, __], words + assert_equal [__("the"), __("rain"), __("in"), __("spain")], words # NOTE: Patterns are formed from Regular Expressions. Ruby has a - # very powerful Regular Expression library. Unfortunately, time - # does not permit us to explore it in detail in Ruby 101. + # very powerful Regular Expression library. We will become + # enlightened about them soon. end def test_strings_can_be_joined words = ["Now", "is", "the", "time"] - assert_equal __, words.join(" ") + assert_equal __("Now is the time"), words.join(" ") + end + + def test_strings_are_unique_objects + a = "a string" + b = "a string" + + assert_equal __(true), a == b + assert_equal __(false), a.object_id == b.object_id end end diff --git a/src/about_symbols.rb b/src/about_symbols.rb new file mode 100644 index 000000000..5963c65fd --- /dev/null +++ b/src/about_symbols.rb @@ -0,0 +1,100 @@ +require File.expand_path(File.dirname(__FILE__) + '/neo') + +class AboutSymbols < Neo::Koan + def test_symbols_are_symbols + symbol = :ruby + assert_equal __(true), symbol.is_a?(Symbol) + end + + def test_symbols_can_be_compared + symbol1 = :a_symbol + symbol2 = :a_symbol + symbol3 = :something_else + + assert_equal __(true), symbol1 == symbol2 + assert_equal __(false), symbol1 == symbol3 + end + + def test_identical_symbols_are_a_single_internal_object + symbol1 = :a_symbol + symbol2 = :a_symbol + + assert_equal __(true), symbol1 == symbol2 + assert_equal __(true), symbol1.object_id == symbol2.object_id + end + + def test_method_names_become_symbols + symbols_as_strings = Symbol.all_symbols.map { |x| x.to_s } + assert_equal __(true), symbols_as_strings.include?("test_method_names_become_symbols") + end + + # THINK ABOUT IT: + # + # Why do we convert the list of symbols to strings and then compare + # against the string value rather than against symbols? + + in_ruby_version("mri") do + RubyConstant = "What is the sound of one hand clapping?" + def test_constants_become_symbols + all_symbols_as_strings = Symbol.all_symbols.map { |x| x.to_s } + + assert_equal __(true), all_symbols_as_strings.include?(__("RubyConstant")) + end + end + + def test_symbols_can_be_made_from_strings + string = "catsAndDogs" + assert_equal __(:catsAndDogs), string.to_sym + end + + def test_symbols_with_spaces_can_be_built + symbol = :"cats and dogs" + + assert_equal __("cats and dogs").to_sym, symbol + end + + def test_symbols_with_interpolation_can_be_built + value = "and" + symbol = :"cats #{value} dogs" + + assert_equal __("cats and dogs").to_sym, symbol + end + + def test_to_s_is_called_on_interpolated_symbols + symbol = :cats + string = "It is raining #{symbol} and dogs." + + assert_equal __('It is raining cats and dogs.'), string + end + + def test_symbols_are_not_strings + symbol = :ruby + assert_equal __(false), symbol.is_a?(String) + assert_equal __(false), symbol.eql?("ruby") + end + + def test_symbols_do_not_have_string_methods + symbol = :not_a_string + assert_equal __(false), symbol.respond_to?(:each_char) + assert_equal __(false), symbol.respond_to?(:reverse) + end + + # It's important to realize that symbols are not "immutable + # strings", though they are immutable. None of the + # interesting string operations are available on symbols. + + def test_symbols_cannot_be_concatenated + # Exceptions will be pondered further down the path + assert_raise(___(NoMethodError)) do + :cats + :dogs + end + end + + def test_symbols_can_be_dynamically_created + assert_equal __(:catsdogs), ("cats" + "dogs").to_sym + end + + # THINK ABOUT IT: + # + # Why is it not a good idea to dynamically create a lot of symbols? +end diff --git a/src/about_to_str.rb b/src/about_to_str.rb new file mode 100644 index 000000000..669a4df01 --- /dev/null +++ b/src/about_to_str.rb @@ -0,0 +1,54 @@ +require File.expand_path(File.dirname(__FILE__) + '/neo') + +class AboutToStr < Neo::Koan + + class CanNotBeTreatedAsString + def to_s + "non-string-like" + end + end + + def test_to_s_returns_a_string_representation + not_like_a_string = CanNotBeTreatedAsString.new + assert_equal __("non-string-like"), not_like_a_string.to_s + end + + def test_normally_objects_cannot_be_used_where_strings_are_expected + assert_raise(___(TypeError)) do + File.exist?(CanNotBeTreatedAsString.new) + end + end + + # ------------------------------------------------------------------ + + class CanBeTreatedAsString + def to_s + "string-like" + end + + def to_str + to_s + end + end + + def test_to_str_also_returns_a_string_representation + like_a_string = CanBeTreatedAsString.new + assert_equal __("string-like"), like_a_string.to_str + end + + def test_to_str_allows_objects_to_be_treated_as_strings + assert_equal __(false), File.exist?(CanBeTreatedAsString.new) + end + + # ------------------------------------------------------------------ + + def acts_like_a_string?(string) + string = string.to_str if string.respond_to?(:to_str) + string.is_a?(String) + end + + def test_user_defined_code_can_check_for_to_str + assert_equal __(false), acts_like_a_string?(CanNotBeTreatedAsString.new) + assert_equal __(true), acts_like_a_string?(CanBeTreatedAsString.new) + end +end diff --git a/koans/about_triangle_project.rb b/src/about_triangle_project.rb similarity index 85% rename from koans/about_triangle_project.rb rename to src/about_triangle_project.rb index 5c1855cf1..2ac9d9ad0 100644 --- a/koans/about_triangle_project.rb +++ b/src/about_triangle_project.rb @@ -1,9 +1,9 @@ -require 'edgecase' +require File.expand_path(File.dirname(__FILE__) + '/neo') # You need to write the triangle method in the file 'triangle.rb' -require 'triangle.rb' +require './triangle' -class AboutTriangleAssignment < EdgeCase::Koan +class AboutTriangleProject < Neo::Koan def test_equilateral_triangles_have_equal_sides assert_equal :equilateral, triangle(2, 2, 2) assert_equal :equilateral, triangle(10, 10, 10) @@ -22,4 +22,3 @@ def test_scalene_triangles_have_no_equal_sides assert_equal :scalene, triangle(5, 4, 2) end end - diff --git a/src/about_triangle_project_2.rb b/src/about_triangle_project_2.rb new file mode 100644 index 000000000..fdeb8dbb2 --- /dev/null +++ b/src/about_triangle_project_2.rb @@ -0,0 +1,16 @@ +require File.expand_path(File.dirname(__FILE__) + '/neo') + +# You need to write the triangle method in the file 'triangle.rb' +require './triangle.rb' + +class AboutTriangleProject2 < Neo::Koan + # The first assignment did not talk about how to handle errors. + # Let's handle that part now. + def test_illegal_triangles_throw_exceptions + assert_raise(TriangleError) do triangle(0, 0, 0) end + assert_raise(TriangleError) do triangle(3, 4, -5) end + assert_raise(TriangleError) do triangle(1, 1, 3) end + assert_raise(TriangleError) do triangle(2, 4, 2) end + # HINT: for tips, see http://stackoverflow.com/questions/3834203/ruby-koan-151-raising-exceptions + end +end diff --git a/src/about_true_and_false.rb b/src/about_true_and_false.rb new file mode 100644 index 000000000..470489eb0 --- /dev/null +++ b/src/about_true_and_false.rb @@ -0,0 +1,33 @@ +require File.expand_path(File.dirname(__FILE__) + '/neo') + +class AboutTrueAndFalse < Neo::Koan + def truth_value(condition) + if condition + :true_stuff + else + :false_stuff + end + end + + def test_true_is_treated_as_true + assert_equal __(:true_stuff), truth_value(true) + end + + def test_false_is_treated_as_false + assert_equal __(:false_stuff), truth_value(false) + end + + def test_nil_is_treated_as_false_too + assert_equal __(:false_stuff), truth_value(nil) + end + + def test_everything_else_is_treated_as_true + assert_equal __(:true_stuff), truth_value(1) + assert_equal __(:true_stuff), truth_value(0) + assert_equal __(:true_stuff), truth_value([]) + assert_equal __(:true_stuff), truth_value({}) + assert_equal __(:true_stuff), truth_value("Strings") + assert_equal __(:true_stuff), truth_value("") + end + +end diff --git a/koans/example_file.txt b/src/example_file.txt similarity index 100% rename from koans/example_file.txt rename to src/example_file.txt diff --git a/src/koans.watchr b/src/koans.watchr new file mode 100644 index 000000000..b740cf9c5 --- /dev/null +++ b/src/koans.watchr @@ -0,0 +1,3 @@ +watch( '.*\.rb' ) do + system 'rake' +end diff --git a/src/neo.rb b/src/neo.rb new file mode 100644 index 000000000..18e7ef734 --- /dev/null +++ b/src/neo.rb @@ -0,0 +1,535 @@ +#!/usr/bin/env ruby +# -*- ruby -*- + +begin + require 'win32console' +rescue LoadError +end + +# -------------------------------------------------------------------- +# Support code for the Ruby Koans. +# -------------------------------------------------------------------- + +class FillMeInError < StandardError +end + +def ruby_version?(version) + RUBY_VERSION =~ /^#{version}/ || + (version == 'jruby' && defined?(JRUBY_VERSION)) || + (version == 'mri' && ! defined?(JRUBY_VERSION)) +end + +def in_ruby_version(*versions) + yield if versions.any? { |v| ruby_version?(v) } +end + +in_ruby_version("1.8") do + class KeyError < StandardError + end +end + +# Standard, generic replacement value. +# If value19 is given, it is used in place of value for Ruby 1.9. +def __(value="FILL ME IN", value19=:mu) + if RUBY_VERSION < "1.9" + value + else + (value19 == :mu) ? value : value19 + end +end + +# Numeric replacement value. +def _n_(value=999999, value19=:mu) + if RUBY_VERSION < "1.9" + value + else + (value19 == :mu) ? value : value19 + end +end + +# Error object replacement value. +def ___(value=FillMeInError, value19=:mu) + if RUBY_VERSION < "1.9" + value + else + (value19 == :mu) ? value : value19 + end +end + +# Method name replacement. +class Object + def ____(method=nil) + if method + self.send(method) + end + end + + in_ruby_version("1.9", "2") do + public :method_missing + end +end + +class String + def side_padding(width) + extra = width - self.size + if width < 0 + self + else + left_padding = extra / 2 + right_padding = (extra+1)/2 + (" " * left_padding) + self + (" " *right_padding) + end + end +end + +module Neo + class << self + def simple_output + ENV['SIMPLE_KOAN_OUTPUT'] == 'true' + end + end + + module Color + #shamelessly stolen (and modified) from redgreen + COLORS = { + :clear => 0, :black => 30, :red => 31, + :green => 32, :yellow => 33, :blue => 34, + :magenta => 35, :cyan => 36, + } + + module_function + + COLORS.each do |color, value| + module_eval "def #{color}(string); colorize(string, #{value}); end" + module_function color + end + + def colorize(string, color_value) + if use_colors? + color(color_value) + string + color(COLORS[:clear]) + else + string + end + end + + def color(color_value) + "\e[#{color_value}m" + end + + def use_colors? + return false if ENV['NO_COLOR'] + if ENV['ANSI_COLOR'].nil? + if using_windows? + using_win32console + else + return true + end + else + ENV['ANSI_COLOR'] =~ /^(t|y)/i + end + end + + def using_windows? + File::ALT_SEPARATOR + end + + def using_win32console + defined? Win32::Console + end + end + + module Assertions + FailedAssertionError = Class.new(StandardError) + + def flunk(msg) + raise FailedAssertionError, msg + end + + def assert(condition, msg=nil) + msg ||= "Failed assertion." + flunk(msg) unless condition + true + end + + def assert_equal(expected, actual, msg=nil) + msg ||= "Expected #{expected.inspect} to equal #{actual.inspect}" + assert(expected == actual, msg) + end + + def assert_not_equal(expected, actual, msg=nil) + msg ||= "Expected #{expected.inspect} to not equal #{actual.inspect}" + assert(expected != actual, msg) + end + + def assert_nil(actual, msg=nil) + msg ||= "Expected #{actual.inspect} to be nil" + assert(nil == actual, msg) + end + + def assert_not_nil(actual, msg=nil) + msg ||= "Expected #{actual.inspect} to not be nil" + assert(nil != actual, msg) + end + + def assert_match(pattern, actual, msg=nil) + msg ||= "Expected #{actual.inspect} to match #{pattern.inspect}" + assert pattern =~ actual, msg + end + + def assert_raise(exception) + begin + yield + rescue Exception => ex + expected = ex.is_a?(exception) + assert(expected, "Exception #{exception.inspect} expected, but #{ex.inspect} was raised") + return ex + end + flunk "Exception #{exception.inspect} expected, but nothing raised" + end + + def assert_nothing_raised + begin + yield + rescue Exception => ex + flunk "Expected nothing to be raised, but exception #{exception.inspect} was raised" + end + end + end + + class Sensei + attr_reader :failure, :failed_test, :pass_count + + FailedAssertionError = Assertions::FailedAssertionError + + def initialize + @pass_count = 0 + @failure = nil + @failed_test = nil + @observations = [] + end + + PROGRESS_FILE_NAME = '.path_progress' + + def add_progress(prog) + @_contents = nil + exists = File.exists?(PROGRESS_FILE_NAME) + File.open(PROGRESS_FILE_NAME,'a+') do |f| + f.print "#{',' if exists}#{prog}" + end + end + + def progress + if @_contents.nil? + if File.exists?(PROGRESS_FILE_NAME) + File.open(PROGRESS_FILE_NAME,'r') do |f| + @_contents = f.read.to_s.gsub(/\s/,'').split(',') + end + else + @_contents = [] + end + end + @_contents + end + + def observe(step) + if step.passed? + @pass_count += 1 + if @pass_count > progress.last.to_i + @observations << Color.green("#{step.koan_file}##{step.name} has expanded your awareness.") + end + else + @failed_test = step + @failure = step.failure + add_progress(@pass_count) + @observations << Color.red("#{step.koan_file}##{step.name} has damaged your karma.") + throw :neo_exit + end + end + + def failed? + ! @failure.nil? + end + + def assert_failed? + failure.is_a?(FailedAssertionError) + end + + def instruct + if failed? + @observations.each{|c| puts c } + encourage + guide_through_error + a_zenlike_statement + show_progress + else + end_screen + end + end + + def show_progress + bar_width = 50 + total_tests = Neo::Koan.total_tests + scale = bar_width.to_f/total_tests + print Color.green("your path thus far [") + happy_steps = (pass_count*scale).to_i + happy_steps = 1 if happy_steps == 0 && pass_count > 0 + print Color.green('.'*happy_steps) + if failed? + print Color.red('X') + print Color.cyan('_'*(bar_width-1-happy_steps)) + end + print Color.green(']') + print " #{pass_count}/#{total_tests}" + puts + end + + def end_screen + if Neo.simple_output + boring_end_screen + else + artistic_end_screen + end + end + + def boring_end_screen + puts "Mountains are again merely mountains" + end + + def artistic_end_screen + "JRuby 1.9.x Koans" + ruby_version = "(in #{'J' if defined?(JRUBY_VERSION)}Ruby #{defined?(JRUBY_VERSION) ? JRUBY_VERSION : RUBY_VERSION})" + ruby_version = ruby_version.side_padding(54) + completed = <<-ENDTEXT + ,, , ,, + : ::::, :::, + , ,,: :::::::::::::,, :::: : , + , ,,, ,:::::::::::::::::::, ,: ,: ,, + :, ::, , , :, ,::::::::::::::::::, ::: ,:::: + : : ::, ,:::::::: ::, ,:::: + , ,::::: :,:::::::,::::, + ,: , ,:,,: ::::::::::::: + ::,: ,,:::, ,::::::::::::, + ,:::, :,,::: ::::::::::::, + ,::: :::::::, Mountains are again merely mountains ,:::::::::::: + :::,,,:::::: :::::::::::: + ,:::::::::::, ::::::::::::, + :::::::::::, ,:::::::::::: +::::::::::::: ,:::::::::::: +:::::::::::: Ruby Koans :::::::::::: +::::::::::::#{ ruby_version },:::::::::::: +:::::::::::, , ::::::::::: +,:::::::::::::, brought to you by ,,:::::::::::: +:::::::::::::: ,:::::::::::: + ::::::::::::::, ,::::::::::::: + ::::::::::::, Neo Software Artisans , :::::::::::: + :,::::::::: :::: ::::::::::::: + ,::::::::::: ,: ,,:::::::::::::, + :::::::::::: ,::::::::::::::, + :::::::::::::::::, :::::::::::::::: + :::::::::::::::::::, :::::::::::::::: + ::::::::::::::::::::::, ,::::,:, , ::::,::: + :::::::::::::::::::::::, ::,: ::,::, ,,: :::: + ,:::::::::::::::::::: ::,, , ,, ,:::: + ,:::::::::::::::: ::,, , ,:::, + ,:::: , ,, + ,,, +ENDTEXT + puts completed + end + + def encourage + puts + puts "The Master says:" + puts Color.cyan(" You have not yet reached enlightenment.") + if ((recents = progress.last(5)) && recents.size == 5 && recents.uniq.size == 1) + puts Color.cyan(" I sense frustration. Do not be afraid to ask for help.") + elsif progress.last(2).size == 2 && progress.last(2).uniq.size == 1 + puts Color.cyan(" Do not lose hope.") + elsif progress.last.to_i > 0 + puts Color.cyan(" You are progressing. Excellent. #{progress.last} completed.") + end + end + + def guide_through_error + puts + puts "The answers you seek..." + puts Color.red(indent(failure.message).join) + puts + puts "Please meditate on the following code:" + puts embolden_first_line_only(indent(find_interesting_lines(failure.backtrace))) + puts + end + + def embolden_first_line_only(text) + first_line = true + text.collect { |t| + if first_line + first_line = false + Color.red(t) + else + Color.cyan(t) + end + } + end + + def indent(text) + text = text.split(/\n/) if text.is_a?(String) + text.collect{|t| " #{t}"} + end + + def find_interesting_lines(backtrace) + backtrace.reject { |line| + line =~ /neo\.rb/ + } + end + + # Hat's tip to Ara T. Howard for the zen statements from his + # metakoans Ruby Quiz (http://rubyquiz.com/quiz67.html) + def a_zenlike_statement + if !failed? + zen_statement = "Mountains are again merely mountains" + else + zen_statement = case (@pass_count % 10) + when 0 + "mountains are merely mountains" + when 1, 2 + "learn the rules so you know how to break them properly" + when 3, 4 + "remember that silence is sometimes the best answer" + when 5, 6 + "sleep is the best meditation" + when 7, 8 + "when you lose, don't lose the lesson" + else + "things are not what they appear to be: nor are they otherwise" + end + end + puts Color.green(zen_statement) + end + end + + class Koan + include Assertions + + attr_reader :name, :failure, :koan_count, :step_count, :koan_file + + def initialize(name, koan_file=nil, koan_count=0, step_count=0) + @name = name + @failure = nil + @koan_count = koan_count + @step_count = step_count + @koan_file = koan_file + end + + def passed? + @failure.nil? + end + + def failed(failure) + @failure = failure + end + + def setup + end + + def teardown + end + + def meditate + setup + begin + send(name) + rescue StandardError, Neo::Sensei::FailedAssertionError => ex + failed(ex) + ensure + begin + teardown + rescue StandardError, Neo::Sensei::FailedAssertionError => ex + failed(ex) if passed? + end + end + self + end + + # Class methods for the Neo test suite. + class << self + def inherited(subclass) + subclasses << subclass + end + + def method_added(name) + testmethods << name if !tests_disabled? && Koan.test_pattern =~ name.to_s + end + + def end_of_enlightenment + @tests_disabled = true + end + + def command_line(args) + args.each do |arg| + case arg + when /^-n\/(.*)\/$/ + @test_pattern = Regexp.new($1) + when /^-n(.*)$/ + @test_pattern = Regexp.new(Regexp.quote($1)) + else + if File.exist?(arg) + load(arg) + else + fail "Unknown command line argument '#{arg}'" + end + end + end + end + + # Lazy initialize list of subclasses + def subclasses + @subclasses ||= [] + end + + # Lazy initialize list of test methods. + def testmethods + @test_methods ||= [] + end + + def tests_disabled? + @tests_disabled ||= false + end + + def test_pattern + @test_pattern ||= /^test_/ + end + + def total_tests + self.subclasses.inject(0){|total, k| total + k.testmethods.size } + end + end + end + + class ThePath + def walk + sensei = Neo::Sensei.new + each_step do |step| + sensei.observe(step.meditate) + end + sensei.instruct + end + + def each_step + catch(:neo_exit) { + step_count = 0 + Neo::Koan.subclasses.each_with_index do |koan,koan_index| + koan.testmethods.each do |method_name| + step = koan.new(method_name, koan.to_s, koan_index+1, step_count+=1) + yield step + end + end + } + end + end +end + +END { + Neo::Koan.command_line(ARGV) + Neo::ThePath.new.walk +} diff --git a/koans/path_to_enlightenment.rb b/src/path_to_enlightenment.rb similarity index 67% rename from koans/path_to_enlightenment.rb rename to src/path_to_enlightenment.rb index f8e323f95..9e8ccbe2b 100644 --- a/koans/path_to_enlightenment.rb +++ b/src/path_to_enlightenment.rb @@ -1,12 +1,21 @@ # The path to Ruby Enlightenment starts with the following: -require 'about_basics' +$LOAD_PATH << File.dirname(__FILE__) + +require 'about_asserts' require 'about_nil' +require 'about_objects' require 'about_arrays' require 'about_array_assignment' require 'about_hashes' require 'about_strings' +require 'about_symbols' +require 'about_regular_expressions' require 'about_methods' +in_ruby_version("2") do + require 'about_keyword_arguments' +end +require 'about_constants' require 'about_control_statements' require 'about_true_and_false' require 'about_triangle_project' @@ -17,6 +26,7 @@ require 'about_sandwich_code' require 'about_scoring_project' require 'about_classes' +require 'about_open_classes' require 'about_dice_project' require 'about_inheritance' require 'about_modules' @@ -24,4 +34,8 @@ require 'about_class_methods' require 'about_message_passing' require 'about_proxy_object_project' +require 'about_to_str' +in_ruby_version("jruby") do + require 'about_java_interop' +end require 'about_extra_credit' diff --git a/koans/triangle.rb b/src/triangle.rb similarity index 78% rename from koans/triangle.rb rename to src/triangle.rb index 8df052a1e..ef4859bac 100644 --- a/koans/triangle.rb +++ b/src/triangle.rb @@ -15,6 +15,12 @@ # def triangle(a, b, c) # WRITE THIS CODE + #-- + a, b, c = [a, b, c].sort + fail TriangleError if (a+b) <= c + sides = [a, b, c].uniq + [nil, :equilateral, :isosceles, :scalene][sides.size] + #++ end # Error class used in part 2. No need to change this code.