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