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.